@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
1 lines • 165 kB
Source Map (JSON)
{"version":3,"file":"c8y-ngx-components-repository-shared.mjs","sources":["../../repository/shared/columns/description.grid-column.ts","../../repository/shared/columns/device-type.cell-renderer.component.ts","../../repository/shared/columns/device-type.cell-renderer.component.html","../../repository/shared/columns/device-type.grid-column.ts","../../repository/shared/repository.model.ts","../../repository/shared/repository.service.ts","../../repository/shared/columns/file.cell-renderer.component.ts","../../repository/shared/columns/file.cell-renderer.component.html","../../repository/shared/columns/file.grid-column.ts","../../repository/shared/columns/name.cell-renderer.component.ts","../../repository/shared/columns/name.grid-column.ts","../../repository/shared/columns/type.cell-renderer.component.ts","../../repository/shared/columns/type.cell-renderer.component.html","../../repository/shared/columns/type.filtering-form-renderer.component.ts","../../repository/shared/columns/type.filtering-form-renderer.component.html","../../repository/shared/columns/type.grid-column.ts","../../repository/shared/columns/versions.cell-renderer.component.ts","../../repository/shared/columns/versions.cell-renderer.component.html","../../repository/shared/columns/versions.grid-column.ts","../../repository/shared/file-download/link-render-type.enum.ts","../../repository/shared/file-download/file-download.component.ts","../../repository/shared/file-download/file-download.component.html","../../repository/shared/select-modal/repository-select-modal.component.ts","../../repository/shared/select-modal/repository-select-modal.component.html","../../repository/shared/software-type/software-type.component.ts","../../repository/shared/software-type/software-type.component.html","../../repository/shared/shared-repository.module.ts","../../repository/shared/c8y-ngx-components-repository-shared.ts"],"sourcesContent":["import {\n BaseColumn,\n ColumnConfig,\n getBasicInputArrayFormFieldConfig,\n gettext\n} from '@c8y/ngx-components';\n\nexport class DescriptionGridColumn extends BaseColumn {\n constructor(initialColumnConfig?: ColumnConfig & { filterLabel?: string; placeholder?: string }) {\n super(initialColumnConfig);\n this.name = 'description';\n this.path = 'description';\n this.header = gettext('Description');\n\n this.filterable = true;\n this.filteringConfig = {\n fields: getBasicInputArrayFormFieldConfig({\n key: 'descriptions',\n label: initialColumnConfig?.filterLabel ?? gettext('Filter items by description'),\n addText: gettext('Add next`description`'),\n tooltip: gettext('Use * as a wildcard character'),\n placeholder: initialColumnConfig?.placeholder ?? gettext('Description…')\n }),\n getFilter(model: any): any {\n const filter: any = {};\n if (model.descriptions.length) {\n filter.description = { __in: model.descriptions };\n }\n return filter;\n }\n };\n\n this.sortable = true;\n this.sortingConfig = {\n pathSortingConfigs: [{ path: this.path }]\n };\n }\n}\n","import { Component, OnInit } from '@angular/core';\nimport { RouterModule } from '@angular/router';\nimport { CellRendererContext, CommonModule } from '@c8y/ngx-components';\nimport { DeviceGridModule } from '@c8y/ngx-components/device-grid';\nimport { get } from 'lodash-es';\nimport { TooltipModule } from 'ngx-bootstrap/tooltip';\n\n@Component({\n templateUrl: './device-type.cell-renderer.component.html',\n selector: 'c8y-device-type-cell-renderer',\n standalone: true,\n imports: [CommonModule, DeviceGridModule, TooltipModule, RouterModule]\n})\nexport class DeviceTypeCellRendererComponent implements OnInit {\n deviceType: string;\n\n constructor(public context: CellRendererContext) {}\n\n ngOnInit(): void {\n this.deviceType = get(this.context?.item, this.context?.property?.path);\n }\n}\n","<span *ngIf=\"deviceType; else emptyText\">\n {{ deviceType }}\n</span>\n<ng-template #emptyText>\n <small class=\"text-muted\">\n <em translate>Undefined`device type`</em>\n </small>\n</ng-template>\n","import {\n BaseColumn,\n ColumnConfig,\n getBasicInputArrayFormFieldConfig,\n gettext\n} from '@c8y/ngx-components';\nimport { DeviceTypeCellRendererComponent } from './device-type.cell-renderer.component';\n\nexport class DeviceTypeGridColumn extends BaseColumn {\n constructor(\n initialColumnConfig?: ColumnConfig & {\n filterLabel?: string;\n placeholder?: string;\n path?: string;\n }\n ) {\n super(initialColumnConfig);\n this.name = 'deviceType';\n this.path = initialColumnConfig?.path ?? 'c8y_Filter.type';\n this.header = gettext('Device type');\n this.cellRendererComponent = DeviceTypeCellRendererComponent;\n\n this.filterable = true;\n\n this.filteringConfig = {\n fields: [\n ...getBasicInputArrayFormFieldConfig({\n key: 'types',\n label: initialColumnConfig?.filterLabel ?? gettext('Filter items by device type'),\n addText: gettext('Add next`type`'),\n tooltip: gettext('Use * as a wildcard character'),\n placeholder: initialColumnConfig?.placeholder ?? 'c8y_Linux',\n optional: true\n }),\n {\n key: 'noDeviceType',\n type: 'switch',\n templateOptions: {\n label: gettext('No device type')\n }\n }\n ],\n getFilter(model: any): any {\n const filter: any = { __or: {} };\n if (model.types?.length) {\n filter.__or = {\n 'c8y_Filter.type': { __in: model.types }\n };\n }\n if (model.noDeviceType) {\n filter.__or = {\n ...filter.__or,\n __or: {\n __not: { __has: 'c8y_Filter.type' },\n 'c8y_Filter.type': ''\n }\n };\n }\n return filter;\n }\n };\n\n this.sortable = true;\n this.sortingConfig = {\n pathSortingConfigs: [{ path: this.path }]\n };\n }\n}\n","import { IManagedObject } from '@c8y/client';\nimport { SupportedIconsSuggestions } from '@c8y/ngx-components/icon-selector/icons';\n\nexport enum RepositoryType {\n FIRMWARE = 'c8y_Firmware',\n SOFTWARE = 'c8y_Software',\n CONFIGURATION = 'c8y_ConfigurationDump',\n PROFILE = 'c8y_Profile'\n}\n\nexport const REPOSITORY_BINARY_TYPES = {\n [RepositoryType.SOFTWARE]: 'c8y_SoftwareBinary',\n [RepositoryType.FIRMWARE]: 'c8y_FirmwareBinary',\n [RepositoryType.CONFIGURATION]: 'c8y_ConfigurationDumpBinary'\n};\n\nexport interface ModalModel {\n selected?: { id?: string; name?: string; [key: string]: any };\n version?: string;\n dependency?: { c8y_Firmware: { version: string } };\n patchVersion?: string;\n description?: string;\n deviceType?: string;\n softwareType?;\n configurationType?: string;\n binary?: {\n file?: File;\n url?: string;\n };\n c8y_Global?;\n}\n\nexport interface RepositoryCategory extends Partial<IManagedObject> {\n type: string;\n description?: string;\n c8y_Filter?: {\n type: string;\n };\n softwareType?: string;\n}\n\nexport interface FirmwareBinary extends Partial<IManagedObject> {\n type: 'c8y_FirmwareBinary';\n c8y_Firmware: {\n version: string;\n url: string;\n };\n}\n\nexport interface FirmwarePatchBinary extends FirmwareBinary {\n c8y_Patch: {\n dependency: string;\n };\n}\n\nexport interface SoftwareBinary extends Partial<IManagedObject> {\n type: string;\n c8y_Software: {\n version: string;\n url: string;\n };\n}\n\nexport type RepositoryBinary = FirmwareBinary | SoftwareBinary | FirmwarePatchBinary;\n\nexport interface SelectedRepositoryBinary {\n id?: string;\n name: string;\n version: string;\n url: string;\n dependency?: string;\n c8y_Patch?: boolean;\n isPatch?: boolean;\n patchDependency?: string;\n softwareType?: string;\n}\n\nexport interface DeviceFirmware {\n version: string;\n url: string;\n name: string;\n}\n\nexport interface DeviceSoftware {\n name: string;\n version?: string;\n url: string;\n softwareType?: string;\n}\n\nexport interface DeviceSoftwareChange extends DeviceSoftware {\n action: 'install' | 'delete';\n}\n\nexport interface ConfigurationSnapshot {\n id?: string;\n time: string;\n name: string;\n binaryUrl?: string;\n binary?: any;\n binaryType?: any;\n deviceType?: string;\n configurationType?: string;\n description?: string;\n}\n\nexport interface SupportedConfigurationItem {\n name: string;\n deviceType?: string;\n configurationType?: string;\n isLegacy?: boolean;\n}\n\nexport interface DeviceConfigurationListEmptyState {\n icon?: SupportedIconsSuggestions;\n title?: string;\n text?: string;\n}\n\nexport enum DeviceConfigurationOperation {\n UPLOAD_CONFIG = 'c8y_UploadConfigFile',\n DOWNLOAD_CONFIG = 'c8y_DownloadConfigFile',\n CONFIG = 'c8y_Configuration',\n SEND_CONFIG = 'c8y_SendConfiguration'\n}\nexport interface FilterCriteria {\n name?: string;\n [key: string]: any;\n}\n\nexport const PRODUCT_EXPERIENCE_REPOSITORY_SHARED = {\n SOFTWARE: {\n EVENTS: {\n REPOSITORY: 'softwareRepository',\n DEVICE_TAB: 'deviceSoftware'\n },\n COMPONENTS: {\n ADD_SOFTWARE_MODAL: 'add-software-modal',\n DEVICE_SOFTWARE_CHANGES: 'device-software-changes',\n DEVICE_SOFTWARE_LIST: 'device-software-list'\n },\n ACTIONS: {\n APPLY_SOFTWARE_CHANGES: 'applySoftwareChanges',\n CLEAR_SOFTWARE_CHANGES: 'clearSoftwareChanges',\n OPEN_INSTALL_SOFTWARE: 'openInstallSoftwareModal',\n OPEN_UPDATE_SOFTWARE: 'openUpdateSoftwareModal',\n DELETE_SOFTWARE: 'deleteSoftware'\n },\n RESULTS: {\n ADD_SOFTWARE: 'addSoftware',\n ADD_SOFTWARE_VERSION: 'addSoftwareVersion',\n EDIT_SOFTWARE: 'editSoftware'\n }\n },\n FIRMWARE: {\n EVENTS: {\n REPOSITORY: 'firmwareRepository',\n DEVICE_TAB: 'deviceFirmware'\n },\n COMPONENTS: {\n ADD_FIRMWARE_MODAL: 'add-firmware-modal',\n ADD_FIRMWAR_PATCH_MODAL: 'add-firmware-patch-modal',\n FIRMWARE_DEVICE_TAB: 'firmware-device-tab',\n DEVICE_FIRMWARE_LIST: 'device-firmware-list'\n },\n ACTIONS: {\n OPEN_INSTALL_FIRMWARE_DIALOG: 'openInstallFirmwareDialog',\n OPEN_REPLACE_FIRMWARE_DIALOG: 'openReplaceFirmwareDialog',\n OPEN_INSTALL_FIRMWARE_PATCH_DIALOG: 'openInstallFirmwarePatchDialog'\n },\n RESULTS: {\n ADD_FIRMWARE: 'addFirmware',\n ADD_FIRMWARE_VERSION: 'addFirmwareVersion',\n ADD_FIRMWARE_PATCH: 'addFirmwarePatch',\n EDIT_FIRMWARE: 'editFirmware',\n CREATE_FIRMWARE_UPDATE_OPERATION: 'createFirmwareUpdateOperation'\n }\n },\n SHARED: {\n COMPONENTS: {\n REPOSITORY_SELECT_MODAL: 'repository-select-modal',\n SELECT_CONFIGURATION_MODAL: 'select-configuration-modal'\n }\n }\n} as const;\n","import { Injectable } from '@angular/core';\nimport {\n EventBinaryService,\n EventService,\n IdReference,\n IEvent,\n IFetchResponse,\n IIdentified,\n IManagedObject,\n IManagedObjectBinary,\n InventoryBinaryService,\n InventoryService,\n IOperation,\n IResult,\n IResultList,\n OperationService,\n OperationStatus,\n QueriesUtil\n} from '@c8y/client';\nimport {\n AlertService,\n gettext,\n GlobalConfigService,\n OperationRealtimeService,\n ServiceRegistry\n} from '@c8y/ngx-components';\nimport {\n map as _map,\n assign,\n cloneDeep,\n find,\n forEach,\n get,\n head,\n isNil,\n isString,\n isUndefined,\n omitBy,\n pick,\n remove,\n set\n} from 'lodash-es';\nimport { defer, from, merge, Observable, of, throwError } from 'rxjs';\nimport { filter, map, switchMap, take, takeWhile, withLatestFrom } from 'rxjs/operators';\nimport { IAdvancedSoftwareService } from './advanced-software-management.model';\nimport {\n ConfigurationSnapshot,\n DeviceFirmware,\n DeviceSoftware,\n DeviceSoftwareChange,\n FirmwareBinary,\n FirmwarePatchBinary,\n ModalModel,\n REPOSITORY_BINARY_TYPES,\n RepositoryBinary,\n RepositoryCategory,\n RepositoryType,\n SelectedRepositoryBinary,\n SoftwareBinary\n} from './repository.model';\n\n@Injectable()\nexport class RepositoryService {\n readonly dateFrom = new Date(0);\n readonly dateTo = new Date(Date.now() + 86400000); // 1 day in the future\n private queriesUtil: QueriesUtil;\n private advancedSoftwareService: IAdvancedSoftwareService | undefined;\n\n constructor(\n private inventory: InventoryService,\n private inventoryBinary: InventoryBinaryService,\n private operation: OperationService,\n private alert: AlertService,\n private event: EventService,\n private operationRealtime: OperationRealtimeService,\n private eventBinary: EventBinaryService,\n private serviceRegistry: ServiceRegistry,\n private globalConfigService: GlobalConfigService\n ) {\n this.queriesUtil = new QueriesUtil();\n this.advancedSoftwareService = head(this.serviceRegistry.get('asm'));\n }\n\n /**\n * Lists repository entries of given type.\n * @param type The type of repository entries to list.\n * @param options Extra listing options.\n */\n listRepositoryEntries(\n type: RepositoryType,\n options?: {\n /** Additional query. */\n query?: any;\n /** (deprecated - to be removed) Only include entries with matching partial names. */\n partialName?: string;\n /** Include entries with matching partial text in the specified properties. */\n partialTextFilter?: { partialText: string; properties: string[] };\n /** Exclude legacy entries. */\n skipLegacy?: boolean;\n /** Exclude default ordering. */\n skipDefaultOrder?: boolean;\n /** Other request params. */\n params?: any;\n }\n ) {\n const defaultOrder = [{ name: 1 }];\n const defaultFilters = { type };\n const legacyFilters = { __has: `url` };\n let filters = {};\n\n let fullQuery = (options && options.query) || {};\n if (!options || (options && !options.skipDefaultOrder)) {\n fullQuery = this.queriesUtil.addOrderbys(fullQuery, defaultOrder, 'prepend');\n }\n\n fullQuery = this.queriesUtil.addAndFilter(fullQuery, defaultFilters);\n\n if (options && options.partialTextFilter) {\n const { partialText, properties } = options.partialTextFilter;\n const orFilter = { __or: properties.map(property => ({ [property]: `*${partialText}*` })) };\n fullQuery = this.queriesUtil.addAndFilter(fullQuery, orFilter);\n }\n\n if (options && options.partialName) {\n // backwards compatibility if\n fullQuery = this.queriesUtil.addAndFilter(fullQuery, { name: `*${options.partialName}*` });\n }\n\n if (options && options.skipLegacy) {\n fullQuery = this.queriesUtil.addAndFilter(fullQuery, { __not: legacyFilters });\n }\n\n filters = {\n query: this.queriesUtil.buildQuery(fullQuery),\n pageSize: 50,\n withTotalPages: true,\n ...((options && options.params) || {})\n };\n return this.inventory.list(filters);\n }\n\n async create(modal: ModalModel, type: RepositoryType, mo: Partial<IManagedObject> = {}) {\n switch (type) {\n case RepositoryType.FIRMWARE:\n case RepositoryType.SOFTWARE:\n return this.createRepositoryObject(modal, type);\n\n case RepositoryType.CONFIGURATION:\n Object.assign(modal, {\n selected: {\n id: mo.id,\n name: modal.version\n },\n configurationType: modal.configurationType,\n name: modal.version\n });\n\n if (!modal.deviceType && mo.id) {\n modal.deviceType = null;\n }\n if (!modal.selected && mo.id) {\n modal.configurationType = null;\n }\n\n const repositoryObject = this.createRepositoryObject(modal, type);\n\n if (mo.url) {\n const newBinaryUrl = (await repositoryObject).url;\n this.removeOutdatedBinary(newBinaryUrl, mo.url);\n }\n\n return repositoryObject;\n }\n }\n\n async createRepositoryObject(\n modal: ModalModel,\n type: RepositoryType\n ): Promise<RepositoryCategory> {\n let binary: IManagedObjectBinary;\n let binaryURL: string;\n let repositoryEntry: RepositoryCategory;\n let repositoryBinary: FirmwareBinary | SoftwareBinary;\n const mos = [];\n const {\n selected: { id: selectedId },\n binary: { file, url }\n } = modal;\n try {\n const globalParam = await this.getGlobalFragment(type);\n\n if (file) {\n ({ data: binary } = await this.saveBinary(file, globalParam));\n ({ self: binaryURL } = binary);\n if (type === RepositoryType.CONFIGURATION) {\n modal.binary.url = binaryURL;\n }\n mos.push(binary);\n } else {\n binaryURL = url;\n }\n\n ({ data: repositoryEntry } = await this.createOrUpdateRepositoryEntry(\n { ...modal, ...globalParam },\n type\n ));\n if (isNil(selectedId)) {\n mos.push(repositoryEntry);\n }\n\n if (type !== RepositoryType.CONFIGURATION) {\n ({ data: repositoryBinary } = await this.createRepositoryBinary(\n { ...modal, ...globalParam },\n binaryURL,\n type,\n repositoryEntry\n ));\n mos.push(repositoryBinary);\n }\n\n if (file) {\n await this.linkBinary(repositoryBinary, binary, repositoryEntry);\n }\n\n return repositoryEntry;\n } catch (error) {\n this.cleanUp(mos);\n this.errorMsg();\n\n // Propagate error\n throw error;\n }\n }\n\n saveBinary(file: File, global: Partial<IManagedObject>): Promise<IResult<IManagedObjectBinary>> {\n return this.inventoryBinary.create(file, global);\n }\n\n async createOrUpdateRepositoryEntry(\n modal: ModalModel,\n type: RepositoryType\n ): Promise<IResult<RepositoryCategory>> {\n const {\n selected: { id, name },\n description,\n deviceType,\n c8y_Global,\n binary\n } = modal;\n\n const mo = {\n id,\n name,\n description,\n type,\n c8y_Global\n };\n\n if (deviceType && type !== RepositoryType.CONFIGURATION) {\n set(mo, 'c8y_Filter.type', deviceType);\n }\n\n if ((deviceType || id) && type === RepositoryType.CONFIGURATION) {\n set(mo, 'deviceType', deviceType);\n }\n\n if (modal.softwareType) {\n set(mo, 'softwareType', modal.softwareType.softwareType);\n }\n\n if ((modal.configurationType || id) && type === RepositoryType.CONFIGURATION) {\n set(mo, 'configurationType', modal.configurationType);\n }\n\n if (type === RepositoryType.CONFIGURATION) {\n set(mo, 'url', binary?.url);\n }\n\n return id\n ? (this.inventory.update(mo) as Promise<IResult<RepositoryCategory>>)\n : (this.inventory.create(mo) as Promise<IResult<RepositoryCategory>>);\n }\n\n createRepositoryBinary(\n modal: ModalModel,\n binaryURL: string,\n type: RepositoryType,\n parent: RepositoryCategory\n ): Promise<IResult<FirmwareBinary | SoftwareBinary | FirmwarePatchBinary>> {\n const mo = this.prepareRepositoryBinaryMO(modal, binaryURL, type);\n\n return this.inventory.childAdditionsCreate(mo, parent) as Promise<\n IResult<FirmwareBinary | SoftwareBinary | FirmwarePatchBinary>\n >;\n }\n\n prepareRepositoryBinaryMO(modal: ModalModel, binaryURL: string, type: RepositoryType) {\n const { version, patchVersion, dependency, c8y_Global } = modal;\n const result = {\n type: REPOSITORY_BINARY_TYPES[type],\n [type]: {\n url: binaryURL\n },\n c8y_Global\n };\n\n if (dependency) {\n set(result, [type, 'version'], patchVersion);\n assign(result, {\n c8y_Patch: {\n dependency: dependency.c8y_Firmware.version\n }\n });\n } else {\n set(result, [type, 'version'], version);\n }\n return result;\n }\n\n async linkBinary(\n repositoryBinary,\n binary: IManagedObjectBinary,\n repositoryEntry?: RepositoryCategory\n ) {\n if (repositoryBinary) {\n const { id: repositoryBinaryId } = repositoryBinary;\n if (binary) {\n const { id: binaryId } = binary;\n return this.inventory.childAdditionsAdd(binaryId, repositoryBinaryId);\n }\n } else return this.inventory.childAdditionsAdd(binary, repositoryEntry);\n }\n\n cleanUp(mosToDelete: IIdentified[]) {\n mosToDelete.forEach(mo => {\n const { c8y_IsBinary } = mo;\n isUndefined(c8y_IsBinary) ? this.delete(mo) : this.inventoryBinary.delete(mo);\n });\n }\n\n delete(entity: IIdentified): Promise<IResult<null>> {\n return this.inventory.delete(entity, { forceCascade: true });\n }\n\n errorMsg() {\n const msg = gettext('Failed to save');\n this.alert.danger(msg);\n }\n\n getBaseVersionsCount$(entry: IManagedObject): Observable<number> {\n if (this.isLegacyEntry(entry)) {\n return of(1);\n }\n return from(this.listBaseVersions(entry, { withTotalElements: true })).pipe(\n map(({ paging }) => paging.totalElements)\n );\n }\n\n getBaseVersionFromMO(mo: RepositoryBinary): string {\n return this.isPatch(mo) ? get(mo, 'c8y_Patch.dependency') : get(mo, 'c8y_Firmware.version');\n }\n\n isPatch(mo: RepositoryBinary): boolean {\n return !!get(mo, 'c8y_Patch.dependency');\n }\n\n getPatchVersionsCount$(entry: IManagedObject, baseVersion: FirmwareBinary): Observable<number> {\n if (this.isLegacyEntry(baseVersion)) {\n return of(0);\n }\n return from(this.listPatchVersions(entry, baseVersion, { withTotalElements: true })).pipe(\n map(({ paging }) => paging.totalElements)\n );\n }\n\n isLegacyEntry(entry: Partial<IManagedObject>): boolean {\n return Boolean(entry.url);\n }\n\n /**\n * Lists all versions (base and patch ones) of given top level entry.\n * Versions are ordered by creation time (assuming the earlier created, the older the version).\n * @param entry Top level repository entry.\n * @param params Additional query params.\n */\n listAllVersions(entry: Partial<IManagedObject>, params = {}) {\n if (this.isLegacyEntry(entry)) {\n return this.getBaseVersionResultListForLegacyEntry(entry);\n }\n\n const VERSION_FILTER_ORDER = {\n __filter: {},\n __orderby: [{ 'creationTime.date': -1 }]\n };\n return this.listChildren(entry, VERSION_FILTER_ORDER, params);\n }\n\n /**\n * Lists base versions of given top level entry.\n * Versions are ordered by creation time (assuming the earlier created, the older the version).\n * @param entry Top level repository entry.\n * @param params Additional query params.\n */\n listBaseVersions(entry: Partial<IManagedObject>, params = {}) {\n if (this.isLegacyEntry(entry)) {\n return this.getBaseVersionResultListForLegacyEntry(entry);\n }\n\n const NO_PATCH_FILTER_ORDER = {\n __filter: {\n __not: { __has: 'c8y_Patch' }\n },\n __orderby: [{ 'creationTime.date': -1 }]\n };\n return this.listChildren(entry, NO_PATCH_FILTER_ORDER, params);\n }\n\n /**\n * Lists patch versions of given base version under the entry.\n * Versions are ordered by creation time (assuming the earlier created, the older the version).\n * @param entry Top level repository entry.\n * @param baseVersion Base version.\n * @param params Additional query params.\n */\n listPatchVersions(entry: IManagedObject, baseVersion: FirmwareBinary | string, params = {}) {\n const version = isString(baseVersion) ? baseVersion : get(baseVersion, 'c8y_Firmware.version');\n const PATCH_FILTER_ORDER = {\n __filter: {\n 'c8y_Patch.dependency': version\n },\n __orderby: [{ 'creationTime.date': -1 }]\n };\n return this.listChildren(entry, PATCH_FILTER_ORDER, params);\n }\n\n /**\n * Lists patch versions of given base version under the entry including the base version.\n * Versions are ordered by creation time (assuming the earlier created, the older the version).\n * In terms of legacy base version the entry gets transformed to fit the needed data model.\n * @param entry Top level repository entry.\n * @param baseVersion Base version.\n * @param params Additional query params.\n */\n listBaseVersionAndPatches(entry: IManagedObject, baseVersion: IManagedObject, params = {}) {\n if (this.isLegacyEntry(entry)) {\n return Promise.resolve({\n data: [\n Object.assign(\n {\n c8y_Firmware: {\n version: entry.version,\n url: entry.url\n }\n },\n entry\n )\n ]\n });\n }\n\n const PATCH_FILTER_ORDER = {\n __filter: {\n __or: {\n 'c8y_Patch.dependency': baseVersion.c8y_Firmware.version,\n 'c8y_Firmware.version': baseVersion.c8y_Firmware.version\n }\n },\n __orderby: [{ 'c8y_Patch.dependency': 1 }, { 'c8y_Firmware.version': 1 }]\n };\n return this.listChildren(entry, PATCH_FILTER_ORDER, params);\n }\n\n listChildren(entry: Partial<IManagedObject>, filters = {}, params: any = {}) {\n const childrenFilters = { __bygroupid: entry.id };\n const query = this.queriesUtil.addAndFilter(filters, childrenFilters);\n // FIXME: needed because of issue in forOf directive (...)\n params.withTotalPages = true;\n return this.inventory.listQuery(query, params);\n }\n\n /**\n * Fetches all items from the list starting with the provided page.\n * @param firstPage The first page of the list to fetch all items for.\n */\n async fetchAllItemsFromList(firstPage) {\n let allItems;\n\n if (!firstPage.then) {\n allItems = [...firstPage];\n } else {\n let { paging, data: items } = await firstPage;\n allItems = [...items];\n\n while (paging && paging.nextPage) {\n ({ paging, data: items } = await paging.next());\n allItems = [...allItems, ...items];\n }\n }\n\n return allItems;\n }\n\n /**\n * Gets top level repository entry managed object for base or patch version.\n * @param mo Base or patch version managed object with parents.\n */\n getRepositoryEntryMO$(mo: IManagedObject): Observable<IManagedObject | undefined> {\n if (!mo) {\n return of(undefined);\n }\n const [reference] = get(mo, 'additionParents.references');\n const id = get(reference, 'managedObject.id');\n return id\n ? from(this.inventory.detail(id, { withChildren: false })).pipe(map(({ data }) => data))\n : of(undefined);\n }\n /**\n * Gets base or patch version managed object.\n * @param deviceRepositoryFragment Device repository fragment.\n * @param type Top level repository entry type.\n * @param configuration Configuration object with options:\n * - **skipLegacy** - `boolean` - Exclude legacy entries.\n * - **filters** - `object` - Filter object.\n *\n * @deprecated as it doesn't support 'missing url' case\n */\n getRepositoryBinaryMoByVersion(\n deviceRepositoryFragment: DeviceFirmware | DeviceSoftware,\n type: RepositoryType,\n { skipLegacy = false, filters = {} }: { skipLegacy?: boolean; filters?: object } = {}\n ): Promise<IManagedObject> {\n const { version, url, name } = deviceRepositoryFragment;\n const repositoryBinaryType = REPOSITORY_BINARY_TYPES[type];\n let query;\n const newModelBaseVersionQuery = {\n [`${type}.version`]: version,\n [`${type}.url`]: url,\n type: repositoryBinaryType\n };\n const legacyVersionQuery = { url, type, name };\n filters = { withChildren: false, withParents: true, ...filters };\n\n if (skipLegacy) {\n query = {\n __and: {\n ...newModelBaseVersionQuery\n }\n };\n } else {\n query = {\n __or: [{ __and: { ...newModelBaseVersionQuery } }, { __and: { ...legacyVersionQuery } }]\n };\n }\n\n return this.inventory.listQuery(query, filters).then(({ data }) => head(data));\n }\n\n getBinaryName$(binaryUrl: string): Observable<string> {\n if (!binaryUrl) {\n return of('---');\n }\n\n const binaryId = this.inventoryBinary.getIdFromUrl(binaryUrl);\n if (!binaryId) {\n return of(binaryUrl);\n }\n return defer(() => this.inventory.detail(binaryId).then(result => result.data)).pipe(\n map(mo => mo.name)\n );\n }\n\n /**\n * Generates an inventory query object which can be used to find\n * repository entries of specified type matching the type of provided device.\n * @param repositoryType The type of repository entries which will be queried with the generated query.\n * @param device The device for which matching repository entries will be queried with the generated query.\n */\n\n getDeviceTypeQuery(repositoryType: RepositoryType, device: IManagedObject): object {\n let result = { type: repositoryType };\n if (repositoryType === RepositoryType.CONFIGURATION) {\n if (device.type) {\n result = this.queriesUtil.addAndFilter(result, {\n __or: [{ deviceType: device.type }, { __not: { __has: `deviceType` } }]\n });\n }\n } else {\n result = this.queriesUtil.addAndFilter(result, {\n __or: [\n { 'c8y_Filter.type': device.type },\n { 'c8y_Filter.type': '' },\n { __not: { __has: `c8y_Filter.type` } }\n ]\n });\n }\n\n return result;\n }\n\n /**\n * Generates an inventory query object which can be used to find\n * repository entries matching the predefined software types provided in the device.\n * @param device The device for which matching repository entries will be queried with the generated query.\n * @param query The query to which the software types filters will be attached. Default value is an object containg repository type software.\n */\n getSoftwareTypeQuery(device: IManagedObject, query?: object): object {\n let result = { ...(query || {}), type: RepositoryType.SOFTWARE };\n\n if (device.c8y_SupportedSoftwareTypes) {\n result = this.queriesUtil.addAndFilter(result, {\n __or: [device.c8y_SupportedSoftwareTypes.map(type => ({ softwareType: type }))]\n });\n }\n\n return result;\n }\n\n /**\n * Generates an inventory query object which can be used to find configuration repository entries\n * matching the type of provided device and specified configuration type.\n * @param device The device for which matching repository entries will be queried with the generated query.\n * @param configurationType Configuration type for which matching repository entries will be queried with the generated query.\n */\n getConfigurationTypeQuery(device: IManagedObject, configurationType: string): object {\n const query = this.getDeviceTypeQuery(RepositoryType.CONFIGURATION, device);\n return this.queriesUtil.addAndFilter(query, {\n __or: [\n { configurationType },\n { configurationType: '' },\n { __not: { __has: `configurationType` } }\n ]\n });\n }\n\n /**\n * Gets the list of software installed in the device in the uniform format.\n * Supports c8y_SoftwareList and c8y_Software fragments.\n * @param device The device whose software list should be returned.\n */\n getDeviceSoftwareList(device: IManagedObject): DeviceSoftware[] {\n if (device.c8y_SoftwareList) {\n return cloneDeep(device.c8y_SoftwareList);\n }\n if (device.c8y_Software) {\n return _map(device.c8y_Software, (version, name) => ({ name, version }));\n }\n return [];\n }\n\n /**\n * Prepares a software update operation for given device and the list of changes, and sends it to the device.\n * @param device The device which the operation should be prepared for and sent to.\n * @param changes The list of software changes which should be applied.\n */\n async createSoftwareUpdateOperation(\n device: IManagedObject,\n changes: DeviceSoftwareChange[]\n ): Promise<IOperation> {\n const operation = await this.getSoftwareUpdateOperation(device, changes);\n return (await this.operation.create(operation)).data;\n }\n\n /**\n * Prepares a software update operation for given device and changes.\n * Returned operation type depends on device's supported operations.\n * Supports c8y_SoftwareUpdate, c8y_SoftwareList, and c8y_Software operations.\n * @param device The device for which operation should be prepared.\n * @param changes The list of software changes which should be applied.\n */\n async getSoftwareUpdateOperation(\n device: IManagedObject,\n changes: DeviceSoftwareChange[]\n ): Promise<IOperation> {\n const operation: IOperation = {\n deviceId: device.id,\n description: `Apply software changes: ${changes\n .map(\n change =>\n `${change.action} \"${change.name}\"${\n change.version ? ` (version: ${change.version})` : ''\n }`\n )\n .join(', ')}`\n };\n if (device.c8y_SupportedOperations.includes('c8y_SoftwareUpdate')) {\n operation.c8y_SoftwareUpdate = (cloneDeep(changes) || []).map(change =>\n omitBy(change, isNil)\n );\n } else if (device.c8y_SupportedOperations.includes('c8y_SoftwareList')) {\n operation.c8y_SoftwareList = cloneDeep(\n await this.getCurrentSoftware(device, 'c8y_SoftwareList', [])\n );\n changes.forEach(change => {\n const deviceSoftware: DeviceSoftware = pick(omitBy(change, isNil), [\n 'name',\n 'version',\n 'url',\n 'softwareType'\n ]);\n if (change.action === 'delete') {\n remove(operation.c8y_SoftwareList, deviceSoftware);\n }\n if (change.action === 'install') {\n const softwareItemToUpdateIdx = operation.c8y_SoftwareList.findIndex(\n item => item.name === change.name\n );\n if (softwareItemToUpdateIdx > -1) {\n // update software\n operation.c8y_SoftwareList.splice(softwareItemToUpdateIdx, 1, deviceSoftware);\n } else {\n // install software\n operation.c8y_SoftwareList.push(deviceSoftware);\n }\n }\n });\n } else if (device.c8y_SupportedOperations.includes('c8y_Software')) {\n operation.c8y_Software = cloneDeep(await this.getCurrentSoftware(device, 'c8y_Software', {}));\n changes.forEach(change => {\n if (change.action === 'delete') {\n delete operation.c8y_Software[change.name];\n }\n if (change.action === 'install') {\n operation.c8y_Software[change.name] = change.version;\n }\n });\n }\n return operation;\n }\n\n /**\n * Extracts the list of device software changes from given operation in the context of given device.\n * @param operation The operation from which the list should be extracted.\n * @param device The target device of the operation.\n */\n async getDeviceSoftwareChangesFromOperation(\n operation: IOperation,\n device: IManagedObject\n ): Promise<DeviceSoftwareChange[]> {\n if (operation.c8y_SoftwareUpdate) {\n return cloneDeep(operation.c8y_SoftwareUpdate);\n }\n if (operation.c8y_SoftwareList) {\n return await this.getDeviceSoftwareChangesFromSoftwareListOperation(operation, device);\n }\n if (operation.c8y_Software) {\n return await this.getDeviceSoftwareChangesFromSoftwareOperation(operation, device);\n }\n return [];\n }\n\n /**\n * Prepares a firmware update operation for given device and the selected repository binary, and sends it to the device.\n * @param device The device which the operation should be prepared for and sent to.\n * @param selectedOption The selected repository binary option.\n */\n async createFirmwareUpdateOperation(\n device: IManagedObject,\n selectedOption: SelectedRepositoryBinary\n ): Promise<IOperation> {\n const operation = this.getFirmwareUpdateOperation(device, selectedOption);\n return (await this.operation.create(operation)).data;\n }\n\n /**\n * Prepares a firmware update operation for given device and selected version.\n * Supports c8y_Firmware operation.\n * @param device The device for which operation should be prepared.\n * @param selectedOption Selected firmware version.\n */\n getFirmwareUpdateOperation(\n device: IManagedObject,\n selectedOption: SelectedRepositoryBinary\n ): IOperation {\n delete selectedOption.id;\n\n const operation: IOperation = {\n deviceId: device.id,\n description: `Update firmware to: \"${selectedOption.name}\"${\n selectedOption.version ? ` (version: ${selectedOption.version})` : ''\n }`,\n c8y_Firmware: { ...selectedOption }\n };\n\n return operation;\n }\n\n /**\n * Prepares a configuration file upload operation for given device and configuration type.\n * @param device The device for which operation should be prepared.\n * @param configurationType Selected configuration type.\n * @param isLegacy A legacy operation is created without a configurationType.\n */\n getUploadConfigurationFileOperation(\n device: IManagedObject,\n configurationType: string,\n isLegacy = false\n ): IOperation {\n if (isLegacy) {\n return {\n deviceId: device.id,\n description: `Retrieve configuration snapshot from device ${device.name}`,\n c8y_UploadConfigFile: {}\n };\n }\n return {\n deviceId: device.id,\n description: `Retrieve ${configurationType} configuration snapshot from device ${device.name}`,\n c8y_UploadConfigFile: {\n type: configurationType\n }\n };\n }\n\n /**\n * Prepares a configuration file download operation for given device and configuration type.\n * @param device The device for which operation should be prepared.\n * @param configurationType Selected configuration type.\n * @param binaryUrl The url of a binary to be downloaded.\n * @param isLegacy A legacy operation is created without a configurationType.\n */\n getDownloadConfigurationFileOperation(\n device: IManagedObject,\n configurationType: string,\n configSnapshot: ConfigurationSnapshot,\n isLegacy = false\n ): IOperation {\n if (isLegacy) {\n return {\n deviceId: device.id,\n description: `Send configuration snapshot ${configSnapshot.name} to device ${device.name}`,\n c8y_DownloadConfigFile: {\n url: configSnapshot.binaryUrl,\n c8y_ConfigurationDump: {\n id: configSnapshot.id\n }\n }\n };\n }\n return {\n deviceId: device.id,\n description: `Send configuration snapshot ${configSnapshot.name} of configuration type ${configurationType} to device ${device.name}`,\n c8y_DownloadConfigFile: {\n url: configSnapshot.binaryUrl,\n type: configurationType\n }\n };\n }\n\n /**\n * Gets the last firmware update operation for given device.\n * Looks for c8y_Firmware operations.\n * @param deviceId The ID of the device to find an operation for.\n */\n async getLastFirmwareUpdateOperation(deviceId: string | number): Promise<IOperation> {\n const filters = {\n deviceId,\n dateFrom: new Date(0).toISOString(),\n dateTo: new Date(Date.now()).toISOString(),\n revert: true,\n pageSize: 1\n };\n return this.getFirstMatchingOperation([{ ...filters, fragmentType: 'c8y_Firmware' }]);\n }\n\n /**\n * Gets the last software update operation for given device.\n * Looks for c8y_SoftwareUpdate, c8y_SoftwareList, or c8y_Software operations.\n * @param deviceId The ID of the device to find an operation for.\n */\n async getLastSoftwareUpdateOperation(deviceId: string | number): Promise<IOperation> {\n const filters = {\n deviceId,\n dateFrom: new Date(0).toISOString(),\n dateTo: new Date(Date.now()).toISOString(),\n revert: true,\n pageSize: 1\n };\n return this.getLatestMatchingOperation([\n { ...filters, fragmentType: 'c8y_SoftwareUpdate' },\n { ...filters, fragmentType: 'c8y_SoftwareList' },\n { ...filters, fragmentType: 'c8y_Software' }\n ]);\n }\n\n /**\n * Iterates over the list of filters and queries the operations.\n * If a query returns at least one operation, the first one will be returned.\n * Otherwise the next query will be performed.\n * If none of the queries returns any operation, null will be returned.\n * @param filtersList The list of filters for the queries.\n */\n async getFirstMatchingOperation(filtersList: any[]): Promise<IOperation> {\n let matchingOperation = null;\n\n for (const filters of filtersList) {\n const operations = (await this.operation.list(filters)).data;\n if (operations.length) {\n matchingOperation = operations[0];\n break;\n }\n }\n\n return matchingOperation;\n }\n\n /**\n * Iterates over the list of filters and queries the operations.\n * It compares the operations retrieved by the queries by 'creationTime'\n * and return the latest one.\n * If none of the queries returns any operation, null will be returned.\n * @param filtersList The list of filters for the queries.\n */\n async getLatestMatchingOperation(filtersList: any[]): Promise<IOperation> {\n let matchingOperation: IOperation = null;\n\n for (const filters of filtersList) {\n const operations: IOperation[] = (await this.operation.list(filters)).data;\n if (operations.length) {\n if (matchingOperation) {\n matchingOperation =\n new Date(matchingOperation.creationTime).getTime() <\n new Date(operations[0].creationTime).getTime()\n ? operations[0]\n : matchingOperation;\n } else {\n matchingOperation = operations[0];\n }\n }\n }\n\n return matchingOperation;\n }\n\n /**\n * Creates the operation and returns an observable to track its progress.\n * Fails the observable when the operation returns FAILED status.\n * Completes the observable when the operation returns SUCCESSFUL status.\n * @param operation The operation to create and track.\n */\n createObservedOperation(operation: IOperation): Observable<IOperation> {\n return from(this.operation.create(operation)).pipe(\n map(({ data }) => data),\n take(1),\n switchMap(createdOperation => this.observeOperation(createdOperation))\n );\n }\n\n /**\n * Returns an observable to track progress of given operation.\n * Fails the observable when the operation returns FAILED status.\n * Completes the observable when the operation returns SUCCESSFUL status.\n * @param operation The operation to be observed.\n */\n observeOperation(operation: IOperation): Observable<IOperation> {\n const observedOperation$ = of(operation);\n const operationUpdates$ = observedOperation$.pipe(\n switchMap(observedOperation => this.operationRealtime.onAll$(observedOperation.deviceId)),\n map(({ data }) => data as IOperation),\n withLatestFrom(observedOperation$),\n filter(([operationUpdate, observedOperation]) => operationUpdate.id === observedOperation.id),\n switchMap(([operationUpdate]) => {\n if (operationUpdate.status === OperationStatus.FAILED) {\n return throwError(operationUpdate);\n }\n return of(operationUpdate);\n }),\n takeWhile(operationUpdate => operationUpdate.status !== OperationStatus.SUCCESSFUL, true)\n );\n return merge(observedOperation$, operationUpdates$);\n }\n\n /**\n * Gets a single event with latest creationTime for the given device Id and event type.\n * @param deviceId The device Id for which the events should be queried.\n * @param type Event type.\n */\n async getLatestConfigurationEvent(\n deviceId: string | number,\n type: string\n ): Promise<IEvent | undefined> {\n const eventFilter: object = {\n source: deviceId,\n type,\n dateFrom: this.dateFrom.toISOString(),\n dateTo: this.dateTo.toISOString(),\n pageSize: 1\n };\n\n const { data } = await this.event.list(eventFilter);\n return data[0];\n }\n\n /**\n * Gets a list of operations for the given device Id, and operation type.\n * @param deviceId The device Id for which the operation should be queried.\n * @param operationType Operation type fragment.\n */\n async getConfigFileOperationList(\n deviceId: string | number,\n operationType: string\n ): Promise<IOperation[]> {\n const operationFilter: object = {\n deviceId,\n fragmentType: operationType,\n dateFrom: this.dateFrom.toISOString(),\n dateTo: this.dateTo.toISOString(),\n revert: true,\n pageSize: 2000\n };\n\n return (await this.operation.list(operationFilter)).data;\n }\n\n /**\n * Gets latest uploaded configuration snapshot for the given device, and configuration type.\n * @param device The device for which the configuration snapshot was uploaded.\n * @param configurationType Selected configuration type.\n */\n async getConfigSnapshot(\n device: IManagedObject,\n configurationType: string\n ): Promise<ConfigurationSnapshot | undefined> {\n const event: IEvent = await this.getLatestConfigurationEvent(device.id, configurationType);\n let configSnapshot: ConfigurationSnapshot;\n if (event) {\n configSnapshot = {\n time: event.time,\n name: event.text,\n deviceType: device.type,\n configurationType\n };\n try {\n configSnapshot.binary = await (await this.eventBinary.download(event)).text();\n if (event.c8y_IsBinary) {\n configSnapshot.binaryType = event.c8y_IsBinary.type;\n }\n } catch (ex) {\n const msg = gettext('Could not get the binary.');\n this.alert.danger(msg);\n }\n }\n return configSnapshot;\n }\n\n async getLegacyConfigSnapshot(deviceId) {\n let configSnapshot: ConfigurationSnapshot;\n let mo;\n const device = (await this.inventory.detail(deviceId, { withChildren: false })).data;\n const snapshotId = device.c8y_ConfigurationDump && device.c8y_ConfigurationDump.id;\n if (!snapshotId) {\n return;\n }\n\n try {\n mo = (await this.inventory.detail(snapshotId)).data;\n } catch (ex) {\n // do nothing\n }\n if (mo) {\n configSnapshot = {\n time: mo.creationTime,\n name: mo.name\n };\n configSnapshot.binary = await this.getBinaryText(mo.url, { allowExternal: false });\n }\n return configSnapshot;\n }\n\n /**\n * Returns a binary object as text.\n * @param binaryUrl The URL to find binary\n * @param options The object with additional options:\n * - **allowExternal** - `boolean` - allows downloading external binary file\n * - **noAlerts** - `boolean` - do not display an alert message; defaults to `false`\n */\n async getBinaryText(\n binaryUrl: string,\n options: { allowExternal: boolean; noAlerts?: boolean }\n ): Promise<string> {\n const binaryId = this.inventoryBinary.getIdFromUrl(binaryUrl);\n let res;\n if (!binaryId) {\n if (options.allowExternal) {\n res = await this.getExternalBinaryResponse(binaryUrl, options);\n }\n } else {\n res = await this.getInternalBinaryResponse(binaryId, options);\n }\n if (!res) {\n return null;\n }\n return res.text();\n }\n\n /**\n * Returns a binary object as File.\n * @param binaryUrl The URL to find binary\n * @param options The object with additional options:\n * - **allowExternal** - `boolean` - allows downloading external binary file\n */\n async getBinaryFile(binaryUrl: string, options: { allowExternal: boolean }): Promise<File> {\n const binaryId = this.inventoryBinary.getIdFromUrl(binaryUrl);\n if (!binaryId && !options.allowExternal) {\n return null;\n }\n // @TODO: note that it doesn't solve issue with external binary here, such url won't have binaryId, so we won't know the name or contentType to use in File constructor, let's add a @FIXME comment for now?\n const { name, contentType } = (await this.inventory.detail(binaryId)).data;\n const res = !!binaryId\n ? await this.getInternalBinaryResponse(binaryId)\n : await this.getExternalBinaryResponse(binaryUrl);\n const arrayBuffer = await res.arrayBuffer();\n return new File([arrayBuffer], name, { type: contentType });\n }\n\n /**\n * Gets the last configuration update operation for given device.\n * Looks for c8y_Configuration and c8y_SendConfiguration operations.\n * @param deviceId The ID of the device to find an operation for.\n */\n async getLastConfigUpdateOperation(deviceId: string | number): Promise<IOperation> {\n const filters = {\n deviceId,\n dateFrom: new Date(0).toISOString(),\n dateTo: new Date(Date.now()).toISOString(),\n revert: true,\n pageSize: 1\n };\n return this.getLatestMatchingOperation([\n { ...filters, fragmentType: 'c8y_Configuration' },\n { ...filters, fragmentType: 'c8y_SendConfiguration' }\n ]);\n }\n\n /**\n * Prepares a configuration download operation for given device and its current configuration.\n * Supports c8y_SendConfiguration operation.\n * @param device The device for which operation should be prepared.\n */\n createTextBasedConfigurationReloadOperation(device: IManagedObject): IOperation {\n return {\n deviceId: device.id,\n description: gettext('Requested current configuration'),\n c8y_SendConfiguration: {}\n };\n }\n\n /**\n * Prepares a configuration update operation for the given device.\n * Supports c8y_Configuration operation.\n * @para