@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
446 lines (441 loc) • 134 kB
JavaScript
import * as i0 from '@angular/core';
import { Component, Injectable, NgModule, EventEmitter, ViewChild, Output } from '@angular/core';
import * as i1 from '@angular/router';
import * as i3 from '@c8y/client';
import { OperationStatus } from '@c8y/client';
import * as i3$1 from '@c8y/ngx-components';
import { gettext, ModalSelectionMode, hookRoute, ViewContext, CoreModule, ValidationPattern, Status, memoize, BuiltInActionType, NavigatorNode, hookNavigator, FormsModule } from '@c8y/ngx-components';
import * as i2 from '@c8y/ngx-components/repository/shared';
import { PRODUCT_EXPERIENCE_REPOSITORY_SHARED, RepositoryType, RepositorySelectModalComponent, SharedRepositoryModule, RepositoryItemNameGridColumn, DescriptionGridColumn, DeviceTypeGridColumn, VersionsGridColumn } from '@c8y/ngx-components/repository/shared';
import { isEmpty, get, assign, has, indexOf, isUndefined, property } from 'lodash-es';
import * as i1$1 from 'ngx-bootstrap/modal';
import { BehaviorSubject, from, combineLatest, of, merge, pipe, Subject, defer, map as map$1 } from 'rxjs';
import { map, filter, switchMap, shareReplay, take, distinctUntilChanged, debounceTime, tap, distinctUntilKeyChanged, withLatestFrom, takeUntil } from 'rxjs/operators';
import * as i7 from '@angular/common';
import { CommonModule } from '@angular/common';
import * as i7$1 from '@c8y/ngx-components/operations/operation-details';
import { OperationDetailsModule } from '@c8y/ngx-components/operations/operation-details';
import * as i5 from '@angular/forms';
import 'ngx-bootstrap/dropdown';
import * as i6 from 'ngx-bootstrap/popover';
import { PopoverModule } from 'ngx-bootstrap/popover';
import { __decorate, __metadata } from 'tslib';
import * as i5$1 from '@ngx-translate/core';
import * as i10 from 'ngx-bootstrap/tooltip';
import { TooltipModule } from 'ngx-bootstrap/tooltip';
import { DeviceGridModule } from '@c8y/ngx-components/device-grid';
class FirmwareDeviceTabComponent {
constructor(route, repository, inventory, bsModal, gainsightService) {
this.route = route;
this.repository = repository;
this.inventory = inventory;
this.bsModal = bsModal;
this.gainsightService = gainsightService;
this.PRODUCT_EXPERIENCE = PRODUCT_EXPERIENCE_REPOSITORY_SHARED;
this.isEmpty = isEmpty;
this.reloading = false;
this.device$ = new BehaviorSubject(this.route.parent.snapshot.data.contextData);
this.deviceFirmwareFragment$ = this.device$.pipe(map(device => device.c8y_Firmware));
this.firmwareBinary$ = this.deviceFirmwareFragment$.pipe(filter(deviceFirmwareFragment => !isEmpty(deviceFirmwareFragment)), switchMap(deviceFirmwareFragment => from(this.repository.getRepositoryBinaryMoByVersion(deviceFirmwareFragment, RepositoryType.FIRMWARE))), shareReplay(1));
this.repositoryEntry$ = this.firmwareBinary$.pipe(switchMap(mo => this.repository.getRepositoryEntryMO$(mo)), shareReplay(1));
this.patches$ = combineLatest(this.firmwareBinary$, this.repositoryEntry$).pipe(switchMap(([firmwareBinary, repositoryEntry]) => {
if (repositoryEntry && firmwareBinary) {
const version = this.repository.getBaseVersionFromMO(firmwareBinary);
return from(this.repository.listPatchVersions(repositoryEntry, version)).pipe(map(({ data }) => data));
}
else {
return of([]);
}
}), shareReplay(1));
this.supportsFirmwareOperations$ = this.device$.pipe(map((device) => get(device, 'c8y_SupportedOperations', []).indexOf('c8y_Firmware') > -1));
this.changesOperation$ = new BehaviorSubject(null);
this.changesInProgress$ = this.changesOperation$.pipe(map(operation => this.isInProgress(operation)));
}
async ngOnInit() {
// TODO check route snapshot, why is not refreshing device.
// Scenario: missing deviceFirmwareFragment => install new version => switch tabs.
// Expected: device should be set.
await this.loadDevice();
await this.loadOperation();
}
installFirmware() {
const initialState = {
repositoryEntriesWithVersions$: of([]),
repositoryEntriesWithVersionsFn$: modal => this.getRepositoryEntriesWithVersions$(modal.content.searchTerm),
repositoryType: RepositoryType.FIRMWARE,
title: gettext('Install firmware'),
subTitle: gettext('Available firmwares matching the device type'),
icon: 'c8y-firmware',
mode: ModalSelectionMode.SINGLE,
labels: { ok: gettext('Install') },
disableSelected: false
};
this.deviceFirmwareFragment$
.pipe(take(1), switchMap(deviceFirmwareFragment => {
if (deviceFirmwareFragment) {
const { name, version } = deviceFirmwareFragment;
const selected = [{ name, version }];
assign(initialState, { selected });
}
const modal = this.bsModal.show(RepositorySelectModalComponent, {
ignoreBackdropClick: true,
initialState
});
if (initialState.repositoryEntriesWithVersionsFn$) {
modal.content.repositoryEntriesWithVersions$ =
initialState.repositoryEntriesWithVersionsFn$(modal);
}
modal.content.load.next();
return modal.content.resultEmitter;
}))
.subscribe(([selectedFirmware]) => {
this.handleOperation(selectedFirmware);
});
}
getRepositoryEntriesWithVersions$(searchTerm$) {
return searchTerm$.pipe(distinctUntilChanged(), switchMap(searchTerm => this.repository.listRepositoryEntries(RepositoryType.FIRMWARE, {
query: this.repository.getDeviceTypeQuery(RepositoryType.FIRMWARE, this.device$.value),
partialName: searchTerm?.name,
params: { pageSize: 100 }
})), map(({ data }) => data), map(mos => this.getAndAssignRepositoryBinaries(mos)), shareReplay(1));
}
getAndAssignRepositoryBinaries(mos) {
mos.forEach(mo => {
mo.versions = this.repository.listBaseVersions(mo);
});
return mos;
}
addPatch() {
const initialState = {
repositoryType: RepositoryType.FIRMWARE,
repositoryEntriesWithVersions$: this.getRepositoryEntryWithPatches$(),
title: gettext('Install firmware'),
subTitle: gettext('Available firmwares matching the device type'),
icon: 'c8y-firmware',
mode: ModalSelectionMode.SINGLE,
labels: { ok: gettext('Install') },
disableSelected: false
};
this.deviceFirmwareFragment$
.pipe(take(1), switchMap(deviceFirmwareFragment => {
if (deviceFirmwareFragment) {
const { name, version } = deviceFirmwareFragment;
const selected = [{ name, version }];
assign(initialState, { selected });
}
const modal = this.bsModal.show(RepositorySelectModalComponent, {
ignoreBackdropClick: true,
initialState
});
modal.content.load.next();
return modal.content.resultEmitter;
}))
.subscribe(([selectedOption]) => {
this.handleOperation(selectedOption);
});
}
getRepositoryEntryWithPatches$() {
return combineLatest(this.repositoryEntry$, this.patches$).pipe(map(([repositoryEntry, patches]) => {
return [{ ...repositoryEntry, versions: patches }];
}));
}
async loadDevice() {
this.reloading = true;
const deviceId = this.device$.value.id;
const device = (await this.inventory.detail(deviceId, { withChildren: false })).data;
this.device$.next(device);
this.reloading = false;
}
async handleOperation(selectedFirmware) {
const operation = await this.repository.createFirmwareUpdateOperation(this.device$.value, selectedFirmware);
this.trackOperation(operation);
this.gainsightService.triggerEvent(PRODUCT_EXPERIENCE_REPOSITORY_SHARED.FIRMWARE.EVENTS.DEVICE_TAB, {
component: PRODUCT_EXPERIENCE_REPOSITORY_SHARED.FIRMWARE.COMPONENTS.FIRMWARE_DEVICE_TAB,
result: PRODUCT_EXPERIENCE_REPOSITORY_SHARED.FIRMWARE.RESULTS.CREATE_FIRMWARE_UPDATE_OPERATION
});
}
async loadOperation() {
const deviceId = this.device$.value.id;
const operation = await this.repository.getLastFirmwareUpdateOperation(deviceId);
this.trackOperation(operation);
}
trackOperation(operation) {
if ([OperationStatus.SUCCESSFUL, OperationStatus.FAILED].includes(operation?.status)) {
this.changesOperation$.next(undefined);
}
else
this.changesOperation$.next(operation);
if (this.isInProgress(operation)) {
this.repository.observeOperation(operation).subscribe(operationUpdate => {
this.changesOperation$.next(operationUpdate);
if (operationUpdate.status === OperationStatus.SUCCESSFUL) {
this.loadDevice();
}
}, operationUpdate => {
this.changesOperation$.next(operationUpdate);
});
}
}
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: FirmwareDeviceTabComponent, deps: [{ token: i1.ActivatedRoute }, { token: i2.RepositoryService }, { token: i3.InventoryService }, { token: i1$1.BsModalService }, { token: i3$1.GainsightService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: FirmwareDeviceTabComponent, isStandalone: false, selector: "c8y-firmware-device-tab", ngImport: i0, template: "<div class=\"row\">\n <div class=\"col-lg-12 col-lg-max\">\n <div class=\"card split-view--7-5 m-b-0\">\n <div class=\"d-flex d-col flex-grow split-view__list\">\n <div class=\"card-header separator\">\n <div\n class=\"card-title\"\n translate\n >\n Current firmware\n </div>\n </div>\n <div class=\"inner-scroll\">\n <div class=\"card-block p-t-0 p-b-0\">\n <!-- EMPTY STATE -->\n <ng-container *ngIf=\"isEmpty(deviceFirmwareFragment$ | async); else firmwareBlock\">\n <div class=\"c8y-empty-state text-center\">\n <div\n class=\"h1 c8y-icon-duocolor\"\n c8yIcon=\"c8y-firmware\"\n ></div>\n <p>\n <strong translate>No firmware installed.</strong>\n <br />\n <small translate>Click below to install firmware into this device.</small>\n </p>\n </div>\n </ng-container>\n\n <!-- FIRMWARE -->\n <ng-template #firmwareBlock>\n <c8y-list-group class=\"no-border-last\">\n <c8y-li>\n <c8y-li-icon>\n <i c8yIcon=\"c8y-firmware\"></i>\n </c8y-li-icon>\n\n <c8y-li-body *ngIf=\"deviceFirmwareFragment$ | async as deviceFirmwareFragment\">\n <!-- Firmware title -->\n <p class=\"text-medium\">\n {{ deviceFirmwareFragment.name }}\n </p>\n <!-- Firmware description -->\n <div *ngIf=\"repositoryEntry$ | async as repositoryEntry\">\n <span\n class=\"text-label-small m-r-4\"\n translate\n >\n Description\n </span>\n <span>\n {{ repositoryEntry.description }}\n </span>\n </div>\n\n <!-- BASE/PATCH VERSION -->\n <div class=\"d-flex a-i-baseline\">\n <p\n class=\"text-label-small m-r-4\"\n translate\n >\n Version\n </p>\n <p *ngIf=\"deviceFirmwareFragment.version; else versionNotSpecified\">\n {{ deviceFirmwareFragment.version }}\n </p>\n <ng-template #versionNotSpecified>\n <p>\n <em class=\"text-muted\">({{ 'not specified`version`' | translate }})</em>\n </p>\n </ng-template>\n </div>\n\n <!-- ADD PATCH -->\n <button\n class=\"btn btn-xs btn-primary\"\n title=\"{{ 'Patches available' | translate }}\"\n *ngIf=\"\n (supportsFirmwareOperations$ | async) && (this.patches$ | async)?.length > 0\n \"\n (click)=\"addPatch()\"\n [disabled]=\"changesInProgress$ | async\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.FIRMWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.FIRMWARE.COMPONENTS.FIRMWARE_DEVICE_TAB,\n action:\n PRODUCT_EXPERIENCE.FIRMWARE.ACTIONS.OPEN_INSTALL_FIRMWARE_PATCH_DIALOG\n }\"\n >\n {{ 'Patches available' | translate }}\n </button>\n </c8y-li-body>\n </c8y-li>\n </c8y-list-group>\n </ng-template>\n </div>\n </div>\n <div\n class=\"card-footer separator-top\"\n *ngIf=\"supportsFirmwareOperations$ | async\"\n >\n <!-- INSTALL FIRMWARE -->\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Install firmware' | translate }}\"\n *ngIf=\"isEmpty(deviceFirmwareFragment$ | async)\"\n (click)=\"installFirmware()\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.FIRMWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.FIRMWARE.COMPONENTS.FIRMWARE_DEVICE_TAB,\n action: PRODUCT_EXPERIENCE.FIRMWARE.ACTIONS.OPEN_INSTALL_FIRMWARE_DIALOG\n }\"\n >\n {{ 'Install firmware' | translate }}\n </button>\n\n <!-- REPLACE FIRMWARE -->\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Replace firmware' | translate }}\"\n *ngIf=\"!isEmpty(deviceFirmwareFragment$ | async)\"\n (click)=\"installFirmware()\"\n [disabled]=\"changesInProgress$ | async\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.FIRMWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.FIRMWARE.COMPONENTS.FIRMWARE_DEVICE_TAB,\n action: PRODUCT_EXPERIENCE.FIRMWARE.ACTIONS.OPEN_REPLACE_FIRMWARE_DIALOG\n }\"\n >\n {{ 'Replace firmware' | translate }}\n </button>\n </div>\n </div>\n <div class=\"inner-scroll d-flex d-col bg-level-1 split-view__detail\">\n <div class=\"card-header separator large-padding sticky-top\">\n <div\n class=\"card-title\"\n translate\n >\n Firmware 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=\"changesOperation$ | async\"\n >\n <c8y-operation-details [operation]=\"changesOperation$ | async\"></c8y-operation-details>\n </fieldset>\n </div>\n </div>\n </div>\n </div>\n</div>\n", dependencies: [{ kind: "directive", type: i7.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: i3$1.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i3$1.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: i3$1.ListGroupComponent, selector: "c8y-list-group" }, { kind: "component", type: i3$1.ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: i3$1.ListItemIconComponent, selector: "c8y-list-item-icon, c8y-li-icon", inputs: ["icon", "status"] }, { kind: "component", type: i3$1.ListItemBodyComponent, selector: "c8y-list-item-body, c8y-li-body", inputs: ["body"] }, { kind: "directive", type: i3$1.ProductExperienceDirective, selector: "[c8yProductExperience]", inputs: ["actionName", "actionData", "inherit", "suppressDataOverriding"] }, { kind: "component", type: i7$1.OperationDetailsComponent, selector: "c8y-operation-details", inputs: ["operation"] }, { kind: "pipe", type: i7.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FirmwareDeviceTabComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-firmware-device-tab', standalone: false, template: "<div class=\"row\">\n <div class=\"col-lg-12 col-lg-max\">\n <div class=\"card split-view--7-5 m-b-0\">\n <div class=\"d-flex d-col flex-grow split-view__list\">\n <div class=\"card-header separator\">\n <div\n class=\"card-title\"\n translate\n >\n Current firmware\n </div>\n </div>\n <div class=\"inner-scroll\">\n <div class=\"card-block p-t-0 p-b-0\">\n <!-- EMPTY STATE -->\n <ng-container *ngIf=\"isEmpty(deviceFirmwareFragment$ | async); else firmwareBlock\">\n <div class=\"c8y-empty-state text-center\">\n <div\n class=\"h1 c8y-icon-duocolor\"\n c8yIcon=\"c8y-firmware\"\n ></div>\n <p>\n <strong translate>No firmware installed.</strong>\n <br />\n <small translate>Click below to install firmware into this device.</small>\n </p>\n </div>\n </ng-container>\n\n <!-- FIRMWARE -->\n <ng-template #firmwareBlock>\n <c8y-list-group class=\"no-border-last\">\n <c8y-li>\n <c8y-li-icon>\n <i c8yIcon=\"c8y-firmware\"></i>\n </c8y-li-icon>\n\n <c8y-li-body *ngIf=\"deviceFirmwareFragment$ | async as deviceFirmwareFragment\">\n <!-- Firmware title -->\n <p class=\"text-medium\">\n {{ deviceFirmwareFragment.name }}\n </p>\n <!-- Firmware description -->\n <div *ngIf=\"repositoryEntry$ | async as repositoryEntry\">\n <span\n class=\"text-label-small m-r-4\"\n translate\n >\n Description\n </span>\n <span>\n {{ repositoryEntry.description }}\n </span>\n </div>\n\n <!-- BASE/PATCH VERSION -->\n <div class=\"d-flex a-i-baseline\">\n <p\n class=\"text-label-small m-r-4\"\n translate\n >\n Version\n </p>\n <p *ngIf=\"deviceFirmwareFragment.version; else versionNotSpecified\">\n {{ deviceFirmwareFragment.version }}\n </p>\n <ng-template #versionNotSpecified>\n <p>\n <em class=\"text-muted\">({{ 'not specified`version`' | translate }})</em>\n </p>\n </ng-template>\n </div>\n\n <!-- ADD PATCH -->\n <button\n class=\"btn btn-xs btn-primary\"\n title=\"{{ 'Patches available' | translate }}\"\n *ngIf=\"\n (supportsFirmwareOperations$ | async) && (this.patches$ | async)?.length > 0\n \"\n (click)=\"addPatch()\"\n [disabled]=\"changesInProgress$ | async\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.FIRMWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.FIRMWARE.COMPONENTS.FIRMWARE_DEVICE_TAB,\n action:\n PRODUCT_EXPERIENCE.FIRMWARE.ACTIONS.OPEN_INSTALL_FIRMWARE_PATCH_DIALOG\n }\"\n >\n {{ 'Patches available' | translate }}\n </button>\n </c8y-li-body>\n </c8y-li>\n </c8y-list-group>\n </ng-template>\n </div>\n </div>\n <div\n class=\"card-footer separator-top\"\n *ngIf=\"supportsFirmwareOperations$ | async\"\n >\n <!-- INSTALL FIRMWARE -->\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Install firmware' | translate }}\"\n *ngIf=\"isEmpty(deviceFirmwareFragment$ | async)\"\n (click)=\"installFirmware()\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.FIRMWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.FIRMWARE.COMPONENTS.FIRMWARE_DEVICE_TAB,\n action: PRODUCT_EXPERIENCE.FIRMWARE.ACTIONS.OPEN_INSTALL_FIRMWARE_DIALOG\n }\"\n >\n {{ 'Install firmware' | translate }}\n </button>\n\n <!-- REPLACE FIRMWARE -->\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Replace firmware' | translate }}\"\n *ngIf=\"!isEmpty(deviceFirmwareFragment$ | async)\"\n (click)=\"installFirmware()\"\n [disabled]=\"changesInProgress$ | async\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.FIRMWARE.EVENTS.DEVICE_TAB\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.FIRMWARE.COMPONENTS.FIRMWARE_DEVICE_TAB,\n action: PRODUCT_EXPERIENCE.FIRMWARE.ACTIONS.OPEN_REPLACE_FIRMWARE_DIALOG\n }\"\n >\n {{ 'Replace firmware' | translate }}\n </button>\n </div>\n </div>\n <div class=\"inner-scroll d-flex d-col bg-level-1 split-view__detail\">\n <div class=\"card-header separator large-padding sticky-top\">\n <div\n class=\"card-title\"\n translate\n >\n Firmware 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=\"changesOperation$ | async\"\n >\n <c8y-operation-details [operation]=\"changesOperation$ | async\"></c8y-operation-details>\n </fieldset>\n </div>\n </div>\n </div>\n </div>\n</div>\n" }]
}], ctorParameters: () => [{ type: i1.ActivatedRoute }, { type: i2.RepositoryService }, { type: i3.InventoryService }, { type: i1$1.BsModalService }, { type: i3$1.GainsightService }] });
const FIRMWARE_FRAGMENT = 'c8y_Firmware';
const SUPPORTED_OPERATIONS_FRAGMENT = 'c8y_SupportedOperations';
class FirmwareDeviceTabGuard {
canActivate(route) {
const contextData = get(route, 'data.contextData') || get(route, 'parent.data.contextData');
const supportedOperations = get(contextData, SUPPORTED_OPERATIONS_FRAGMENT);
return ((!!supportedOperations ? indexOf(supportedOperations, FIRMWARE_FRAGMENT) >= 0 : false) ||
has(contextData, 'c8y_Firmware'));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FirmwareDeviceTabGuard, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FirmwareDeviceTabGuard }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FirmwareDeviceTabGuard, decorators: [{
type: Injectable
}] });
class FirmwareRepositoryDeviceTabModule {
static forRoot() {
return {
ngModule: FirmwareRepositoryDeviceTabModule,
providers: [
FirmwareDeviceTabGuard,
hookRoute({
context: ViewContext.Device,
path: 'firmware',
component: FirmwareDeviceTabComponent,
label: gettext('Firmware'),
icon: 'c8y-firmware',
priority: 500,
canActivate: [FirmwareDeviceTabGuard]
})
]
};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FirmwareRepositoryDeviceTabModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "19.2.14", ngImport: i0, type: FirmwareRepositoryDeviceTabModule, declarations: [FirmwareDeviceTabComponent], imports: [CommonModule, CoreModule, SharedRepositoryModule, OperationDetailsModule] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FirmwareRepositoryDeviceTabModule, imports: [CommonModule, CoreModule, SharedRepositoryModule, OperationDetailsModule] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: FirmwareRepositoryDeviceTabModule, decorators: [{
type: NgModule,
args: [{
imports: [CommonModule, CoreModule, SharedRepositoryModule, OperationDetailsModule],
declarations: [FirmwareDeviceTabComponent]
}]
}] });
class AddFirmwarePatchModalComponent {
constructor(modal, repository, alert) {
this.modal = modal;
this.repository = repository;
this.alert = alert;
this.PRODUCT_EXPERIENCE = PRODUCT_EXPERIENCE_REPOSITORY_SHARED;
this.saved = new EventEmitter();
this.textForFirmwareUrlPopover = gettext(`Path for binaries can vary depending on device agent implementation, for example:
/firmware/binaries/firmware1.bin
https://firmware/binary/123
ftp://firmware/binary/123.tar.gz
`);
this.model = {
selected: undefined,
dependency: null,
patchVersion: undefined,
binary: {
file: undefined,
url: undefined
}
};
this.firmwareInput$ = new BehaviorSubject('');
this.firmwares$ = this.firmwareInput$.pipe(debounceTime(300), distinctUntilChanged(), switchMap(searchStr => from(this.repository.listRepositoryEntries(RepositoryType.FIRMWARE, {
partialName: searchStr,
skipLegacy: true
}))), shareReplay(1));
this.firmwareSelected$ = new BehaviorSubject(null);
this.patchDependencyInput$ = new BehaviorSubject('');
this.saving = false;
this.firmwarePreselected = false;
this.baseVersions$ = merge(this.firmwareInput$.pipe(tap(() => {
this.model.dependency = null;
if (this.form) {
this.form.form.get('patchDependency').reset();
}
}), switchMap(() => of(null))), this.firmwareSelected$).pipe(switchMap(selectedFirmware => selectedFirmware ? this.repository.listBaseVersions(selectedFirmware) : of(null)), shareReplay(1));
this.baseVersionsFilterPipe = pipe(switchMap((data) => this.patchDependencyInput$.pipe(map(partialVersion => data.filter((mo) => {
const version = mo.c8y_Firmware.version?.toLowerCase();
return (partialVersion.length === 0 || version?.indexOf(partialVersion.toLowerCase()) > -1);
})))));
}
async ngOnInit() {
this.setInitialState();
}
setInitialState() {
if (this.model.selected) {
this.firmwarePreselected = true;
this.firmwareSelected$.next(this.model.selected);
}
}
async save() {
this.saving = true;
this.repository
.create(this.model, RepositoryType.FIRMWARE)
.then(savedFirmware => {
this.successMsg();
this.saving = false;
this.saved.next(savedFirmware);
this.cancel();
})
.catch(e => {
this.saving = false;
this.saved.error(e);
this.cancel();
});
}
successMsg() {
const msg = gettext('Firmware patch added.');
this.alert.success(msg);
}
cancel() {
this.modal.hide();
this.saved.complete();
}
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
};
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AddFirmwarePatchModalComponent, deps: [{ token: i1$1.BsModalRef }, { token: i2.RepositoryService }, { token: i3$1.AlertService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "19.2.14", type: AddFirmwarePatchModalComponent, isStandalone: false, selector: "c8y-add-firmware-patch-modal.component", outputs: { saved: "saved" }, viewQueries: [{ propertyName: "dropdown", first: true, predicate: ["dropdown"], descendants: true }, { propertyName: "form", first: true, predicate: ["firmwarePatchForm"], descendants: true }], ngImport: i0, template: "<div class=\"viewport-modal\">\n <div class=\"modal-header dialog-header\">\n <i [c8yIcon]=\"'c8y-firmware'\"></i>\n <h4 translate id=\"addFirmwarePatchModalTitle\">Add firmware patch</h4>\n </div>\n <div class=\"p-16 text-center separator-bottom\" id=\"addFirmwarePatchModalDescription\">\n <p class=\"text-medium text-16 m-0\" translate>Select a firmware version</p>\n </div>\n\n <form\n class=\"d-contents\"\n autocomplete=\"off\"\n #firmwarePatchForm=\"ngForm\"\n (ngSubmit)=\"firmwarePatchForm.form.valid && save()\"\n >\n <div class=\"modal-inner-scroll\">\n <div class=\"modal-body\">\n <div [hidden]=\"firmwarePreselected\">\n <c8y-form-group>\n <label for=\"firmwareName\" translate>Firmware</label>\n <c8y-typeahead\n [ngModel]=\"model.selected\"\n name=\"firmwareName\"\n placeholder=\"{{ 'Select or enter' | translate }}\"\n (onSearch)=\"firmwareInput$.next($event)\"\n [allowFreeEntries]=\"false\"\n [required]=\"true\"\n >\n <c8y-li\n *c8yFor=\"let firmware of firmwares$ | async; loadMore: 'auto'\"\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n (click)=\"model.selected = firmware; firmwareSelected$.next(firmware)\"\n [active]=\"model.selected === firmware\"\n >\n <c8y-highlight\n [text]=\"firmware.name || '--'\"\n [pattern]=\"firmwareInput$ | async\"\n ></c8y-highlight>\n </c8y-li>\n </c8y-typeahead>\n <c8y-messages>\n <c8y-message\n name=\"notExisting\"\n [text]=\"'Select one of the existing firmwares.' | translate\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n\n <c8y-form-group>\n <label for=\"patchDependency\" class=\"m-r-8\" translate>Version</label>\n <c8y-typeahead\n [ngModel]=\"model.dependency\"\n name=\"patchDependency\"\n data-cy=\"add-firmware-patch-modal--patchDependency\"\n placeholder=\"{{ 'Select or enter' | translate }}\"\n (onSearch)=\"patchDependencyInput$.next($event)\"\n [displayProperty]=\"'c8y_Firmware.version'\"\n [allowFreeEntries]=\"false\"\n [disabled]=\"\n (baseVersions$ | async) === null || (baseVersions$ | async)?.data.length === 0\n \"\n [required]=\"true\"\n >\n <c8y-li\n *c8yFor=\"\n let baseVersion of baseVersions$;\n loadMore: 'auto';\n pipe: baseVersionsFilterPipe\n \"\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n (click)=\"model.dependency = baseVersion\"\n [active]=\"model.dependency === baseVersion\"\n >\n <c8y-highlight\n [text]=\"baseVersion.c8y_Firmware.version || '--'\"\n [pattern]=\"patchDependencyInput$ | async\"\n ></c8y-highlight>\n </c8y-li>\n </c8y-typeahead>\n <c8y-messages>\n <c8y-message\n name=\"notExisting\"\n [text]=\"'Select one of the existing versions.' | translate\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n\n <c8y-form-group>\n <label for=\"patchVersion\" translate>Patch</label>\n <input\n id=\"patchVersion\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"patchVersion\"\n data-cy=\"add-firmware-patch-modal--patchVersion\"\n [(ngModel)]=\"model.patchVersion\"\n placeholder=\"{{ 'e.g.' | translate }} 1.0.0\"\n required\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <div class=\"legend form-block m-t-40\" translate>Patch file</div>\n <c8y-file-picker\n [maxAllowedFiles]=\"1\"\n (onFilesPicked)=\"onFile($event)\"\n [fileUrlPopover]=\"textForFirmwareUrlPopover\"\n ></c8y-file-picker>\n </c8y-form-group>\n </div>\n </div>\n <div class=\"modal-footer\">\n <button\n title=\"{{ 'Cancel' | translate }}\"\n data-cy=\"add-firmware-patch-modal--cancel-btn\"\n class=\"btn btn-default\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving\"\n translate\n >\n Cancel\n </button>\n <button\n title=\"{{ 'Add firmware patch' | translate }}\"\n class=\"btn btn-primary\"\n type=\"submit\"\n [ngClass]=\"{ 'btn-pending': saving }\"\n [disabled]=\"\n !firmwarePatchForm.form.valid ||\n firmwarePatchForm.form.pristine ||\n (!model.binary?.url && !model.binary?.file) ||\n saving\n \"\n translate\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.FIRMWARE.EVENTS.REPOSITORY\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.FIRMWARE.COMPONENTS.ADD_FIRMWAR_PATCH_MODAL,\n result: PRODUCT_EXPERIENCE.FIRMWARE.RESULTS.ADD_FIRMWARE_PATCH\n }\"\n >\n Add firmware patch\n </button>\n </div>\n </form>\n</div>\n", dependencies: [{ kind: "directive", type: i7.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i3$1.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i3$1.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i3$1.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: i3$1.HighlightComponent, selector: "c8y-highlight", inputs: ["pattern", "text", "elementClass", "shouldTrimPattern"] }, { kind: "component", type: i3$1.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.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: i3$1.FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "directive", type: i3$1.MessageDirective, selector: "c8y-message", inputs: ["name", "text"] }, { kind: "component", type: i3$1.MessagesComponent, selector: "c8y-messages", inputs: ["show", "defaults", "helpMessage"] }, { kind: "directive", type: i3$1.RequiredInputPlaceholderDirective, selector: "input[required], input[formControlName]" }, { kind: "component", type: i3$1.ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: i3$1.FilePickerComponent, selector: "c8y-file-picker", inputs: ["maxAllowedFiles", "uploadChoice", "fileUrl", "fileBinary", "config", "filePickerIndex", "fileUrlPopover"], outputs: ["onFilesPicked"] }, { kind: "directive", type: i3$1.ProductExperienceDirective, selector: "[c8yProductExperience]", inputs: ["actionName", "actionData", "inherit", "suppressDataOverriding"] }, { kind: "pipe", type: i7.AsyncPipe, name: "async" }, { kind: "pipe", type: i3$1.C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "19.2.14", ngImport: i0, type: AddFirmwarePatchModalComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-add-firmware-patch-modal.component', standalone: false, template: "<div class=\"viewport-modal\">\n <div class=\"modal-header dialog-header\">\n <i [c8yIcon]=\"'c8y-firmware'\"></i>\n <h4 translate id=\"addFirmwarePatchModalTitle\">Add firmware patch</h4>\n </div>\n <div class=\"p-16 text-center separator-bottom\" id=\"addFirmwarePatchModalDescription\">\n <p class=\"text-medium text-16 m-0\" translate>Select a firmware version</p>\n </div>\n\n <form\n class=\"d-contents\"\n autocomplete=\"off\"\n #firmwarePatchForm=\"ngForm\"\n (ngSubmit)=\"firmwarePatchForm.form.valid && save()\"\n >\n <div class=\"modal-inner-scroll\">\n <div class=\"modal-body\">\n <div [hidden]=\"firmwarePreselected\">\n <c8y-form-group>\n <label for=\"firmwareName\" translate>Firmware</label>\n <c8y-typeahead\n [ngModel]=\"model.selected\"\n name=\"firmwareName\"\n placeholder=\"{{ 'Select or enter' | translate }}\"\n (onSearch)=\"firmwareInput$.next($event)\"\n [allowFreeEntries]=\"false\"\n [required]=\"true\"\n >\n <c8y-li\n *c8yFor=\"let firmware of firmwares$ | async; loadMore: 'auto'\"\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n (click)=\"model.selected = firmware; firmwareSelected$.next(firmware)\"\n [active]=\"model.selected === firmware\"\n >\n <c8y-highlight\n [text]=\"firmware.name || '--'\"\n [pattern]=\"firmwareInput$ | async\"\n ></c8y-highlight>\n </c8y-li>\n </c8y-typeahead>\n <c8y-messages>\n <c8y-message\n name=\"notExisting\"\n [text]=\"'Select one of the existing firmwares.' | translate\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n </div>\n\n <c8y-form-group>\n <label for=\"patchDependency\" class=\"m-r-8\" translate>Version</label>\n <c8y-typeahead\n [ngModel]=\"model.dependency\"\n name=\"patchDependency\"\n data-cy=\"add-firmware-patch-modal--patchDependency\"\n placeholder=\"{{ 'Select or enter' | translate }}\"\n (onSearch)=\"patchDependencyInput$.next($event)\"\n [displayProperty]=\"'c8y_Firmware.version'\"\n [allowFreeEntries]=\"false\"\n [disabled]=\"\n (baseVersions$ | async) === null || (baseVersions$ | async)?.data.length === 0\n \"\n [required]=\"true\"\n >\n <c8y-li\n *c8yFor=\"\n let baseVersion of baseVersions$;\n loadMore: 'auto';\n pipe: baseVersionsFilterPipe\n \"\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n (click)=\"model.dependency = baseVersion\"\n [active]=\"model.dependency === baseVersion\"\n >\n <c8y-highlight\n [text]=\"baseVersion.c8y_Firmware.version || '--'\"\n [pattern]=\"patchDependencyInput$ | async\"\n ></c8y-highlight>\n </c8y-li>\n </c8y-typeahead>\n <c8y-messages>\n <c8y-message\n name=\"notExisting\"\n [text]=\"'Select one of the existing versions.' | translate\"\n ></c8y-message>\n </c8y-messages>\n </c8y-form-group>\n\n <c8y-form-group>\n <label for=\"patchVersion\" translate>Patch</label>\n <input\n id=\"patchVersion\"\n class=\"form-control\"\n autocomplete=\"off\"\n name=\"patchVersion\"\n data-cy=\"add-firmware-patch-modal--patchVersion\"\n [(ngModel)]=\"model.patchVersion\"\n placeholder=\"{{ 'e.g.' | translate }} 1.0.0\"\n required\n />\n </c8y-form-group>\n\n <c8y-form-group>\n <div class=\"legend form-block m-t-40\" translate>Patch file</div>\n <c8y-file-picker\n [maxAllowedFiles]=\"1\"\n (onFilesPicked)=\"onFile($event)\"\n [fileUrlPopover]=\"textForFirmwareUrlPopover\"\n ></c8y-file-picker>\n </c8y-form-group>\n </div>\n </div>\n <div class=\"modal-footer\">\n <button\n title=\"{{ 'Cancel' | translate }}\"\n data-cy=\"add-firmware-patch-modal--cancel-btn\"\n class=\"btn btn-default\"\n type=\"button\"\n (click)=\"cancel()\"\n [disabled]=\"saving\"\n translate\n >\n Cancel\n </button>\n <button\n title=\"{{ 'Add firmware patch' | translate }}\"\n class=\"btn btn-primary\"\n type=\"submit\"\n [ngClass]=\"{ 'btn-pending': saving }\"\n [disabled]=\"\n !firmwarePatchForm.form.valid ||\n firmwarePatchForm.form.pristine ||\n (!model.binary?.url && !model.binary?.file) ||\n saving\n \"\n translate\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.FIRMWARE.EVENTS.REPOSITORY\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.FIRMWARE.COMPONENTS.ADD_FIRMWAR_PATCH_MODAL,\n result: PRODUCT_EXPERIENCE.FIRMWARE.RESULTS.ADD_FIRMWARE_PATCH\n }\"\n >\n Add firmware patch\n </button>\n </div>\n </form>\n</div>\n" }]
}], ctorParameters: () => [{ type: i1$1.BsModalRef }, { type: i2.RepositoryService }, { type: i3$1.AlertService }], propDecorators: { saved: [{
type: Output
}], dropdown: [{
type: ViewChild,
args: ['dropdown', { static: false }]
}], form: [{
type: ViewChild,
args: ['firmwarePatchForm', { static: false }]
}] } });
class AddFirmwareModalComponent {
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,
binary: {
file: undefined,
url: undefined
}
};
this.saving = false;
this.firmwarePreselected = false;
this.textForFirmwareUrlPopover = gettext(`Path for binaries can vary depending on device agent implementation, for example:
/firmware/binaries/firmware1.bin
https://firmware/binary/123
ftp://firmware/binary/123.tar.gz
`);
this.ValidationPattern = ValidationPattern;
}
ngOnInit() {
this.setInitialState();
this.loadFirmwares();
}
setInitialState() {
if (this.model.selected) {
this.firmwarePreselected = true;
}
}
loadFirmwares() {
this.inputSubscription$ = this.onInput
.pipe(tap(() => {
if (!this.firmwarePreselected) {
this.model.description = null;
if (this.form) {
this.form.form.get('description').reset();
}
}
}), debounceTime(300), distinctUntilChanged(), switchMap(searchStr => this.getFirmwareResult(searchStr)))
.subscribe(result => {
this.firmwaresResult = result;
});
}
getFirmwareResult(searchStr) {
return from(this.repositoryService.listRepositoryEntries(RepositoryType.FIRMWARE, {
partialName: searchStr,
skipLegacy: true
}));
}
async save() {
this.saving = true;
this.repositoryService
.create(this.model, RepositoryType.FIRMWARE)
.then(savedFirmware => {
this.successMsg();
this.saving = false;
this.saved.next(savedFirmware);
this.cancel();
})
.catch(e => {
this.saving = false;
this.saved.error(e);
this.cancel();
});
}
successMsg() {
const msg = gettext('Firmware 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: undefine