@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
1,078 lines • 144 kB
JavaScript
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 { RouterModule } from '@angular/router';
import * as i4 from '@c8y/ngx-components';
import { gettext, ValidationPattern, BuiltInActionType, Status, memoize, NavigatorNode, hookNavigator, hookRoute, CoreModule, FormsModule, ModalSelectionMode, ViewContext } from '@c8y/ngx-components';
import * as i1$1 from '@c8y/ngx-components/repository/shared';
import { PRODUCT_EXPERIENCE_REPOSITORY_SHARED, RepositoryType, RepositoryItemNameGridColumn, DescriptionGridColumn, DeviceTypeGridColumn, TypeGridColumn, VersionsGridColumn, SoftwareTypeComponent, 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 i5 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 * as i7 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i6 from 'ngx-bootstrap/popover';
import { PopoverModule } from 'ngx-bootstrap/popover';
import { __decorate, __metadata } from 'tslib';
import * as i2 from '@c8y/client';
import { QueriesUtil, OperationStatus } from '@c8y/client';
import * as i10 from 'ngx-bootstrap/tooltip';
import { TooltipModule } from 'ngx-bootstrap/tooltip';
import { DeviceGridModule } from '@c8y/ngx-components/device-grid';
import * as i3 from '@c8y/ngx-components/operations/operation-details';
import { 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: "19.2.14", ngImport: i0, type: AddSoftwareModalComponent, deps: [{ token: i1.BsModalRef }, { token: i1$1.RepositoryService }, { token: i4.AlertService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: AddSoftwareModalComponent, isStandalone: false, 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 class=\"modal-title\" translate id=\"addSoftwareModalTitle\">Add software</div>\n </div>\n <div class=\"p-16 text-center separator-bottom\" *ngIf=\"!softwarePreselected\">\n <p class=\"text-medium text-16\" translate>Select or create new software</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 class=\"modal-body\" id=\"addSoftwareModalDescription\">\n <div [hidden]=\"softwarePreselected\">\n <c8y-form-group>\n <label for=\"softwareName\" translate>Software</label>\n <c8y-typeahead\n [(ngModel)]=\"model.selected\"\n name=\"softwareName\"\n placeholder=\"{{ 'Select or enter' | translate }}\"\n (onSearch)=\"onInput.next($event)\"\n [required]=\"true\"\n >\n <c8y-li\n *c8yFor=\"\n let software of softwaresResult;\n loadMore: 'auto';\n notFound: notFoundTemplate\n \"\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\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 class=\"bg-level-2 p-8\" *ngIf=\"(onInput | async)?.length > 0\">\n <span translate>No match found.</span>\n <button\n class=\"btn btn-primary btn-xs m-l-8\"\n type=\"button\"\n title=\"{{ 'Add new`software`' | translate }}\"\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 for=\"softwareDescription\" translate>Description</label>\n <input\n id=\"softwareDescription\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"description\"\n [(ngModel)]=\"model.description\"\n placeholder=\"{{ 'e.g. Cloud connectivity software' | translate }}\"\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 class=\"control-label\" for=\"softwareDeviceTypeFilter\">\n {{ 'Device type filter' | translate }}\n <button\n class=\"btn-help\"\n type=\"button\"\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 ></button>\n </label>\n <input\n id=\"softwareDeviceTypeFilter\"\n class=\"form-control\"\n name=\"softwareDeviceTypeFilter\"\n [(ngModel)]=\"model.deviceType\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_Linux\"\n [disabled]=\"model.selected?.id\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label for=\"softwareType\" translate>Software type</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 for=\"softwareVersion\" translate>Version</label>\n <input\n id=\"softwareVersion\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"version\"\n [(ngModel)]=\"model.version\"\n placeholder=\"{{ 'e.g.' | translate }} 1.0.0\"\n [required]=\"true\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <div class=\"legend form-block m-t-40\" translate>Software file</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 type=\"button\"\n title=\"{{ 'Cancel' | translate }}\"\n (click)=\"cancel()\"\n [disabled]=\"saving\"\n >\n {{ 'Cancel' | translate }}\n </button>\n\n <button\n class=\"btn btn-primary\"\n type=\"submit\"\n title=\"{{ 'Add software' | translate }}\"\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: i7.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i7.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i4.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i4.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: i4.HighlightComponent, selector: "c8y-highlight", inputs: ["pattern", "text", "elementClass", "shouldTrimPattern"] }, { kind: "component", type: i4.TypeaheadComponent, selector: "c8y-typeahead", inputs: ["required", "maxlength", "disabled", "allowFreeEntries", "placeholder", "displayProperty", "icon", "name", "autoClose", "hideNew", "container", "selected", "highlightFirstItem"], outputs: ["onSearch", "onIconClick"] }, { kind: "directive", type: i5.ɵNgNoValidate, selector: "form:not([ngNoForm]):not([ngNativeValidate])" }, { kind: "directive", type: i5.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: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.NgControlStatusGroup, selector: "[formGroupName],[formArrayName],[ngModelGroup],[formGroup],form:not([ngNoForm]),[ngForm]" }, { kind: "directive", type: i5.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i5.PatternValidator, selector: "[pattern][formControlName],[pattern][formControl],[pattern][ngModel]", inputs: ["pattern"] }, { kind: "directive", type: i5.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i5.NgForm, selector: "form:not([ngNoForm]):not([formGroup]),ng-form,[ngForm]", inputs: ["ngFormOptions"], outputs: ["ngSubmit"], exportAs: ["ngForm"] }, { kind: "component", type: i4.FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: i4.RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "component", type: i4.ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: i4.FilePickerComponent, selector: "c8y-file-picker", inputs: ["maxAllowedFiles", "uploadChoice", "allowedUploadChoices", "fileUrl", "fileBinary", "config", "filePickerIndex", "fileUrlPopover"], outputs: ["onFilesPicked"] }, { kind: "directive", type: i4.ProductExperienceDirective, selector: "[c8yProductExperience]", inputs: ["actionName", "actionData", "inherit", "suppressDataOverriding"] }, { kind: "directive", type: i6.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: i1$1.SoftwareTypeComponent, selector: "c8y-software-type", inputs: ["softwareTypeMO", "disabled", "style", "required", "placeholder", "emitResultsOnly", "showBtnInNotFoundMessage", "allowFreeEntries", "showClearSelectionOption", "clearSelectionOptionLabel", "presetSoftwareTypes"], outputs: ["onSelectSoftware"] }, { kind: "pipe", type: i7.AsyncPipe, name: "async" }, { kind: "pipe", type: i4.C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AddSoftwareModalComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-add-software-software-modal', standalone: false, template: "<div class=\"viewport-modal\">\n <div class=\"modal-header dialog-header\">\n <i [c8yIcon]=\"'c8y-tools'\"></i>\n <div class=\"modal-title\" translate id=\"addSoftwareModalTitle\">Add software</div>\n </div>\n <div class=\"p-16 text-center separator-bottom\" *ngIf=\"!softwarePreselected\">\n <p class=\"text-medium text-16\" translate>Select or create new software</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 class=\"modal-body\" id=\"addSoftwareModalDescription\">\n <div [hidden]=\"softwarePreselected\">\n <c8y-form-group>\n <label for=\"softwareName\" translate>Software</label>\n <c8y-typeahead\n [(ngModel)]=\"model.selected\"\n name=\"softwareName\"\n placeholder=\"{{ 'Select or enter' | translate }}\"\n (onSearch)=\"onInput.next($event)\"\n [required]=\"true\"\n >\n <c8y-li\n *c8yFor=\"\n let software of softwaresResult;\n loadMore: 'auto';\n notFound: notFoundTemplate\n \"\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\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 class=\"bg-level-2 p-8\" *ngIf=\"(onInput | async)?.length > 0\">\n <span translate>No match found.</span>\n <button\n class=\"btn btn-primary btn-xs m-l-8\"\n type=\"button\"\n title=\"{{ 'Add new`software`' | translate }}\"\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 for=\"softwareDescription\" translate>Description</label>\n <input\n id=\"softwareDescription\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"description\"\n [(ngModel)]=\"model.description\"\n placeholder=\"{{ 'e.g. Cloud connectivity software' | translate }}\"\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 class=\"control-label\" for=\"softwareDeviceTypeFilter\">\n {{ 'Device type filter' | translate }}\n <button\n class=\"btn-help\"\n type=\"button\"\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 ></button>\n </label>\n <input\n id=\"softwareDeviceTypeFilter\"\n class=\"form-control\"\n name=\"softwareDeviceTypeFilter\"\n [(ngModel)]=\"model.deviceType\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_Linux\"\n [disabled]=\"model.selected?.id\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <label for=\"softwareType\" translate>Software type</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 for=\"softwareVersion\" translate>Version</label>\n <input\n id=\"softwareVersion\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"version\"\n [(ngModel)]=\"model.version\"\n placeholder=\"{{ 'e.g.' | translate }} 1.0.0\"\n [required]=\"true\"\n [pattern]=\"ValidationPattern.rules.noWhiteSpaceOnly.pattern\"\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <div class=\"legend form-block m-t-40\" translate>Software file</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 type=\"button\"\n title=\"{{ 'Cancel' | translate }}\"\n (click)=\"cancel()\"\n [disabled]=\"saving\"\n >\n {{ 'Cancel' | translate }}\n </button>\n\n <button\n class=\"btn btn-primary\"\n type=\"submit\"\n title=\"{{ 'Add software' | translate }}\"\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: i4.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: "19.2.14", ngImport: i0, type: SoftwareListComponent, deps: [{ token: i1$1.RepositoryService }, { token: i4.DataGridService }, { token: i4.ModalService }, { token: i1.BsModalService }, { token: i4$1.TranslateService }, { token: i4.AlertService }, { token: i1$2.Router }, { token: i1$2.ActivatedRoute }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: SoftwareListComponent, isStandalone: false, 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: "directive", type: i7.NgForOf, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "directive", type: i7.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i4.ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "component", type: i4.BreadcrumbComponent, selector: "c8y-breadcrumb" }, { kind: "component", type: i4.BreadcrumbItemComponent, selector: "c8y-breadcrumb-item", inputs: ["icon", "translate", "label", "path", "injector"] }, { kind: "component", type: i4.EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: i4.EmptyStateContextDirective, selector: "[emptyStateContext]" }, { kind: "directive", type: i4.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i4.ColumnDirective, selector: "c8y-column", inputs: ["name"] }, { kind: "component", type: i4.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: "component", type: i4.TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "component", type: i4.HelpComponent, selector: "c8y-help", inputs: ["src", "isCollapsed", "priority", "icon"] }, { kind: "pipe", type: i4.C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareListComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-software-list', standalone: false, 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: i4.DataGridService }, { type: i4.ModalService }, { type: i1.BsModalService }, { type: i4$1.TranslateService }, { type: i4.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: "19.2.14", ngImport: i0, type: SoftwareDetailsComponent, deps: [{ token: i1$2.ActivatedRoute }, { token: i2.InventoryService }, { token: i1$1.RepositoryService }, { token: i4.AlertService }, { token: i4$1.TranslateService }, { token: i4.ModalService }, { token: i1.BsModalService }, { token: i4.GainsightService }, { token: i1$2.Router }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: SoftwareDetailsComponent, isStandalone: false, 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 label=\"{{ 'Management' | translate }}\"\n icon=\"c8y-management\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n path=\"#/software\"\n label=\"{{ 'Software repository' | translate }}\"\n icon=\"c8y-tools\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n label=\"{{ (software$ | async)?.name }}\"\n icon=\"c8y-tools\"\n ></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n type=\"button\"\n title=\"{{ 'Add software' | translate }}\"\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 type=\"button\"\n title=\"{{ 'Reload' | translate }}\"\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 #nameInput\n type=\"text\"\n class=\"form-control\"\n [ngModel]=\"(software$ | async)?.name\"\n #nameModel=\"ngModel\"\n placeholder=\"{{ 'e.g. My software' | translate }}\"\n [ngStyle]=\"{ 'width.ch': (software$ | async)?.name?.length + 2 || 31 }\"\n required\n />\n <span></span>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-primary\"\n type=\"button\"\n title=\"{{ 'Save' | translate }}\"\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 #descriptionInput\n type=\"text\"\n class=\"form-control\"\n [ngModel]=\"(software$ | async)?.description\"\n #descriptionModel=\"ngModel\"\n placeholder=\"{{ 'e.g. Cloud connectivity software' | translate }}\"\n [ngStyle]=\"{ 'width.ch': (software$ | async)?.description?.length + 2 || 31 }\"\n />\n <span></span>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-primary\"\n type=\"button\"\n title=\"{{ 'Save' | translate }}\"\n (click)=\"\n updateSoftware$.next({ description: descriptionInput.value });\n descriptionModel.reset()\n \"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n </div>\n </c8y-form-group>\n </div>\n </div>\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label class=\"control-label\">\n {{ 'Device type' | translate }}\n <button\n class=\"btn-help\"\n type=\"button\"\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 triggers=\"focus\"\n container=\"body\"\n >\n <i c8yIcon=\"question-circle-o\"></i>\n </button>\n </label>\n <div class=\"input-group input-group-editable\">\n <input\n #deviceTypeInput\n type=\"text\"\n class=\"form-control\"\n [ngModel]=\"(software$ | async)?.c8y_Filter?.type\"\n #deviceTypeModel=\"ngModel\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_Linux\"\n [ngStyle]=\"{ 'width.ch': (software$ | async)?.type?.length + 2 || 31 }\"\n />\n <span></span>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-primary\"\n type=\"button\"\n title=\"{{ 'Save' | translate }}\"\n (click)=\"\n updateSoftware$.next({ c8y_Filter: { type: deviceTypeInput.value } });\n deviceTypeModel.reset()\n \"\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 {{ 'Software type' | translate }}\n </label>\n <div class=\"input-group input-group-editable\">\n <c8y-software-type\n [softwareTypeMO]=\"softwareTypeObject\"\n [style]=\"{ 'width.ch': softwareTypeObject?.softwareType?.length + 2 || 31 }\"\n (onSelectSoftware)=\"onSelectSoftwareType($event)\"\n ></c8y-software-type>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-primary\"\n type=\"button\"\n title=\"{{ 'Save' | translate }}\"\n [ngClass]=\"isSoftwareTypeChanged ? '' : 'hidden'\"\n [disabled]=\"softwareTypeObject?.softwareType === ''\"\n (click)=\"\n updateSoftware$.next({ softwareType: softwareTypeObject.softwareType });\n softwareType.resetInput();\n isSoftwareTypeChanged = false\n \"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n </div>\n </c8y-form-group>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"inner-scroll\">\n <div class=\"card-header separator-top-bottom sticky-top bg-component\">\n <div\n class=\"card-title\"\n translate\n >\n Versions\n </div>\n </div>\n\n <div class=\"card-block p-t-0 p-b-24\">\n <c8y-ui-empty-state\n *ngIf=\"(baseVersions$ | async)?.data.length === 0\"\n [icon]=\"'c8y-tools'\"\n [title]=\"'No versions to display.' | translate\"\n [subtitle]=\"'Add a new version by clicking below.' | translate\"\n >\n <p>\n <button\n class=\"btn btn-primary\"\n type=\"button\"\n title=\"{{ 'Add software' | translate }}\"\n (click)=\"addBaseVersion()\"\n >\n {{ 'Add software' | translate }}\n </button>\n </p>\n </c8y-ui-empty-state>\n\n <c8y-list-group *ngIf=\"(baseVersions$ | async)?.data.length > 0\">\n <c8y-li\n *c8yFor=\"let baseVersion of baseVersions$ | async; let i = index; loadMore: 'auto'\"\n >\n <c8y-li-icon>\n <i c8yIcon=\"c8y-tools\"></i>\n </c8y-li-icon>\n\n <c8y-li-body class=\"content-flex-50\">\n <div class=\"col-4\">\n <p\n class=\"text-truncate-wrap\"\n title=\"{{ baseVersion.c8y_Software.version }}\"\n >\n {{ baseVersion.c8y_Software.version }}\n </p>\n </div>\n <div class=\"col-5\">\n <p class=\"text-truncate-wrap\">\n <span\n class=\"text-label-small m-r-8\"\n translate\n >\n File\n </span>\n <ng-container\n *ngIf=\"baseVersion?.c8y_Software?.url === '$PROVIDED'; else fileDownload\"\n >\n <span title=\"{{ 'Provided by device' | translate }}\">\n {{ 'Provided by device' | translate }}\n </span>\n </ng-container>\n\n <ng-template #fileDownload>\n <span title=\"{{ getBinaryName$(baseVersion.c8y_Software.url) | async }}\">\n <c8y-file-download [url]=\"baseVersion.c8y_Software.url\"></c8y-file-download>\n </span>\n </ng-template>\n </p>\n </div>\n <div class=\"col-2 d-flex a-i-start\">\n <span\n *ngIf=\"isLegacy$ | async\"\n class=\"label label-warning m-l-auto-sm\"\n >\n {{ 'Legacy' | translate }}\n </span>\n\n <div\n class=\"fit-h-20\"\n *ngIf=\"!(isLegacy$ | async)\"\n >\n <button\n class=\"btn btn-danger btn-xs visible-xs m-l-auto m-r-8 m-t-8\"\n type=\"button\"\n title=\"{{ 'Delete' | translate }}\"\n (click)=\"deleteBaseVersion(baseVersion)\"\n >\n <i c8yIcon=\"delete\"></i>\n {{ 'Delete' | translate }}\n </button>\n </div>\n </div>\n <div\n *ngIf=\"!(isLegacy$ | async)\"\n class=\"m-l-auto fit-h-20 hidden-xs\"\n >\n <button\n class=\"btn btn-dot text-danger showOnHover\"\n type=\"button\"\n data-cy=\"software-details-component--Delete-button\"\n [attr.aria-label]=\"'Delete' | translate\"\n tooltip=\"{{ 'Delete' | translate }}\"\n [delay]=\"500\"\n (click)=\"deleteBaseVersion(baseVersion)\"\n >\n <i c8yIcon=\"delete\"></i>\n </button>\n </div>\n </c8y-li-body>\n </c8y-li>\n </c8y-list-group>\n </div>\n </div>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: i7.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i7.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i7.NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "component", type: i4.ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "component", type: i4.BreadcrumbComponent, selector: "c8y-breadcrumb" }, { kind: "component", type: i4.BreadcrumbItemComponent, selector: "c8y-breadcrumb-item", inputs: ["icon", "translate", "label", "path", "injector"] }, { kind: "component", type: i4.EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: i4.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i4.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i4.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: i4.TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "directive", type: i5.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: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i5.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "component", type: i4.FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: i4.RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "component", type: i4.ListGroupComponent, selector: "c8y-list-group" }, { kind: "component", type: i4.ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: i4.ListItemIconComponent, selector: "c8y-list-item-icon, c8y-li-icon", inputs: ["icon", "status"] }, { kind: "component", type: i4.ListItemBodyComponent, selector: "c8y-list-item-body, c8y-li-body", inputs: ["body"] }, { kind: "directive", type: i6.PopoverDirective, selector: "[popover]", inputs: ["adaptivePosition", "boundariesElement", "popover", "popoverContext", "popoverTitle", "placement", "outsideClick", "triggers", "container", "containerClass", "isOpen", "delay"], outputs: ["onShown", "onHidden"], exportAs: ["bs-popover"] }, { kind: "directive", type: i10.TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "component", type: i1$1.FileDownloadComponent, selector: "c8y-file-download", inputs: ["url"] }, { kind: "component", type: i1$1.SoftwareTypeComponent, selector: "c8y-software-type", inputs: ["softwareTypeMO", "disabled", "style", "required", "placeholder", "emitResultsOnly", "showBtnInNotFoundMessage", "allowFreeEntries", "showClearSelectionOption", "clearSelectionOptionLabel", "presetSoftwareTypes"], outputs: ["onSelectSoftware"] }, { kind: "pipe", type: i7.AsyncPipe, name: "async" }, { kind: "pipe", type: i4.C8yTranslatePipe, name: "translate" }] }); }
}
__decorate([
memoize(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], SoftwareDetailsComponent.prototype, "getBinaryName$", null);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareDetailsComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-software-details', standalone: false, template: "<c8y-title>\n {{ (software$ | async)?.name }}\n</c8y-title>\n\n<c8y-breadcrumb>\n <c8y-breadcrumb-item\n label=\"{{ 'Management' | translate }}\"\n icon=\"c8y-management\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n path=\"#/software\"\n label=\"{{ 'Software repository' | translate }}\"\n icon=\"c8y-tools\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n label=\"{{ (software$ | async)?.name }}\"\n icon=\"c8y-tools\"\n ></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n type=\"button\"\n title=\"{{ 'Add software' | translate }}\"\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 type=\"button\"\n title=\"{{ 'Reload' | translate }}\"\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 #nameInput\n type=\"text\"\n class=\"form-control\"\n [ngModel]=\"(software$ | async)?.name\"\n #nameModel=\"ngModel\"\n placeholder=\"{{ 'e.g. My software' | translate }}\"\n [ngStyle]=\"{ 'width.ch': (software$ | async)?.name?.length + 2 || 31 }\"\n required\n />\n <span></span>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-primary\"\n type=\"button\"\n title=\"{{ 'Save' | translate }}\"\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 #descriptionInput\n type=\"text\"\n class=\"form-control\"\n [ngModel]=\"(software$ | async)?.description\"\n #descriptionModel=\"ngModel\"\n placeholder=\"{{ 'e.g. Cloud connectivity software' | translate }}\"\n [ngStyle]=\"{ 'width.ch': (software$ | async)?.description?.length + 2 || 31 }\"\n />\n <span></span>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-primary\"\n type=\"button\"\n title=\"{{ 'Save' | translate }}\"\n (click)=\"\n updateSoftware$.next({ description: descriptionInput.value });\n descriptionModel.reset()\n \"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n </div>\n </c8y-form-group>\n </div>\n </div>\n <div class=\"row\">\n <div class=\"col-sm-6\">\n <c8y-form-group>\n <label class=\"control-label\">\n {{ 'Device type' | translate }}\n <button\n class=\"btn-help\"\n type=\"button\"\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 triggers=\"focus\"\n container=\"body\"\n >\n <i c8yIcon=\"question-circle-o\"></i>\n </button>\n </label>\n <div class=\"input-group input-group-editable\">\n <input\n #deviceTypeInput\n type=\"text\"\n class=\"form-control\"\n [ngModel]=\"(software$ | async)?.c8y_Filter?.type\"\n #deviceTypeModel=\"ngModel\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_Linux\"\n [ngStyle]=\"{ 'width.ch': (software$ | async)?.type?.length + 2 || 31 }\"\n />\n <span></span>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-primary\"\n type=\"button\"\n title=\"{{ 'Save' | translate }}\"\n (click)=\"\n updateSoftware$.next({ c8y_Filter: { type: deviceTypeInput.value } });\n deviceTypeModel.reset()\n \"\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 {{ 'Software type' | translate }}\n </label>\n <div class=\"input-group input-group-editable\">\n <c8y-software-type\n [softwareTypeMO]=\"softwareTypeObject\"\n [style]=\"{ 'width.ch': softwareTypeObject?.softwareType?.length + 2 || 31 }\"\n (onSelectSoftware)=\"onSelectSoftwareType($event)\"\n ></c8y-software-type>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-primary\"\n type=\"button\"\n title=\"{{ 'Save' | translate }}\"\n [ngClass]=\"isSoftwareTypeChanged ? '' : 'hidden'\"\n [disabled]=\"softwareTypeObject?.softwareType === ''\"\n (click)=\"\n updateSoftware$.next({ softwareType: softwareTypeObject.softwareType });\n softwareType.resetInput();\n isSoftwareTypeChanged = false\n \"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n </div>\n </c8y-form-group>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"inner-scroll\">\n <div class=\"card-header separator-top-bottom sticky-top bg-component\">\n <div\n class=\"card-title\"\n translate\n >\n Versions\n </div>\n </div>\n\n <div class=\"card-block p-t-0 p-b-24\">\n <c8y-ui-empty-state\n *ngIf=\"(baseVersions$ | async)?.data.length === 0\"\n [icon]=\"'c8y-tools'\"\n [title]=\"'No versions to display.' | translate\"\n [subtitle]=\"'Add a new version by clicking below.' | translate\"\n >\n <p>\n <button\n class=\"btn btn-primary\"\n type=\"button\"\n title=\"{{ 'Add software' | translate }}\"\n (click)=\"addBaseVersion()\"\n >\n {{ 'Add software' | translate }}\n </button>\n </p>\n </c8y-ui-empty-state>\n\n <c8y-list-group *ngIf=\"(baseVersions$ | async)?.data.length > 0\">\n <c8y-li\n *c8yFor=\"let baseVersion of baseVersions$ | async; let i = index; loadMore: 'auto'\"\n >\n <c8y-li-icon>\n <i c8yIcon=\"c8y-tools\"></i>\n </c8y-li-icon>\n\n <c8y-li-body class=\"content-flex-50\">\n <div class=\"col-4\">\n <p\n class=\"text-truncate-wrap\"\n title=\"{{ baseVersion.c8y_Software.version }}\"\n >\n {{ baseVersion.c8y_Software.version }}\n </p>\n </div>\n <div class=\"col-5\">\n <p class=\"text-truncate-wrap\">\n <span\n class=\"text-label-small m-r-8\"\n translate\n >\n File\n </span>\n <ng-container\n *ngIf=\"baseVersion?.c8y_Software?.url === '$PROVIDED'; else fileDownload\"\n >\n <span title=\"{{ 'Provided by device' | translate }}\">\n {{ 'Provided by device' | translate }}\n </span>\n </ng-container>\n\n <ng-template #fileDownload>\n <span title=\"{{ getBinaryName$(baseVersion.c8y_Software.url) | async }}\">\n <c8y-file-download [url]=\"baseVersion.c8y_Software.url\"></c8y-file-download>\n </span>\n </ng-template>\n </p>\n </div>\n <div class=\"col-2 d-flex a-i-start\">\n <span\n *ngIf=\"isLegacy$ | async\"\n class=\"label label-warning m-l-auto-sm\"\n >\n {{ 'Legacy' | translate }}\n </span>\n\n <div\n class=\"fit-h-20\"\n *ngIf=\"!(isLegacy$ | async)\"\n >\n <button\n class=\"btn btn-danger btn-xs visible-xs m-l-auto m-r-8 m-t-8\"\n type=\"button\"\n title=\"{{ 'Delete' | translate }}\"\n (click)=\"deleteBaseVersion(baseVersion)\"\n >\n <i c8yIcon=\"delete\"></i>\n {{ 'Delete' | translate }}\n </button>\n </div>\n </div>\n <div\n *ngIf=\"!(isLegacy$ | async)\"\n class=\"m-l-auto fit-h-20 hidden-xs\"\n >\n <button\n class=\"btn btn-dot text-danger showOnHover\"\n type=\"button\"\n data-cy=\"software-details-component--Delete-button\"\n [attr.aria-label]=\"'Delete' | translate\"\n tooltip=\"{{ 'Delete' | translate }}\"\n [delay]=\"500\"\n (click)=\"deleteBaseVersion(baseVersion)\"\n >\n <i c8yIcon=\"delete\"></i>\n </button>\n </div>\n </c8y-li-body>\n </c8y-li>\n </c8y-list-group>\n </div>\n </div>\n </div>\n </div>\n</div>\n" }]
}], ctorParameters: () => [{ type: i1$2.ActivatedRoute }, { type: i2.InventoryService }, { type: i1$1.RepositoryService }, { type: i4.AlertService }, { type: i4$1.TranslateService }, { type: i4.ModalService }, { type: i1.BsModalService }, { type: i4.GainsightService }, { type: i1$2.Router }], propDecorators: { softwareType: [{
type: ViewChild,
args: [SoftwareTypeComponent, { static: true }]
}], getBinaryName$: [] } });
class SoftwareRepositoryNavigationFactory {
constructor() {
this.node = new NavigatorNode({
label: gettext('Software repository'),
path: 'software',
icon: 'c8y-tools',
parent: gettext('Management'),
priority: 900
});
}
get() {
return this.node;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareRepositoryNavigationFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareRepositoryNavigationFactory }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareRepositoryNavigationFactory, decorators: [{
type: Injectable
}], ctorParameters: () => [] });
class SoftwareRepositoryListModule {
static forRoot() {
return {
ngModule: SoftwareRepositoryListModule,
providers: [
hookNavigator(SoftwareRepositoryNavigationFactory),
hookRoute([
{
path: 'software',
component: SoftwareListComponent
},
{
path: 'software/:id',
component: SoftwareDetailsComponent
}
])
]
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareRepositoryListModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: SoftwareRepositoryListModule, declarations: [SoftwareListComponent, SoftwareDetailsComponent, AddSoftwareModalComponent], imports: [CommonModule,
CoreModule,
FormsModule,
DeviceGridModule,
PopoverModule,
TooltipModule,
RouterModule,
SharedRepositoryModule] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareRepositoryListModule, imports: [CommonModule,
CoreModule,
FormsModule,
DeviceGridModule,
PopoverModule,
TooltipModule,
RouterModule,
SharedRepositoryModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareRepositoryListModule, decorators: [{
type: NgModule,
args: [{
imports: [
CommonModule,
CoreModule,
FormsModule,
DeviceGridModule,
PopoverModule,
TooltipModule,
RouterModule,
SharedRepositoryModule
],
declarations: [SoftwareListComponent, SoftwareDetailsComponent, AddSoftwareModalComponent]
}]
}] });
class DeviceSoftwareService {
/**
* Indicates whether device software data is currently being loaded.
*/
get loading$() {
return this.loadingSubject$.asObservable().pipe(share());
}
constructor(serviceRegistry) {
this.serviceRegistry = serviceRegistry;
this.reloadSubject$ = new BehaviorSubject(null);
this.loadingSubject$ = new Subject();
}
/**
* Trigger device software data reload.
*/
reload() {
this.reloadSubject$.next();
}
/**
* Retrieves the software list that is installed on a given device.
*
* @param deviceId ID of the device to retrieve software data for
* @param filterCriteria Criteria that software items are filtered by.
* @param legacySoftwareList If provided no data will be fetched from backend. The provided software list
* will be filtered by the specified filter criteria.
*
* @returns The software items installed on the specified device filtered by the specified criteria.
*/
getSoftwareList(deviceId, filterCriteria, legacySoftwareList) {
this.loadingSubject$.next(true);
return this.reloadSubject$.pipe(switchMap(() => {
const softwareList$ = !!legacySoftwareList
? this.getLegacySoftwareList(legacySoftwareList, filterCriteria)
: this.getAdvancedSoftwareList(deviceId, filterCriteria);
return softwareList$.then(resultList => {
this.loadingSubject$.next(false);
return resultList;
});
}), share());
}
getAdvancedSoftwareList(deviceId, filterCriteria) {
const queryFilter = {
deviceId,
currentPage: 1,
pageSize: 50,
withTotalPages: true
};
const advancedSoftwareService = head(this.serviceRegistry.get('asm'));
if (filterCriteria?.name) {
set(queryFilter, 'name', `*${filterCriteria.name}*`);
}
if (filterCriteria?.softwareType) {
set(queryFilter, 'type', `${filterCriteria.softwareType}`);
}
return advancedSoftwareService.list(queryFilter);
}
getLegacySoftwareList(legacySoftwareList, filterCriteria) {
const data = filterCriteria
? legacySoftwareList.filter(item => {
let match = true;
if (filterCriteria?.name) {
match = match && item.name?.includes(filterCriteria.name);
}
if (filterCriteria?.softwareType) {
match = match && item.softwareType === filterCriteria.softwareType;
}
return match;
})
: legacySoftwareList;
return Promise.resolve({
data,
res: null,
paging: {
totalPages: data.length
}
});
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DeviceSoftwareService, deps: [{ token: i4.ServiceRegistry }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DeviceSoftwareService, providedIn: 'root' }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DeviceSoftwareService, decorators: [{
type: Injectable,
args: [{
providedIn: 'root'
}]
}], ctorParameters: () => [{ type: i4.ServiceRegistry }] });
class DeviceSoftwareListComponent {
set softwareList(softwareList) {
if (softwareList !== null) {
this.legacySoftwareList$.next(softwareList);
}
}
constructor(deviceSoftwareService) {
this.deviceSoftwareService = deviceSoftwareService;
this.PRODUCT_EXPERIENCE = PRODUCT_EXPERIENCE_REPOSITORY_SHARED;
this.filterCriteria$ = of(null);
this.update = new EventEmitter();
this.remove = new EventEmitter();
this.onListEmpty = new EventEmitter();
this.alreadyInstalledMessage = gettext('This software is already installed on the device');
this.supportsSoftwareOperations = false;
this.operationTypes = ['c8y_SoftwareUpdate', 'c8y_SoftwareList', 'c8y_Software'];
this.legacySoftwareList$ = new BehaviorSubject([]);
this.destroyed$ = new Subject();
}
ngOnInit() {
this.softwareItems$ = combineLatest([
this.filterCriteria$.pipe(debounceTime(300), distinctUntilChanged()),
this.legacySoftwareList$
]).pipe(switchMap(([filterCriteria, legacySoftwareList]) => this.deviceSoftwareService
.getSoftwareList(this.device?.id, filterCriteria, legacySoftwareList)
.pipe(map(resultList => ({ resultList, filterCriteria })))), tap(({ resultList, filterCriteria }) => {
this.notifyListEmpty(!resultList?.paging?.totalPages && !filterCriteria);
this.noSearchResults = !resultList?.paging?.totalPages && !!filterCriteria;
}), map(({ resultList }) => resultList), share(), takeUntil(this.destroyed$));
const supportedOperations = get(this.device, 'c8y_SupportedOperations', []);
this.supportsSoftwareOperations = this.operationTypes.some(operationType => supportedOperations.indexOf(operationType) > -1);
}
ngAfterContentInit() {
this.showUpdate = this.update.observers.length > 0;
this.showRemove = this.remove.observers.length > 0;
}
isSoftwareGoingToBeChanged(software) {
const relevantChanges = filter(this.deviceSoftwareChanges, software);
return relevantChanges.length > 0;
}
ngOnDestroy() {
this.destroyed$.next();
this.destroyed$.complete();
}
notifyListEmpty(isEmpty) {
this.emptyList = isEmpty;
this.onListEmpty.emit(isEmpty);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DeviceSoftwareListComponent, deps: [{ token: DeviceSoftwareService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: DeviceSoftwareListComponent, isStandalone: false, selector: "c8y-device-software-list", inputs: { softwareList: "softwareList", device: "device", deviceSoftwareChanges: "deviceSoftwareChanges", filterCriteria$: "filterCriteria$" }, outputs: { update: "update", remove: "remove", onListEmpty: "onListEmpty" }, ngImport: i0, template: "<c8y-list-group class=\"no-border-2nd-last\">\n <c8y-li\n [ngClass]=\"{ disabled: isSoftwareGoingToBeChanged(software) }\"\n *c8yFor=\"let software of softwareItems$\"\n >\n <!-- SOFTWARE ICON -->\n <c8y-li-icon>\n <i c8yIcon=\"c8y-tools\"></i>\n </c8y-li-icon>\n\n <c8y-li-body class=\"content-flex-20\">\n <div title=\"{{ software.name }}\" class=\"col-9\">\n <p class=\"d-flex\">\n <!-- SOFTWARE NAME -->\n <span class=\"text-truncate\">\n {{ software.name }}\n </span>\n <!-- SOFTWARE TYPE-->\n <span class=\"text-truncate\">\n <span class=\"label label-primary m-l-8\">{{ software.softwareType }}</span>\n </span>\n </p>\n <!-- SOFTWARE VERSION -->\n <p class=\"d-flex a-i-center\">\n <span class=\"text-truncate text-label-small m-r-4\" translate>Version</span>\n <span class=\"text-truncate m-r-4\" title=\"{{ software.version }}\">\n {{ software.version }}\n </span>\n <i\n *ngIf=\"software.installed\"\n c8yIcon=\"warning\"\n class=\"text-warning a-s-center\"\n [tooltip]=\"\n alreadyInstalledMessage\n | translate: { name: software.name, version: software.version }\n \"\n container=\"body\"\n placement=\"top\"\n [delay]=\"500\"\n ></i>\n </p>\n </div>\n\n <div\n *ngIf=\"supportsSoftwareOperations && (showUpdate || showRemove)\"\n class=\"col-3 text-right m-0 flex-grow d-flex a-i-center\"\n >\n <!-- UPDATE SOFTWARE -->\n <button\n class=\"btn btn-default btn-xs m-l-auto m-r-4\"\n type=\"button\"\n title=\"{{ 'Update`software,verb`' | translate }}\"\n *ngIf=\"showUpdate && !isSoftwareGoingToBeChanged(software)\"\n (click)=\"update.emit(software)\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.SOFTWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.SOFTWARE.COMPONENTS.DEVICE_SOFTWARE_LIST,\n action: PRODUCT_EXPERIENCE.SOFTWARE.ACTIONS.OPEN_UPDATE_SOFTWARE\n }\"\n >\n {{ 'Update`software,verb`' | translate }}\n </button>\n\n <!-- REMOVE SOFTWARE -->\n <button\n class=\"btn btn-dot btn-dot--danger m-l-4\"\n type=\"button\"\n [attr.aria-label]=\"'Remove`software,verb`' | translate\"\n tooltip=\"{{ 'Remove`software,verb`' | translate }}\"\n placement=\"bottom\"\n container=\"body\"\n [delay]=\"500\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.SOFTWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.SOFTWARE.COMPONENTS.DEVICE_SOFTWARE_LIST,\n action: PRODUCT_EXPERIENCE.SOFTWARE.ACTIONS.DELETE_SOFTWARE\n }\"\n *ngIf=\"showRemove && !isSoftwareGoingToBeChanged(software)\"\n (click)=\"remove.emit(software)\"\n >\n <i c8yIcon=\"minus-circle\"></i>\n </button>\n </div>\n </c8y-li-body>\n </c8y-li>\n</c8y-list-group>\n<!-- NO SEARCH RESULTS STATE -->\n<div class=\"card-block\" *ngIf=\"noSearchResults || emptyList\">\n <ng-content *ngIf=\"emptyList\" select=\".c8y-empty-state:not(.c8y-no-results-state)\"></ng-content>\n <ng-content *ngIf=\"noSearchResults\" select=\".c8y-no-results-state\"></ng-content>\n</div>\n", dependencies: [{ kind: "directive", type: i7.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i7.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i4.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i4.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: i4.ListGroupComponent, selector: "c8y-list-group" }, { kind: "component", type: i4.ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: i4.ListItemIconComponent, selector: "c8y-list-item-icon, c8y-li-icon", inputs: ["icon", "status"] }, { kind: "component", type: i4.ListItemBodyComponent, selector: "c8y-list-item-body, c8y-li-body", inputs: ["body"] }, { kind: "directive", type: i4.ProductExperienceDirective, selector: "[c8yProductExperience]", inputs: ["actionName", "actionData", "inherit", "suppressDataOverriding"] }, { kind: "directive", type: i10.TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "pipe", type: i4.C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DeviceSoftwareListComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-device-software-list', standalone: false, template: "<c8y-list-group class=\"no-border-2nd-last\">\n <c8y-li\n [ngClass]=\"{ disabled: isSoftwareGoingToBeChanged(software) }\"\n *c8yFor=\"let software of softwareItems$\"\n >\n <!-- SOFTWARE ICON -->\n <c8y-li-icon>\n <i c8yIcon=\"c8y-tools\"></i>\n </c8y-li-icon>\n\n <c8y-li-body class=\"content-flex-20\">\n <div title=\"{{ software.name }}\" class=\"col-9\">\n <p class=\"d-flex\">\n <!-- SOFTWARE NAME -->\n <span class=\"text-truncate\">\n {{ software.name }}\n </span>\n <!-- SOFTWARE TYPE-->\n <span class=\"text-truncate\">\n <span class=\"label label-primary m-l-8\">{{ software.softwareType }}</span>\n </span>\n </p>\n <!-- SOFTWARE VERSION -->\n <p class=\"d-flex a-i-center\">\n <span class=\"text-truncate text-label-small m-r-4\" translate>Version</span>\n <span class=\"text-truncate m-r-4\" title=\"{{ software.version }}\">\n {{ software.version }}\n </span>\n <i\n *ngIf=\"software.installed\"\n c8yIcon=\"warning\"\n class=\"text-warning a-s-center\"\n [tooltip]=\"\n alreadyInstalledMessage\n | translate: { name: software.name, version: software.version }\n \"\n container=\"body\"\n placement=\"top\"\n [delay]=\"500\"\n ></i>\n </p>\n </div>\n\n <div\n *ngIf=\"supportsSoftwareOperations && (showUpdate || showRemove)\"\n class=\"col-3 text-right m-0 flex-grow d-flex a-i-center\"\n >\n <!-- UPDATE SOFTWARE -->\n <button\n class=\"btn btn-default btn-xs m-l-auto m-r-4\"\n type=\"button\"\n title=\"{{ 'Update`software,verb`' | translate }}\"\n *ngIf=\"showUpdate && !isSoftwareGoingToBeChanged(software)\"\n (click)=\"update.emit(software)\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.SOFTWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.SOFTWARE.COMPONENTS.DEVICE_SOFTWARE_LIST,\n action: PRODUCT_EXPERIENCE.SOFTWARE.ACTIONS.OPEN_UPDATE_SOFTWARE\n }\"\n >\n {{ 'Update`software,verb`' | translate }}\n </button>\n\n <!-- REMOVE SOFTWARE -->\n <button\n class=\"btn btn-dot btn-dot--danger m-l-4\"\n type=\"button\"\n [attr.aria-label]=\"'Remove`software,verb`' | translate\"\n tooltip=\"{{ 'Remove`software,verb`' | translate }}\"\n placement=\"bottom\"\n container=\"body\"\n [delay]=\"500\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.SOFTWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.SOFTWARE.COMPONENTS.DEVICE_SOFTWARE_LIST,\n action: PRODUCT_EXPERIENCE.SOFTWARE.ACTIONS.DELETE_SOFTWARE\n }\"\n *ngIf=\"showRemove && !isSoftwareGoingToBeChanged(software)\"\n (click)=\"remove.emit(software)\"\n >\n <i c8yIcon=\"minus-circle\"></i>\n </button>\n </div>\n </c8y-li-body>\n </c8y-li>\n</c8y-list-group>\n<!-- NO SEARCH RESULTS STATE -->\n<div class=\"card-block\" *ngIf=\"noSearchResults || emptyList\">\n <ng-content *ngIf=\"emptyList\" select=\".c8y-empty-state:not(.c8y-no-results-state)\"></ng-content>\n <ng-content *ngIf=\"noSearchResults\" select=\".c8y-no-results-state\"></ng-content>\n</div>\n" }]
}], ctorParameters: () => [{ type: DeviceSoftwareService }], propDecorators: { softwareList: [{
type: Input
}], device: [{
type: Input
}], deviceSoftwareChanges: [{
type: Input
}], filterCriteria$: [{
type: Input
}], update: [{
type: Output
}], remove: [{
type: Output
}], onListEmpty: [{
type: Output
}] } });
class DeviceSoftwareChangesComponent {
constructor() {
this.PRODUCT_EXPERIENCE = PRODUCT_EXPERIENCE_REPOSITORY_SHARED;
this.clear = new EventEmitter();
this.drop = new EventEmitter();
this.apply = new EventEmitter();
this.hideSoftwareChanges = new EventEmitter();
this.changes$ = new BehaviorSubject([]);
this.installations$ = this.changes$.pipe(map(changes => {
return changes.filter(change => change.action === 'install');
}));
this.removals$ = this.changes$.pipe(map(changes => {
return changes.filter(change => change.action === 'delete');
}));
}
get applyButtonLabel() {
return this.changesInProgress ? gettext('Applying changes…') : gettext('Apply changes');
}
ngOnChanges(changes) {
if (changes.changes) {
this.changes$.next(changes.changes.currentValue);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DeviceSoftwareChangesComponent, deps: [], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: DeviceSoftwareChangesComponent, isStandalone: false, selector: "c8y-device-software-changes", inputs: { changes: "changes", changesInProgress: "changesInProgress", deviceSoftwareChangesOperation: "deviceSoftwareChangesOperation" }, outputs: { clear: "clear", drop: "drop", apply: "apply", hideSoftwareChanges: "hideSoftwareChanges" }, usesOnChanges: true, ngImport: i0, template: "<div class=\"inner-scroll d-flex d-col\">\n <div class=\"card-header separator sticky-top visible-sm visible-xs\">\n <button\n class=\"btn btn-dot text-primary\"\n [title]=\"'Back' | translate\"\n type=\"button\"\n (click)=\"hideSoftwareChanges.emit()\"\n >\n <i c8yIcon=\"chevron-left\"></i>\n <span translate>Back</span>\n </button>\n </div>\n <div class=\"card-header separator large-padding sticky-top\">\n <div\n class=\"card-title\"\n translate\n >\n Software changes\n </div>\n </div>\n <div class=\"flex-grow\">\n <fieldset\n class=\"card-block large-padding bg-level-2 p-0\"\n id=\"operation-block\"\n *ngIf=\"deviceSoftwareChangesOperation\"\n >\n <c8y-operation-details [operation]=\"deviceSoftwareChangesOperation\"></c8y-operation-details>\n </fieldset>\n </div>\n <div\n class=\"flex-grow inner-scroll\"\n id=\"software-changes-list\"\n >\n <div\n class=\"card-block large-padding\"\n *ngIf=\"(changes$ | async).length === 0; else changesList\"\n >\n <div class=\"c8y-empty-state\">\n <div class=\"h1 c8y-icon c8y-icon-device-control c8y-icon-duocolor\"></div>\n <p>\n <strong translate>No software changes.</strong>\n <br />\n <small translate>Selected actions will be displayed here.</small>\n </p>\n </div>\n </div>\n\n <ng-template #changesList>\n <fieldset\n class=\"card-block p-t-0 p-b-0\"\n *ngIf=\"(installations$ | async).length > 0\"\n [disabled]=\"changesInProgress\"\n >\n <div\n class=\"form-block legend\"\n translate\n >\n Installations`software`\n </div>\n <c8y-device-software-list\n [softwareList]=\"installations$ | async\"\n (remove)=\"drop.emit($event)\"\n ></c8y-device-software-list>\n </fieldset>\n\n <fieldset\n class=\"card-block p-t-0 p-b-0\"\n *ngIf=\"(removals$ | async).length > 0\"\n [disabled]=\"changesInProgress\"\n >\n <div\n class=\"form-block legend\"\n translate\n >\n Removals`software`\n </div>\n <c8y-device-software-list\n [softwareList]=\"removals$ | async\"\n (remove)=\"drop.emit($event)\"\n ></c8y-device-software-list>\n </fieldset>\n </ng-template>\n </div>\n <div\n class=\"card-footer separator large-padding sticky-bottom\"\n *ngIf=\"(changes$ | async).length > 0\"\n >\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Clear changes' | translate }}\"\n type=\"button\"\n (click)=\"clear.emit()\"\n [disabled]=\"changesInProgress\"\n translate\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.SOFTWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.SOFTWARE.COMPONENTS.DEVICE_SOFTWARE_CHANGES,\n action: PRODUCT_EXPERIENCE.SOFTWARE.ACTIONS.CLEAR_SOFTWARE_CHANGES\n }\"\n >\n Clear`changes`\n </button>\n <button\n class=\"btn btn-primary\"\n title=\"{{ applyButtonLabel | translate }}\"\n type=\"button\"\n [ngClass]=\"{ 'btn-pending': changesInProgress }\"\n [disabled]=\"changesInProgress\"\n (click)=\"apply.emit(); hideSoftwareChanges.emit()\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.SOFTWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.SOFTWARE.COMPONENTS.DEVICE_SOFTWARE_CHANGES,\n action: PRODUCT_EXPERIENCE.SOFTWARE.ACTIONS.APPLY_SOFTWARE_CHANGES\n }\"\n >\n {{ applyButtonLabel | translate }}\n </button>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: i7.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i7.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i4.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i4.ProductExperienceDirective, selector: "[c8yProductExperience]", inputs: ["actionName", "actionData", "inherit", "suppressDataOverriding"] }, { kind: "component", type: i3.OperationDetailsComponent, selector: "c8y-operation-details", inputs: ["operation"] }, { kind: "component", type: DeviceSoftwareListComponent, selector: "c8y-device-software-list", inputs: ["softwareList", "device", "deviceSoftwareChanges", "filterCriteria$"], outputs: ["update", "remove", "onListEmpty"] }, { kind: "pipe", type: i7.AsyncPipe, name: "async" }, { kind: "pipe", type: i4.C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: DeviceSoftwareChangesComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-device-software-changes', standalone: false, template: "<div class=\"inner-scroll d-flex d-col\">\n <div class=\"card-header separator sticky-top visible-sm visible-xs\">\n <button\n class=\"btn btn-dot text-primary\"\n [title]=\"'Back' | translate\"\n type=\"button\"\n (click)=\"hideSoftwareChanges.emit()\"\n >\n <i c8yIcon=\"chevron-left\"></i>\n <span translate>Back</span>\n </button>\n </div>\n <div class=\"card-header separator large-padding sticky-top\">\n <div\n class=\"card-title\"\n translate\n >\n Software changes\n </div>\n </div>\n <div class=\"flex-grow\">\n <fieldset\n class=\"card-block large-padding bg-level-2 p-0\"\n id=\"operation-block\"\n *ngIf=\"deviceSoftwareChangesOperation\"\n >\n <c8y-operation-details [operation]=\"deviceSoftwareChangesOperation\"></c8y-operation-details>\n </fieldset>\n </div>\n <div\n class=\"flex-grow inner-scroll\"\n id=\"software-changes-list\"\n >\n <div\n class=\"card-block large-padding\"\n *ngIf=\"(changes$ | async).length === 0; else changesList\"\n >\n <div class=\"c8y-empty-state\">\n <div class=\"h1 c8y-icon c8y-icon-device-control c8y-icon-duocolor\"></div>\n <p>\n <strong translate>No software changes.</strong>\n <br />\n <small translate>Selected actions will be displayed here.</small>\n </p>\n </div>\n </div>\n\n <ng-template #changesList>\n <fieldset\n class=\"card-block p-t-0 p-b-0\"\n *ngIf=\"(installations$ | async).length > 0\"\n [disabled]=\"changesInProgress\"\n >\n <div\n class=\"form-block legend\"\n translate\n >\n Installations`software`\n </div>\n <c8y-device-software-list\n [softwareList]=\"installations$ | async\"\n (remove)=\"drop.emit($event)\"\n ></c8y-device-software-list>\n </fieldset>\n\n <fieldset\n class=\"card-block p-t-0 p-b-0\"\n *ngIf=\"(removals$ | async).length > 0\"\n [disabled]=\"changesInProgress\"\n >\n <div\n class=\"form-block legend\"\n translate\n >\n Removals`software`\n </div>\n <c8y-device-software-list\n [softwareList]=\"removals$ | async\"\n (remove)=\"drop.emit($event)\"\n ></c8y-device-software-list>\n </fieldset>\n </ng-template>\n </div>\n <div\n class=\"card-footer separator large-padding sticky-bottom\"\n *ngIf=\"(changes$ | async).length > 0\"\n >\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Clear changes' | translate }}\"\n type=\"button\"\n (click)=\"clear.emit()\"\n [disabled]=\"changesInProgress\"\n translate\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.SOFTWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.SOFTWARE.COMPONENTS.DEVICE_SOFTWARE_CHANGES,\n action: PRODUCT_EXPERIENCE.SOFTWARE.ACTIONS.CLEAR_SOFTWARE_CHANGES\n }\"\n >\n Clear`changes`\n </button>\n <button\n class=\"btn btn-primary\"\n title=\"{{ applyButtonLabel | translate }}\"\n type=\"button\"\n [ngClass]=\"{ 'btn-pending': changesInProgress }\"\n [disabled]=\"changesInProgress\"\n (click)=\"apply.emit(); hideSoftwareChanges.emit()\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.SOFTWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.SOFTWARE.COMPONENTS.DEVICE_SOFTWARE_CHANGES,\n action: PRODUCT_EXPERIENCE.SOFTWARE.ACTIONS.APPLY_SOFTWARE_CHANGES\n }\"\n >\n {{ applyButtonLabel | translate }}\n </button>\n </div>\n</div>\n" }]
}], propDecorators: { changes: [{
type: Input
}], changesInProgress: [{
type: Input
}], deviceSoftwareChangesOperation: [{
type: Input
}], clear: [{
type: Output
}], drop: [{
type: Output
}], apply: [{
type: Output
}], hideSoftwareChanges: [{
type: Output
}] } });
class InstalledSoftwareComponent {
constructor(repository, bsModal) {
this.repository = repository;
this.bsModal = bsModal;
this.PRODUCT_EXPERIENCE = PRODUCT_EXPERIENCE_REPOSITORY_SHARED;
this.changes = new EventEmitter();
this.showSoftwareChanges = new EventEmitter();
this.alreadyInstalledMessage = gettext('{{ name }} (v. {{ version }}) is already installed on this device');
this.showFilter = false;
this.supportsSoftwareOperations = false;
this.textFilter$ = new BehaviorSubject('');
this.softwareTypeFilter$ = new BehaviorSubject('');
this.operationTypes = ['c8y_SoftwareUpdate', 'c8y_SoftwareList', 'c8y_Software'];
this.destroyed$ = new Subject();
this.filterCriteria$ = combineLatest([this.textFilter$, this.softwareTypeFilter$]).pipe(map(([textFilter, softwareTypeFilter]) => ({
name: textFilter,
softwareType: softwareTypeFilter
})), map(filterCriteria => !filterCriteria.name && !filterCriteria.softwareType ? null : filterCriteria));
this.queriesUtil = new QueriesUtil();
}
ngOnInit() {
const supportedOperations = get(this.device, 'c8y_SupportedOperations', []);
this.supportsSoftwareOperations = this.operationTypes.some(operationType => supportedOperations.indexOf(operationType) > -1);
}
installSoftware() {
const { resultEmitter, choiceEmitter, updateInstallableList$ } = this.displaySoftwareSelectModal({
title: gettext('Install software'),
subTitle: gettext('Available softwares matching the device type'),
labels: { ok: gettext('Install') },
noItemsMessage: gettext('No matching software available.'),
showAdditionalFilter: true,
additionalFilterTemplate: this.softwareTypeTemplate,
repositoryEntriesWithVersions$: of([]),
repositoryEntriesWithVersionsFn$: modal => this.getInstallableSoftwareListWithVersions$(modal.content.searchTerm)
});
resultEmitter.pipe(takeUntil(this.destroyed$)).subscribe(softwareToInstall => {
this.emitSoftwareInstall(softwareToInstall);
this.showSoftwareChanges.emit();
});
choiceEmitter
.pipe(this.isSoftwareInstalledOnDevicePipe(updateInstallableList$), takeUntil(this.destroyed$))
.subscribe(({ item }) => updateInstallableList$.next({
object: item,
template: this.alreadyInstalledWarningTemplate,
mapper: object => {
object.installed = true;
return object;
}
}));
}
updateSoftware(softwareToUpdate) {
const { resultEmitter, choiceEmitter, updateInstallableList$ } = this.displaySoftwareSelectModal({
title: gettext('Update software'),
subTitle: gettext('Select one of the available software versions'),
labels: { ok: gettext('Update') },
noItemsMessage: gettext('No other software versions available.'),
showFilter: false,
repositoryEntriesWithVersions$: this.getSingleSoftwareWithVersions$(softwareToUpdate)
});
resultEmitter.pipe(takeUntil(this.destroyed$)).subscribe(softwareToInstall => {
this.emitSoftwareInstall(softwareToInstall);
this.showSoftwareChanges.emit();
});
choiceEmitter
.pipe(this.isSoftwareInstalledOnDevicePipe(updateInstallableList$), takeUntil(this.destroyed$))
.subscribe(({ item }) => updateInstallableList$.next({
object: item,
template: this.alreadyInstalledWarningTemplate,
mapper: object => {
object.installed = true;
return object;
}
}));
}
removeSoftware(softwareToRemove) {
this.emitSoftwareRemoval([softwareToRemove]);
}
getInstallableSoftwareListWithVersions$(searchTerm$) {
const installedSoftwareNames = (this.softwareList || []).map(s => s.name);
return searchTerm$.pipe(distinctUntilChanged(), switchMap(searchTerm => this.repository.listRepositoryEntries(RepositoryType.SOFTWARE, {
query: searchTerm.softwareType
? this.queriesUtil.addAndFilter(this.typesQuery, {
softwareType: searchTerm.softwareType
})
: this.typesQuery,
partialName: searchTerm?.name,
params: { pageSize: 100 }
})), map(({ data }) => data), map(softwareList => {
return softwareList.filter(software => {
return !installedSoftwareNames.includes(software.name);
});
}), map(softwareList => this.attachVersions(softwareList)), shareReplay(1));
}
getSingleSoftwareWithVersions$(software) {
return from(this.repository.listRepositoryEntries(RepositoryType.SOFTWARE, {
query: { name: software.name }
})).pipe(map(({ data }) => data), map(softwareList => this.attachVersions(softwareList)), shareReplay(1));
}
attachVersions(softwareList) {
softwareList.forEach(software => {
software.versions = this.repository.listBaseVersions(software);
});
return softwareList;
}
displaySoftwareSelectModal(initialStateOverrides) {
const initialState = {
repositoryType: RepositoryType.SOFTWARE,
mode: ModalSelectionMode.MULTI,
icon: 'c8y-tools',
disableSelected: false,
selected: this.softwareList,
hideEmptyItems: true,
...initialStateOverrides
};
const modal = this.bsModal.show(RepositorySelectModalComponent, {
ignoreBackdropClick: true,
class: 'modal-sm',
ariaDescribedby: 'modal-body',
ariaLabelledBy: 'modal-title',
initialState
});
if (initialStateOverrides.repositoryEntriesWithVersionsFn$) {
modal.content.repositoryEntriesWithVersions$ =
initialStateOverrides.repositoryEntriesWithVersionsFn$(modal);
}
this.modalSearch = modal.content.search.bind(modal.content);
modal.content.load.next();
return {
resultEmitter: modal.content.resultEmitter,
choiceEmitter: modal.content.onChoiceUpdated,
updateInstallableList$: modal.content.updateInstallableList$
};
}
search(filterCriteria) {
if (this.modalSearch) {
this.modalSearch(filterCriteria);
}
}
emitSoftwareInstall(items) {
this.changes.emit(items.map(item => {
return { ...item, action: 'install' };
}));
}
emitSoftwareRemoval(items) {
this.changes.emit(items.map(item => {
return { ...item, action: 'delete' };
}));
}
ngOnDestroy() {
this.destroyed$.next();
this.destroyed$.complete();
}
isSoftwareInstalledOnDevicePipe(updateInstallableList$) {
return pipe(tap((item) => updateInstallableList$.next({ object: item, template: this.loadingTemplate })), map(item => ({
item,
software: ((item.options || []).find(option => option.obj.id === item.selectedId) || {}).obj
})), mergeMap(({ item, software }) => from(this.repository.isSoftwareInstalledOnDevice(this.device.id, software)).pipe(map(installed => ({ item, installed })))), tap(({ item }) => updateInstallableList$.next({ object: item })), filter$1(({ installed }) => !!installed));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: InstalledSoftwareComponent, deps: [{ token: i1$1.RepositoryService }, { token: i1.BsModalService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: InstalledSoftwareComponent, isStandalone: false, selector: "c8y-installed-software", inputs: { device: "device", softwareList: "softwareList", deviceSoftwareChanges: "deviceSoftwareChanges", deviceSoftwareChangesInProgress: "deviceSoftwareChangesInProgress", typesQuery: "typesQuery" }, outputs: { changes: "changes", showSoftwareChanges: "showSoftwareChanges" }, viewQueries: [{ propertyName: "alreadyInstalledWarningTemplate", first: true, predicate: ["alreadyInstalledWarning"], descendants: true, static: true }, { propertyName: "loadingTemplate", first: true, predicate: ["loading"], descendants: true, static: true }, { propertyName: "softwareTypeTemplate", first: true, predicate: ["softwareType"], descendants: true, static: true }], ngImport: i0, template: "<div class=\"d-flex d-col flex-grow\">\n <div class=\"card-header large-padding separator sticky-top\">\n <div\n class=\"card-title\"\n translate\n >\n Installed software\n </div>\n </div>\n <div class=\"flex-grow\">\n <fieldset\n class=\"card-block large-padding overflow-visible separator-bottom\"\n *ngIf=\"showFilter\"\n >\n <div class=\"row\">\n <div class=\"col-xs-6\">\n <div class=\"input-group input-group-search\">\n <label\n class=\"sr-only\"\n for=\"filter\"\n >\n {{ 'Filter\u2026' | translate }}\n </label>\n <input\n class=\"form-control\"\n title=\"{{ 'Filter\u2026' | translate }}\"\n id=\"filter\"\n placeholder=\"{{ 'Filter\u2026' | translate }}\"\n type=\"search\"\n [ngModel]=\"textFilter$ | async\"\n (ngModelChange)=\"textFilter$.next($event)\"\n />\n <span class=\"input-group-addon\">\n <i\n c8yIcon=\"search\"\n *ngIf=\"(textFilter$ | async).length === 0\"\n ></i>\n <i\n class=\"text-muted\"\n c8yIcon=\"times\"\n *ngIf=\"(textFilter$ | async).length > 0\"\n (click)=\"textFilter$.next('')\"\n ></i>\n </span>\n <span\n class=\"sr-only\"\n for=\"search\"\n translate\n >\n Filter\u2026\n </span>\n </div>\n </div>\n <div class=\"col-xs-6\">\n <c8y-software-type\n [required]=\"false\"\n [emitResultsOnly]=\"true\"\n [showBtnInNotFoundMessage]=\"false\"\n [allowFreeEntries]=\"false\"\n [placeholder]=\"'Filter by software type\u2026' | translate\"\n [showClearSelectionOption]=\"true\"\n [presetSoftwareTypes]=\"device.c8y_SupportedSoftwareTypes\"\n (onSelectSoftware)=\"softwareTypeFilter$.next($event?.softwareType)\"\n ></c8y-software-type>\n </div>\n </div>\n </fieldset>\n\n <fieldset\n class=\"flex-grow inner-scroll\"\n id=\"software-list\"\n [disabled]=\"deviceSoftwareChangesInProgress\"\n >\n <!-- NOT EMPTY STATE -->\n <c8y-device-software-list\n class=\"d-block p-l-16 p-r-16\"\n container=\"body\"\n [device]=\"device\"\n [filterCriteria$]=\"filterCriteria$\"\n [softwareList]=\"softwareList\"\n [deviceSoftwareChanges]=\"deviceSoftwareChanges\"\n (update)=\"updateSoftware($event)\"\n (remove)=\"removeSoftware($event)\"\n (onListEmpty)=\"showFilter = !$event\"\n >\n <!-- EMPTY STATE -->\n <div class=\"c8y-empty-state text-center\">\n <div class=\"h1 c8y-icon c8y-icon-tools c8y-icon-duocolor\"></div>\n <p>\n <strong translate>No software installed.</strong>\n <br />\n <small translate>Click below to install software into this device.</small>\n </p>\n </div>\n <!-- NO SEARCH RESULTS STATE -->\n <div class=\"c8y-empty-state c8y-no-results-state text-center\">\n <div class=\"h1 c8y-icon c8y-icon-tools c8y-icon-duocolor\"></div>\n <p>\n <strong translate>No software matches your filter criteria.</strong>\n <br />\n <small translate>Try changing your search criteria.</small>\n </p>\n </div>\n </c8y-device-software-list>\n </fieldset>\n </div>\n <!-- INSTALL SOFTWARE-->\n <div\n class=\"card-footer large-padding separator sticky-bottom d-flex j-c-between bg-level-0\"\n [ngClass]=\"{ 'visible-sm visible-xs': !supportsSoftwareOperations }\"\n >\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Install software' | translate }}\"\n *ngIf=\"supportsSoftwareOperations\"\n (click)=\"installSoftware()\"\n [disabled]=\"deviceSoftwareChangesInProgress\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.SOFTWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.SOFTWARE.COMPONENTS.DEVICE_SOFTWARE_LIST,\n action: PRODUCT_EXPERIENCE.SOFTWARE.ACTIONS.OPEN_INSTALL_SOFTWARE\n }\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Install software' | translate }}\n </button>\n <button\n class=\"btn btn-clean text-primary visible-sm visible-xs\"\n [title]=\"'Show "Software changes"' | translate\"\n (click)=\"showSoftwareChanges.emit()\"\n >\n <span translate>Show \"Software changes\"</span>\n <i c8yIcon=\"chevron-right\"></i>\n </button>\n </div>\n</div>\n\n<ng-template\n #alreadyInstalledWarning\n let-item\n let-option=\"option\"\n>\n <i\n class=\"text-warning a-s-center\"\n c8yIcon=\"warning\"\n [tooltip]=\"\n alreadyInstalledMessage\n | translate: { name: item.body[0].value, version: option.body[0].value }\n \"\n ></i>\n</ng-template>\n\n<ng-template #loading>\n <div class=\"p-relative d-flex m-l-auto\">\n <i\n class=\"icon-spin\"\n c8yIcon=\"circle-o-notch\"\n ></i>\n </div>\n</ng-template>\n\n<ng-template #softwareType>\n <c8y-software-type\n additionalFilter\n [required]=\"false\"\n [placeholder]=\"'Filter by software type\u2026' | translate\"\n (onSelectSoftware)=\"search({ softwareType: $event?.softwareType })\"\n [emitResultsOnly]=\"true\"\n [showBtnInNotFoundMessage]=\"false\"\n [allowFreeEntries]=\"false\"\n [showClearSelectionOption]=\"true\"\n [presetSoftwareTypes]=\"device.c8y_SupportedSoftwareTypes\"\n ></c8y-software-type>\n</ng-template>\n", dependencies: [{ kind: "directive", type: i7.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i7.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i4.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i4.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i5.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: i5.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i5.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: i4.ProductExperienceDirective, selector: "[c8yProductExperience]", inputs: ["actionName", "actionData", "inherit", "suppressDataOverriding"] }, { kind: "component", type: i1$1.SoftwareTypeComponent, selector: "c8y-software-type", inputs: ["softwareTypeMO", "disabled", "style", "required", "placeholder", "emitResultsOnly", "showBtnInNotFoundMessage", "allowFreeEntries", "showClearSelectionOption", "clearSelectionOptionLabel", "presetSoftwareTypes"], outputs: ["onSelectSoftware"] }, { kind: "directive", type: i10.TooltipDirective, selector: "[tooltip], [tooltipHtml]", inputs: ["adaptivePosition", "tooltip", "placement", "triggers", "container", "containerClass", "boundariesElement", "isOpen", "isDisabled", "delay", "tooltipHtml", "tooltipPlacement", "tooltipIsOpen", "tooltipEnable", "tooltipAppendToBody", "tooltipAnimation", "tooltipClass", "tooltipContext", "tooltipPopupDelay", "tooltipFadeDuration", "tooltipTrigger"], outputs: ["tooltipChange", "onShown", "onHidden", "tooltipStateChanged"], exportAs: ["bs-tooltip"] }, { kind: "component", type: DeviceSoftwareListComponent, selector: "c8y-device-software-list", inputs: ["softwareList", "device", "deviceSoftwareChanges", "filterCriteria$"], outputs: ["update", "remove", "onListEmpty"] }, { kind: "pipe", type: i7.AsyncPipe, name: "async" }, { kind: "pipe", type: i4.C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: InstalledSoftwareComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-installed-software', standalone: false, template: "<div class=\"d-flex d-col flex-grow\">\n <div class=\"card-header large-padding separator sticky-top\">\n <div\n class=\"card-title\"\n translate\n >\n Installed software\n </div>\n </div>\n <div class=\"flex-grow\">\n <fieldset\n class=\"card-block large-padding overflow-visible separator-bottom\"\n *ngIf=\"showFilter\"\n >\n <div class=\"row\">\n <div class=\"col-xs-6\">\n <div class=\"input-group input-group-search\">\n <label\n class=\"sr-only\"\n for=\"filter\"\n >\n {{ 'Filter\u2026' | translate }}\n </label>\n <input\n class=\"form-control\"\n title=\"{{ 'Filter\u2026' | translate }}\"\n id=\"filter\"\n placeholder=\"{{ 'Filter\u2026' | translate }}\"\n type=\"search\"\n [ngModel]=\"textFilter$ | async\"\n (ngModelChange)=\"textFilter$.next($event)\"\n />\n <span class=\"input-group-addon\">\n <i\n c8yIcon=\"search\"\n *ngIf=\"(textFilter$ | async).length === 0\"\n ></i>\n <i\n class=\"text-muted\"\n c8yIcon=\"times\"\n *ngIf=\"(textFilter$ | async).length > 0\"\n (click)=\"textFilter$.next('')\"\n ></i>\n </span>\n <span\n class=\"sr-only\"\n for=\"search\"\n translate\n >\n Filter\u2026\n </span>\n </div>\n </div>\n <div class=\"col-xs-6\">\n <c8y-software-type\n [required]=\"false\"\n [emitResultsOnly]=\"true\"\n [showBtnInNotFoundMessage]=\"false\"\n [allowFreeEntries]=\"false\"\n [placeholder]=\"'Filter by software type\u2026' | translate\"\n [showClearSelectionOption]=\"true\"\n [presetSoftwareTypes]=\"device.c8y_SupportedSoftwareTypes\"\n (onSelectSoftware)=\"softwareTypeFilter$.next($event?.softwareType)\"\n ></c8y-software-type>\n </div>\n </div>\n </fieldset>\n\n <fieldset\n class=\"flex-grow inner-scroll\"\n id=\"software-list\"\n [disabled]=\"deviceSoftwareChangesInProgress\"\n >\n <!-- NOT EMPTY STATE -->\n <c8y-device-software-list\n class=\"d-block p-l-16 p-r-16\"\n container=\"body\"\n [device]=\"device\"\n [filterCriteria$]=\"filterCriteria$\"\n [softwareList]=\"softwareList\"\n [deviceSoftwareChanges]=\"deviceSoftwareChanges\"\n (update)=\"updateSoftware($event)\"\n (remove)=\"removeSoftware($event)\"\n (onListEmpty)=\"showFilter = !$event\"\n >\n <!-- EMPTY STATE -->\n <div class=\"c8y-empty-state text-center\">\n <div class=\"h1 c8y-icon c8y-icon-tools c8y-icon-duocolor\"></div>\n <p>\n <strong translate>No software installed.</strong>\n <br />\n <small translate>Click below to install software into this device.</small>\n </p>\n </div>\n <!-- NO SEARCH RESULTS STATE -->\n <div class=\"c8y-empty-state c8y-no-results-state text-center\">\n <div class=\"h1 c8y-icon c8y-icon-tools c8y-icon-duocolor\"></div>\n <p>\n <strong translate>No software matches your filter criteria.</strong>\n <br />\n <small translate>Try changing your search criteria.</small>\n </p>\n </div>\n </c8y-device-software-list>\n </fieldset>\n </div>\n <!-- INSTALL SOFTWARE-->\n <div\n class=\"card-footer large-padding separator sticky-bottom d-flex j-c-between bg-level-0\"\n [ngClass]=\"{ 'visible-sm visible-xs': !supportsSoftwareOperations }\"\n >\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Install software' | translate }}\"\n *ngIf=\"supportsSoftwareOperations\"\n (click)=\"installSoftware()\"\n [disabled]=\"deviceSoftwareChangesInProgress\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.SOFTWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.SOFTWARE.COMPONENTS.DEVICE_SOFTWARE_LIST,\n action: PRODUCT_EXPERIENCE.SOFTWARE.ACTIONS.OPEN_INSTALL_SOFTWARE\n }\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Install software' | translate }}\n </button>\n <button\n class=\"btn btn-clean text-primary visible-sm visible-xs\"\n [title]=\"'Show "Software changes"' | translate\"\n (click)=\"showSoftwareChanges.emit()\"\n >\n <span translate>Show \"Software changes\"</span>\n <i c8yIcon=\"chevron-right\"></i>\n </button>\n </div>\n</div>\n\n<ng-template\n #alreadyInstalledWarning\n let-item\n let-option=\"option\"\n>\n <i\n class=\"text-warning a-s-center\"\n c8yIcon=\"warning\"\n [tooltip]=\"\n alreadyInstalledMessage\n | translate: { name: item.body[0].value, version: option.body[0].value }\n \"\n ></i>\n</ng-template>\n\n<ng-template #loading>\n <div class=\"p-relative d-flex m-l-auto\">\n <i\n class=\"icon-spin\"\n c8yIcon=\"circle-o-notch\"\n ></i>\n </div>\n</ng-template>\n\n<ng-template #softwareType>\n <c8y-software-type\n additionalFilter\n [required]=\"false\"\n [placeholder]=\"'Filter by software type\u2026' | translate\"\n (onSelectSoftware)=\"search({ softwareType: $event?.softwareType })\"\n [emitResultsOnly]=\"true\"\n [showBtnInNotFoundMessage]=\"false\"\n [allowFreeEntries]=\"false\"\n [showClearSelectionOption]=\"true\"\n [presetSoftwareTypes]=\"device.c8y_SupportedSoftwareTypes\"\n ></c8y-software-type>\n</ng-template>\n" }]
}], ctorParameters: () => [{ type: i1$1.RepositoryService }, { type: i1.BsModalService }], propDecorators: { device: [{
type: Input
}], softwareList: [{
type: Input
}], deviceSoftwareChanges: [{
type: Input
}], deviceSoftwareChangesInProgress: [{
type: Input
}], typesQuery: [{
type: Input
}], changes: [{
type: Output
}], showSoftwareChanges: [{
type: Output
}], alreadyInstalledWarningTemplate: [{
type: ViewChild,
args: ['alreadyInstalledWarning', { static: true }]
}], loadingTemplate: [{
type: ViewChild,
args: ['loading', { static: true }]
}], softwareTypeTemplate: [{
type: ViewChild,
args: ['softwareType', { static: true }]
}] } });
class SoftwareDeviceTabComponent {
constructor(route, repository, inventory, deviceSoftwareService, serviceRegistry) {
this.route = route;
this.repository = repository;
this.inventory = inventory;
this.deviceSoftwareService = deviceSoftwareService;
this.serviceRegistry = serviceRegistry;
this.deviceId = this.route.snapshot.parent.data.contextData.id;
this.device$ = new BehaviorSubject(this.route.snapshot.parent.data.contextData);
this.typesQuery$ = this.device$.pipe(map(device => {
const deviceTypeQuery = this.repository.getDeviceTypeQuery(RepositoryType.SOFTWARE, device);
return this.repository.getSoftwareTypeQuery(device, deviceTypeQuery);
}));
this.changes$ = new BehaviorSubject([]);
this.changesOperation$ = new BehaviorSubject(null);
this.changesInProgress$ = this.changesOperation$.pipe(map(operation => this.isInProgress(operation)));
this.reloading = false;
this.showSoftwareChanges = false;
}
async ngOnInit() {
const advancedSoftwareService = head(this.serviceRegistry.get('asm'));
this.list$ = this.device$.pipe(switchMap(device => advancedSoftwareService
? advancedSoftwareService
.isASMAvailable()
.then(isASMAvailable => ({ isASMAvailable, device }))
: of({ isASMAvailable: false, device })), map(({ isASMAvailable, device }) =>
// with ASM available software items will be retrieved directly in the
// device-software-list component
isASMAvailable ? undefined : this.repository.getDeviceSoftwareList(device)));
await this.loadDevice();
await this.loadOperation();
}
addChanges(requestedChanges) {
let stagedChanges = [...this.changes$.value];
requestedChanges.forEach(requestedChange => {
const alreadyStaged = stagedChanges.some(stagedChange => this.areSameChanges(stagedChange, requestedChange));
if (!alreadyStaged) {
stagedChanges = [...stagedChanges, requestedChange];
}
});
this.changes$.next(stagedChanges);
}
dropChange(changeToBeDropped) {
let stagedChanges = [...this.changes$.value];
stagedChanges = stagedChanges.filter(stagedChange => !this.areSameChanges(stagedChange, changeToBeDropped));
this.changes$.next(stagedChanges);
}
areSameChanges(change1, change2) {
return (change1.name === change2.name &&
change1.version === change2.version &&
change1.action === change2.action);
}
clearChanges() {
this.changes$.next([]);
}
async loadDevice() {
this.reloading = true;
this.deviceSoftwareService.reload();
const device = await Promise.all([
this.deviceSoftwareService.loading$
.pipe(filter$1(loading => !loading), take(1))
.toPromise(),
this.inventory.detail(this.deviceId, { withChildren: false }).then(result => result.data)
]).then(([_, device]) => device);
this.device$.next(device);
this.reloading = false;
}
async applyChanges() {
const operation = await this.repository.createSoftwareUpdateOperation(this.device$.value, this.changes$.value);
await this.trackOperation(operation);
}
async loadOperation() {
const operation = await this.repository.getLastSoftwareUpdateOperation(this.deviceId);
await this.trackOperation(operation);
}
async trackOperation(operation) {
if ([OperationStatus.SUCCESSFUL, OperationStatus.FAILED].includes(operation?.status)) {
this.changesOperation$.next(undefined);
}
else
this.changesOperation$.next(operation);
if (this.isInProgress(operation)) {
await this.displayChangesFromOperation(operation);
this.repository.observeOperation(operation).subscribe(operationUpdate => {
this.changesOperation$.next(operationUpdate);
if (operationUpdate.status === OperationStatus.SUCCESSFUL) {
this.clearChanges();
this.loadDevice();
}
}, operationUpdate => {
this.changesOperation$.next(operationUpdate);
});
}
}
async displayChangesFromOperation(operation) {
const changes = await this.repository.getDeviceSoftwareChangesFromOperation(operation, this.device$.value);
this.changes$.next(changes);
}
isInProgress(operation) {
return (operation && [OperationStatus.PENDING, OperationStatus.EXECUTING].includes(operation.status));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareDeviceTabComponent, deps: [{ token: i1$2.ActivatedRoute }, { token: i1$1.RepositoryService }, { token: i2.InventoryService }, { token: DeviceSoftwareService }, { token: i4.ServiceRegistry }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: SoftwareDeviceTabComponent, isStandalone: false, selector: "c8y-software-device-tab", ngImport: i0, template: "<c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Reload' | translate }}\"\n (click)=\"loadDevice()\"\n >\n <i\n c8yIcon=\"refresh\"\n [ngClass]=\"{ 'icon-spin': reloading }\"\n ></i>\n {{ 'Reload' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<div class=\"card split-view--7-5 m-b-0\">\n <c8y-installed-software\n class=\"split-view__list\"\n [device]=\"device$ | async\"\n [typesQuery]=\"typesQuery$ | async\"\n [softwareList]=\"list$ | async\"\n [deviceSoftwareChanges]=\"changes$ | async\"\n [deviceSoftwareChangesInProgress]=\"changesInProgress$ | async\"\n (changes)=\"addChanges($event)\"\n (showSoftwareChanges)=\"showSoftwareChanges = true\"\n ></c8y-installed-software>\n <c8y-device-software-changes\n class=\"bg-level-1 split-view__detail\"\n [ngClass]=\"{ 'split-view__detail--selected': showSoftwareChanges }\"\n [deviceSoftwareChangesOperation]=\"changesOperation$ | async\"\n [changes]=\"changes$ | async\"\n [changesInProgress]=\"changesInProgress$ | async\"\n (clear)=\"clearChanges()\"\n (drop)=\"dropChange($event)\"\n (apply)=\"applyChanges()\"\n (hideSoftwareChanges)=\"showSoftwareChanges = false\"\n ></c8y-device-software-changes>\n</div>\n", dependencies: [{ kind: "directive", type: i7.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: i4.ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "directive", type: i4.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: InstalledSoftwareComponent, selector: "c8y-installed-software", inputs: ["device", "softwareList", "deviceSoftwareChanges", "deviceSoftwareChangesInProgress", "typesQuery"], outputs: ["changes", "showSoftwareChanges"] }, { kind: "component", type: DeviceSoftwareChangesComponent, selector: "c8y-device-software-changes", inputs: ["changes", "changesInProgress", "deviceSoftwareChangesOperation"], outputs: ["clear", "drop", "apply", "hideSoftwareChanges"] }, { kind: "pipe", type: i7.AsyncPipe, name: "async" }, { kind: "pipe", type: i4.C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareDeviceTabComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-software-device-tab', standalone: false, template: "<c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Reload' | translate }}\"\n (click)=\"loadDevice()\"\n >\n <i\n c8yIcon=\"refresh\"\n [ngClass]=\"{ 'icon-spin': reloading }\"\n ></i>\n {{ 'Reload' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<div class=\"card split-view--7-5 m-b-0\">\n <c8y-installed-software\n class=\"split-view__list\"\n [device]=\"device$ | async\"\n [typesQuery]=\"typesQuery$ | async\"\n [softwareList]=\"list$ | async\"\n [deviceSoftwareChanges]=\"changes$ | async\"\n [deviceSoftwareChangesInProgress]=\"changesInProgress$ | async\"\n (changes)=\"addChanges($event)\"\n (showSoftwareChanges)=\"showSoftwareChanges = true\"\n ></c8y-installed-software>\n <c8y-device-software-changes\n class=\"bg-level-1 split-view__detail\"\n [ngClass]=\"{ 'split-view__detail--selected': showSoftwareChanges }\"\n [deviceSoftwareChangesOperation]=\"changesOperation$ | async\"\n [changes]=\"changes$ | async\"\n [changesInProgress]=\"changesInProgress$ | async\"\n (clear)=\"clearChanges()\"\n (drop)=\"dropChange($event)\"\n (apply)=\"applyChanges()\"\n (hideSoftwareChanges)=\"showSoftwareChanges = false\"\n ></c8y-device-software-changes>\n</div>\n" }]
}], ctorParameters: () => [{ type: i1$2.ActivatedRoute }, { type: i1$1.RepositoryService }, { type: i2.InventoryService }, { type: DeviceSoftwareService }, { type: i4.ServiceRegistry }] });
class SoftwareDeviceTabGuard {
constructor() {
this.operations = ['c8y_SoftwareUpdate', 'c8y_SoftwareList', 'c8y_Software'];
}
canActivate(route) {
const device = get(route, 'data.contextData') || get(route, 'parent.data.contextData');
return (this.isAnyOperationSupported(device) ||
has(device, 'c8y_Software') ||
has(device, 'c8y_SoftwareList') ||
has(device, 'c8y_SupportedSoftwareTypes'));
}
isAnyOperationSupported(device) {
const supportedOperations = device?.c8y_SupportedOperations || [];
return this.operations.some(operation => supportedOperations.indexOf(operation) >= 0);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareDeviceTabGuard, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareDeviceTabGuard }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareDeviceTabGuard, decorators: [{
type: Injectable
}] });
class SoftwareRepositoryDeviceTabModule {
static forRoot() {
return {
ngModule: SoftwareRepositoryDeviceTabModule,
providers: [
SoftwareDeviceTabGuard,
hookRoute({
context: ViewContext.Device,
path: 'software',
component: SoftwareDeviceTabComponent,
label: gettext('Software'),
icon: 'c8y-tools',
priority: 499,
canActivate: [SoftwareDeviceTabGuard]
})
]
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareRepositoryDeviceTabModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: SoftwareRepositoryDeviceTabModule, declarations: [SoftwareDeviceTabComponent,
InstalledSoftwareComponent,
DeviceSoftwareListComponent,
DeviceSoftwareChangesComponent], imports: [CommonModule,
CoreModule,
RouterModule,
SharedRepositoryModule,
OperationDetailsModule,
TooltipModule] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareRepositoryDeviceTabModule, imports: [CommonModule,
CoreModule,
RouterModule,
SharedRepositoryModule,
OperationDetailsModule,
TooltipModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareRepositoryDeviceTabModule, decorators: [{
type: NgModule,
args: [{
imports: [
CommonModule,
CoreModule,
RouterModule,
SharedRepositoryModule,
OperationDetailsModule,
TooltipModule
],
declarations: [
SoftwareDeviceTabComponent,
InstalledSoftwareComponent,
DeviceSoftwareListComponent,
DeviceSoftwareChangesComponent
]
}]
}] });
class SoftwareRepositoryModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareRepositoryModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: SoftwareRepositoryModule, imports: [SoftwareRepositoryDeviceTabModule, SoftwareRepositoryListModule] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareRepositoryModule, imports: [SoftwareRepositoryDeviceTabModule.forRoot(), SoftwareRepositoryListModule.forRoot()] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: SoftwareRepositoryModule, decorators: [{
type: NgModule,
args: [{
imports: [SoftwareRepositoryDeviceTabModule.forRoot(), SoftwareRepositoryListModule.forRoot()]
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { AddSoftwareModalComponent, DeviceSoftwareChangesComponent, DeviceSoftwareListComponent, DeviceSoftwareService, InstalledSoftwareComponent, SoftwareDetailsComponent, SoftwareDeviceTabComponent, SoftwareDeviceTabGuard, SoftwareListComponent, SoftwareRepositoryDeviceTabModule, SoftwareRepositoryListModule, SoftwareRepositoryModule };
//# sourceMappingURL=c8y-ngx-components-repository-software.mjs.map