@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
2,100 lines • 113 kB
JavaScript
import { gettext } from '@c8y/ngx-components/gettext';
import * as i1 from '@c8y/ngx-components';
import { BaseColumn, getBasicInputArrayFormFieldConfig, CommonModule, memoize, CoreModule, C8yTranslatePipe, TypeaheadComponent, IconDirective, ModalSelectionMode, SelectModalComponent, ProductExperienceDirective, PRODUCT_EXPERIENCE_EVENT_SOURCE, toObservable, ListItemComponent, ForOfDirective, HighlightComponent, C8yTranslateDirective, OperationRealtimeService } from '@c8y/ngx-components';
import * as i0 from '@angular/core';
import { Component, Injectable, HostListener, ViewChild, ChangeDetectionStrategy, Input, EventEmitter, forwardRef, Output, NgModule } from '@angular/core';
import * as i3 from '@angular/router';
import { RouterModule } from '@angular/router';
import { DeviceGridModule } from '@c8y/ngx-components/device-grid';
import { get, head, isNil, set, assign, isUndefined, isString, cloneDeep, map as map$1, omitBy, pick, remove, forEach, find, property, uniqBy, has, isEmpty, isEqual } from 'lodash-es';
import { TooltipModule } from 'ngx-bootstrap/tooltip';
import * as i2 from '@angular/common';
import { CommonModule as CommonModule$1, NgIf, AsyncPipe, NgStyle } from '@angular/common';
import { __decorate, __metadata } from 'tslib';
import * as i1$1 from '@c8y/client';
import { QueriesUtil, OperationStatus } from '@c8y/client';
import { of, from, defer, throwError, merge, NEVER, BehaviorSubject, pipe, Observable, Subject, interval } from 'rxjs';
import { map, take, switchMap, withLatestFrom, filter, takeWhile, tap, debounceTime, shareReplay, mergeMap, debounce } from 'rxjs/operators';
import * as i3$1 from '@angular/forms';
import { FormsModule, NG_VALUE_ACCESSOR, NG_VALIDATORS } from '@angular/forms';
import { saveAs } from 'file-saver';
import * as i2$1 from '@ngx-translate/core';
class DescriptionGridColumn extends BaseColumn {
constructor(initialColumnConfig) {
super(initialColumnConfig);
this.name = 'description';
this.path = 'description';
this.header = gettext('Description');
this.filterable = true;
this.filteringConfig = {
fields: getBasicInputArrayFormFieldConfig({
key: 'descriptions',
label: initialColumnConfig?.filterLabel ?? gettext('Filter items by description'),
addText: gettext('Add next`description`'),
tooltip: gettext('Use * as a wildcard character'),
placeholder: initialColumnConfig?.placeholder ?? gettext('Description…')
}),
getFilter(model) {
const filter = {};
if (model.descriptions.length) {
filter.description = { __in: model.descriptions };
}
return filter;
}
};
this.sortable = true;
this.sortingConfig = {
pathSortingConfigs: [{ path: this.path }]
};
}
}
class DeviceTypeCellRendererComponent {
constructor(context) {
this.context = context;
}
ngOnInit() {
this.deviceType = get(this.context?.item, this.context?.property?.path);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DeviceTypeCellRendererComponent, deps: [{ token: i1.CellRendererContext }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: DeviceTypeCellRendererComponent, isStandalone: true, selector: "c8y-device-type-cell-renderer", ngImport: i0, template: "<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", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: DeviceGridModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "ngmodule", type: RouterModule }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DeviceTypeCellRendererComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-device-type-cell-renderer', standalone: true, imports: [CommonModule, DeviceGridModule, TooltipModule, RouterModule], template: "<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" }]
}], ctorParameters: () => [{ type: i1.CellRendererContext }] });
class DeviceTypeGridColumn extends BaseColumn {
constructor(initialColumnConfig) {
super(initialColumnConfig);
this.name = 'deviceType';
this.path = initialColumnConfig?.path ?? 'c8y_Filter.type';
this.header = gettext('Device type');
this.cellRendererComponent = DeviceTypeCellRendererComponent;
this.filterable = true;
this.filteringConfig = {
fields: [
...getBasicInputArrayFormFieldConfig({
key: 'types',
label: initialColumnConfig?.filterLabel ?? gettext('Filter items by device type'),
addText: gettext('Add next`type`'),
tooltip: gettext('Use * as a wildcard character'),
placeholder: initialColumnConfig?.placeholder ?? 'c8y_Linux',
optional: true
}),
{
key: 'noDeviceType',
type: 'switch',
templateOptions: {
label: gettext('No device type')
}
}
],
getFilter(model) {
const filter = { __or: {} };
if (model.types?.length) {
filter.__or = {
'c8y_Filter.type': { __in: model.types }
};
}
if (model.noDeviceType) {
filter.__or = {
...filter.__or,
__or: {
__not: { __has: 'c8y_Filter.type' },
'c8y_Filter.type': ''
}
};
}
return filter;
}
};
this.sortable = true;
this.sortingConfig = {
pathSortingConfigs: [{ path: this.path }]
};
}
}
var RepositoryType;
(function (RepositoryType) {
RepositoryType["FIRMWARE"] = "c8y_Firmware";
RepositoryType["SOFTWARE"] = "c8y_Software";
RepositoryType["CONFIGURATION"] = "c8y_ConfigurationDump";
RepositoryType["PROFILE"] = "c8y_Profile";
})(RepositoryType || (RepositoryType = {}));
const REPOSITORY_BINARY_TYPES = {
[RepositoryType.SOFTWARE]: 'c8y_SoftwareBinary',
[RepositoryType.FIRMWARE]: 'c8y_FirmwareBinary',
[RepositoryType.CONFIGURATION]: 'c8y_ConfigurationDumpBinary'
};
var DeviceConfigurationOperation;
(function (DeviceConfigurationOperation) {
DeviceConfigurationOperation["UPLOAD_CONFIG"] = "c8y_UploadConfigFile";
DeviceConfigurationOperation["DOWNLOAD_CONFIG"] = "c8y_DownloadConfigFile";
DeviceConfigurationOperation["CONFIG"] = "c8y_Configuration";
DeviceConfigurationOperation["SEND_CONFIG"] = "c8y_SendConfiguration";
})(DeviceConfigurationOperation || (DeviceConfigurationOperation = {}));
const PRODUCT_EXPERIENCE_REPOSITORY_SHARED = {
SOFTWARE: {
EVENTS: {
REPOSITORY: 'softwareRepository',
DEVICE_TAB: 'deviceSoftware'
},
COMPONENTS: {
ADD_SOFTWARE_MODAL: 'add-software-modal',
DEVICE_SOFTWARE_CHANGES: 'device-software-changes',
DEVICE_SOFTWARE_LIST: 'device-software-list'
},
ACTIONS: {
APPLY_SOFTWARE_CHANGES: 'applySoftwareChanges',
CLEAR_SOFTWARE_CHANGES: 'clearSoftwareChanges',
OPEN_INSTALL_SOFTWARE: 'openInstallSoftwareModal',
OPEN_UPDATE_SOFTWARE: 'openUpdateSoftwareModal',
DELETE_SOFTWARE: 'deleteSoftware'
},
RESULTS: {
ADD_SOFTWARE: 'addSoftware',
ADD_SOFTWARE_VERSION: 'addSoftwareVersion',
EDIT_SOFTWARE: 'editSoftware'
}
},
FIRMWARE: {
EVENTS: {
REPOSITORY: 'firmwareRepository',
DEVICE_TAB: 'deviceFirmware'
},
COMPONENTS: {
ADD_FIRMWARE_MODAL: 'add-firmware-modal',
ADD_FIRMWAR_PATCH_MODAL: 'add-firmware-patch-modal',
FIRMWARE_DEVICE_TAB: 'firmware-device-tab',
DEVICE_FIRMWARE_LIST: 'device-firmware-list'
},
ACTIONS: {
OPEN_INSTALL_FIRMWARE_DIALOG: 'openInstallFirmwareDialog',
OPEN_REPLACE_FIRMWARE_DIALOG: 'openReplaceFirmwareDialog',
OPEN_INSTALL_FIRMWARE_PATCH_DIALOG: 'openInstallFirmwarePatchDialog'
},
RESULTS: {
ADD_FIRMWARE: 'addFirmware',
ADD_FIRMWARE_VERSION: 'addFirmwareVersion',
ADD_FIRMWARE_PATCH: 'addFirmwarePatch',
EDIT_FIRMWARE: 'editFirmware',
CREATE_FIRMWARE_UPDATE_OPERATION: 'createFirmwareUpdateOperation'
}
},
SHARED: {
COMPONENTS: {
REPOSITORY_SELECT_MODAL: 'repository-select-modal',
SELECT_CONFIGURATION_MODAL: 'select-configuration-modal'
}
}
};
class RepositoryService {
constructor(inventory, inventoryBinary, operation, alert, event, operationRealtime, eventBinary, serviceRegistry, globalConfigService) {
this.inventory = inventory;
this.inventoryBinary = inventoryBinary;
this.operation = operation;
this.alert = alert;
this.event = event;
this.operationRealtime = operationRealtime;
this.eventBinary = eventBinary;
this.serviceRegistry = serviceRegistry;
this.globalConfigService = globalConfigService;
this.dateFrom = new Date(0);
this.dateTo = new Date(Date.now() + 86400000); // 1 day in the future
this.queriesUtil = new QueriesUtil();
this.advancedSoftwareService = head(this.serviceRegistry.get('asm'));
}
/**
* Lists repository entries of given type.
* @param type The type of repository entries to list.
* @param options Extra listing options.
*/
listRepositoryEntries(type, options) {
const defaultOrder = [{ name: 1 }];
const defaultFilters = { type };
const legacyFilters = { __has: `url` };
let filters = {};
let fullQuery = (options && options.query) || {};
if (!options || (options && !options.skipDefaultOrder)) {
fullQuery = this.queriesUtil.addOrderbys(fullQuery, defaultOrder, 'prepend');
}
fullQuery = this.queriesUtil.addAndFilter(fullQuery, defaultFilters);
if (options && options.partialTextFilter) {
const { partialText, properties } = options.partialTextFilter;
const orFilter = { __or: properties.map(property => ({ [property]: `*${partialText}*` })) };
fullQuery = this.queriesUtil.addAndFilter(fullQuery, orFilter);
}
if (options && options.partialName) {
// backwards compatibility if
fullQuery = this.queriesUtil.addAndFilter(fullQuery, { name: `*${options.partialName}*` });
}
if (options && options.skipLegacy) {
fullQuery = this.queriesUtil.addAndFilter(fullQuery, { __not: legacyFilters });
}
filters = {
query: this.queriesUtil.buildQuery(fullQuery),
pageSize: 50,
withTotalPages: true,
...((options && options.params) || {})
};
return this.inventory.list(filters);
}
async create(modal, type, mo = {}) {
switch (type) {
case RepositoryType.FIRMWARE:
case RepositoryType.SOFTWARE:
return this.createRepositoryObject(modal, type);
case RepositoryType.CONFIGURATION:
Object.assign(modal, {
selected: {
id: mo.id,
name: modal.version
},
configurationType: modal.configurationType,
name: modal.version
});
if (!modal.deviceType && mo.id) {
modal.deviceType = null;
}
if (!modal.selected && mo.id) {
modal.configurationType = null;
}
const repositoryObject = this.createRepositoryObject(modal, type);
if (mo.url) {
const newBinaryUrl = (await repositoryObject).url;
this.removeOutdatedBinary(newBinaryUrl, mo.url);
}
return repositoryObject;
}
}
async createRepositoryObject(modal, type) {
let binary;
let binaryURL;
let repositoryEntry;
let repositoryBinary;
const mos = [];
const { selected: { id: selectedId }, binary: { file, url } } = modal;
try {
const globalParam = await this.getGlobalFragment(type);
if (file) {
({ data: binary } = await this.saveBinary(file, globalParam));
({ self: binaryURL } = binary);
if (type === RepositoryType.CONFIGURATION) {
modal.binary.url = binaryURL;
}
mos.push(binary);
}
else {
binaryURL = url;
}
({ data: repositoryEntry } = await this.createOrUpdateRepositoryEntry({ ...modal, ...globalParam }, type));
if (isNil(selectedId)) {
mos.push(repositoryEntry);
}
if (type !== RepositoryType.CONFIGURATION) {
({ data: repositoryBinary } = await this.createRepositoryBinary({ ...modal, ...globalParam }, binaryURL, type, repositoryEntry));
mos.push(repositoryBinary);
}
if (file) {
await this.linkBinary(repositoryBinary, binary, repositoryEntry);
}
return repositoryEntry;
}
catch (error) {
this.cleanUp(mos);
this.errorMsg();
// Propagate error
throw error;
}
}
saveBinary(file, global) {
return this.inventoryBinary.create(file, global);
}
async createOrUpdateRepositoryEntry(modal, type) {
const { selected: { id, name }, description, deviceType, c8y_Global, binary } = modal;
const mo = {
id,
name,
description,
type,
c8y_Global
};
if (deviceType && type !== RepositoryType.CONFIGURATION) {
set(mo, 'c8y_Filter.type', deviceType);
}
if ((deviceType || id) && type === RepositoryType.CONFIGURATION) {
set(mo, 'deviceType', deviceType);
}
if (modal.softwareType) {
set(mo, 'softwareType', modal.softwareType.softwareType);
}
if ((modal.configurationType || id) && type === RepositoryType.CONFIGURATION) {
set(mo, 'configurationType', modal.configurationType);
}
if (type === RepositoryType.CONFIGURATION) {
set(mo, 'url', binary?.url);
}
return id
? this.inventory.update(mo)
: this.inventory.create(mo);
}
createRepositoryBinary(modal, binaryURL, type, parent) {
const mo = this.prepareRepositoryBinaryMO(modal, binaryURL, type);
return this.inventory.childAdditionsCreate(mo, parent);
}
prepareRepositoryBinaryMO(modal, binaryURL, type) {
const { version, patchVersion, dependency, c8y_Global } = modal;
const result = {
type: REPOSITORY_BINARY_TYPES[type],
[type]: {
url: binaryURL
},
c8y_Global
};
if (dependency) {
set(result, [type, 'version'], patchVersion);
assign(result, {
c8y_Patch: {
dependency: dependency.c8y_Firmware.version
}
});
}
else {
set(result, [type, 'version'], version);
}
return result;
}
async linkBinary(repositoryBinary, binary, repositoryEntry) {
if (repositoryBinary) {
const { id: repositoryBinaryId } = repositoryBinary;
if (binary) {
const { id: binaryId } = binary;
return this.inventory.childAdditionsAdd(binaryId, repositoryBinaryId);
}
}
else
return this.inventory.childAdditionsAdd(binary, repositoryEntry);
}
cleanUp(mosToDelete) {
mosToDelete.forEach(mo => {
const { c8y_IsBinary } = mo;
isUndefined(c8y_IsBinary) ? this.delete(mo) : this.inventoryBinary.delete(mo);
});
}
delete(entity) {
return this.inventory.delete(entity, { forceCascade: true });
}
errorMsg() {
const msg = gettext('Failed to save');
this.alert.danger(msg);
}
getBaseVersionsCount$(entry) {
if (this.isLegacyEntry(entry)) {
return of(1);
}
return from(this.listBaseVersions(entry, { withTotalElements: true })).pipe(map(({ paging }) => paging.totalElements));
}
getBaseVersionFromMO(mo) {
return this.isPatch(mo) ? get(mo, 'c8y_Patch.dependency') : get(mo, 'c8y_Firmware.version');
}
isPatch(mo) {
return !!get(mo, 'c8y_Patch.dependency');
}
getPatchVersionsCount$(entry, baseVersion) {
if (this.isLegacyEntry(baseVersion)) {
return of(0);
}
return from(this.listPatchVersions(entry, baseVersion, { withTotalElements: true })).pipe(map(({ paging }) => paging.totalElements));
}
isLegacyEntry(entry) {
return Boolean(entry.url);
}
/**
* Lists all versions (base and patch ones) of given top level entry.
* Versions are ordered by creation time (assuming the earlier created, the older the version).
* @param entry Top level repository entry.
* @param params Additional query params.
*/
listAllVersions(entry, params = {}) {
if (this.isLegacyEntry(entry)) {
return this.getBaseVersionResultListForLegacyEntry(entry);
}
const VERSION_FILTER_ORDER = {
__filter: {},
__orderby: [{ 'creationTime.date': -1 }]
};
return this.listChildren(entry, VERSION_FILTER_ORDER, params);
}
/**
* Lists base versions of given top level entry.
* Versions are ordered by creation time (assuming the earlier created, the older the version).
* @param entry Top level repository entry.
* @param params Additional query params.
*/
listBaseVersions(entry, params = {}) {
if (this.isLegacyEntry(entry)) {
return this.getBaseVersionResultListForLegacyEntry(entry);
}
const NO_PATCH_FILTER_ORDER = {
__filter: {
__not: { __has: 'c8y_Patch' }
},
__orderby: [{ 'creationTime.date': -1 }]
};
return this.listChildren(entry, NO_PATCH_FILTER_ORDER, params);
}
/**
* Lists patch versions of given base version under the entry.
* Versions are ordered by creation time (assuming the earlier created, the older the version).
* @param entry Top level repository entry.
* @param baseVersion Base version.
* @param params Additional query params.
*/
listPatchVersions(entry, baseVersion, params = {}) {
const version = isString(baseVersion) ? baseVersion : get(baseVersion, 'c8y_Firmware.version');
const PATCH_FILTER_ORDER = {
__filter: {
'c8y_Patch.dependency': version
},
__orderby: [{ 'creationTime.date': -1 }]
};
return this.listChildren(entry, PATCH_FILTER_ORDER, params);
}
/**
* Lists patch versions of given base version under the entry including the base version.
* Versions are ordered by creation time (assuming the earlier created, the older the version).
* In terms of legacy base version the entry gets transformed to fit the needed data model.
* @param entry Top level repository entry.
* @param baseVersion Base version.
* @param params Additional query params.
*/
listBaseVersionAndPatches(entry, baseVersion, params = {}) {
if (this.isLegacyEntry(entry)) {
return Promise.resolve({
data: [
Object.assign({
c8y_Firmware: {
version: entry.version,
url: entry.url
}
}, entry)
]
});
}
const PATCH_FILTER_ORDER = {
__filter: {
__or: {
'c8y_Patch.dependency': baseVersion.c8y_Firmware.version,
'c8y_Firmware.version': baseVersion.c8y_Firmware.version
}
},
__orderby: [{ 'c8y_Patch.dependency': 1 }, { 'c8y_Firmware.version': 1 }]
};
return this.listChildren(entry, PATCH_FILTER_ORDER, params);
}
listChildren(entry, filters = {}, params = {}) {
const childrenFilters = { __bygroupid: entry.id };
const query = this.queriesUtil.addAndFilter(filters, childrenFilters);
// FIXME: needed because of issue in forOf directive (...)
params.withTotalPages = true;
return this.inventory.listQuery(query, params);
}
/**
* Fetches all items from the list starting with the provided page.
* @param firstPage The first page of the list to fetch all items for.
*/
async fetchAllItemsFromList(firstPage) {
let allItems;
if (!firstPage.then) {
allItems = [...firstPage];
}
else {
let { paging, data: items } = await firstPage;
allItems = [...items];
while (paging && paging.nextPage) {
({ paging, data: items } = await paging.next());
allItems = [...allItems, ...items];
}
}
return allItems;
}
/**
* Gets top level repository entry managed object for base or patch version.
* @param mo Base or patch version managed object with parents.
*/
getRepositoryEntryMO$(mo) {
if (!mo) {
return of(undefined);
}
const [reference] = get(mo, 'additionParents.references');
const id = get(reference, 'managedObject.id');
return id
? from(this.inventory.detail(id, { withChildren: false })).pipe(map(({ data }) => data))
: of(undefined);
}
/**
* Gets base or patch version managed object.
* @param deviceRepositoryFragment Device repository fragment.
* @param type Top level repository entry type.
* @param configuration Configuration object with options:
* - **skipLegacy** - `boolean` - Exclude legacy entries.
* - **filters** - `object` - Filter object.
*
* @deprecated as it doesn't support 'missing url' case
*/
getRepositoryBinaryMoByVersion(deviceRepositoryFragment, type, { skipLegacy = false, filters = {} } = {}) {
const { version, url, name } = deviceRepositoryFragment;
const repositoryBinaryType = REPOSITORY_BINARY_TYPES[type];
let query;
const newModelBaseVersionQuery = {
[`${type}.version`]: version,
[`${type}.url`]: url,
type: repositoryBinaryType
};
const legacyVersionQuery = { url, type, name };
filters = { withChildren: false, withParents: true, ...filters };
if (skipLegacy) {
query = {
__and: {
...newModelBaseVersionQuery
}
};
}
else {
query = {
__or: [{ __and: { ...newModelBaseVersionQuery } }, { __and: { ...legacyVersionQuery } }]
};
}
return this.inventory.listQuery(query, filters).then(({ data }) => head(data));
}
getBinaryName$(binaryUrl) {
if (!binaryUrl) {
return of('---');
}
const binaryId = this.inventoryBinary.getIdFromUrl(binaryUrl);
if (!binaryId) {
return of(binaryUrl);
}
return defer(() => this.inventory.detail(binaryId).then(result => result.data)).pipe(map(mo => mo.name));
}
/**
* Generates an inventory query object which can be used to find
* repository entries of specified type matching the type of provided device.
* @param repositoryType The type of repository entries which will be queried with the generated query.
* @param device The device for which matching repository entries will be queried with the generated query.
*/
getDeviceTypeQuery(repositoryType, device) {
let result = {
type: repositoryType
};
if (repositoryType === RepositoryType.CONFIGURATION) {
if (device.type) {
result = this.queriesUtil.addAndFilter(result, {
__or: [{ deviceType: device.type }, { __not: { __has: `deviceType` } }]
});
}
}
else {
result = this.queriesUtil.addAndFilter(result, {
__or: [
{ 'c8y_Filter.type': device.type },
{ 'c8y_Filter.type': '' },
{ __not: { __has: `c8y_Filter.type` } }
]
});
}
return result;
}
/**
* Generates an inventory query object which can be used to find
* repository entries matching the predefined software types provided in the device.
* @param device The device for which matching repository entries will be queried with the generated query.
* @param query The query to which the software types filters will be attached. Default value is an object containg repository type software.
*/
getSoftwareTypeQuery(device, query) {
let result = {
...(query || {}),
type: RepositoryType.SOFTWARE
};
if (device.c8y_SupportedSoftwareTypes) {
result = this.queriesUtil.addAndFilter(result, {
__or: [device.c8y_SupportedSoftwareTypes.map(type => ({ softwareType: type }))]
});
}
return result;
}
/**
* Generates an inventory query object which can be used to find configuration repository entries
* matching the type of provided device and specified configuration type.
* @param device The device for which matching repository entries will be queried with the generated query.
* @param configurationType Configuration type for which matching repository entries will be queried with the generated query.
*/
getConfigurationTypeQuery(device, configurationType) {
const query = this.getDeviceTypeQuery(RepositoryType.CONFIGURATION, device);
return this.queriesUtil.addAndFilter(query, {
__or: [
{ configurationType },
{ configurationType: '' },
{ __not: { __has: `configurationType` } }
]
});
}
/**
* Gets the list of software installed in the device in the uniform format.
* Supports c8y_SoftwareList and c8y_Software fragments.
* @param device The device whose software list should be returned.
*/
getDeviceSoftwareList(device) {
if (device.c8y_SoftwareList) {
return cloneDeep(device.c8y_SoftwareList);
}
if (device.c8y_Software) {
return map$1(device.c8y_Software, (version, name) => ({ name, version }));
}
return [];
}
/**
* Prepares a software update operation for given device and the list of changes, and sends it to the device.
* @param device The device which the operation should be prepared for and sent to.
* @param changes The list of software changes which should be applied.
*/
async createSoftwareUpdateOperation(device, changes) {
const operation = await this.getSoftwareUpdateOperation(device, changes);
return (await this.operation.create(operation)).data;
}
/**
* Prepares a software update operation for given device and changes.
* Returned operation type depends on device's supported operations.
* Supports c8y_SoftwareUpdate, c8y_SoftwareList, and c8y_Software operations.
* @param device The device for which operation should be prepared.
* @param changes The list of software changes which should be applied.
*/
async getSoftwareUpdateOperation(device, changes) {
const operation = {
deviceId: device.id,
description: `Apply software changes: ${changes
.map(change => `${change.action} "${change.name}"${change.version ? ` (version: ${change.version})` : ''}`)
.join(', ')}`
};
if (device.c8y_SupportedOperations.includes('c8y_SoftwareUpdate')) {
operation.c8y_SoftwareUpdate = (cloneDeep(changes) || []).map(change => omitBy(change, isNil));
}
else if (device.c8y_SupportedOperations.includes('c8y_SoftwareList')) {
operation.c8y_SoftwareList = cloneDeep(await this.getCurrentSoftware(device, 'c8y_SoftwareList', []));
changes.forEach(change => {
const deviceSoftware = pick(omitBy(change, isNil), [
'name',
'version',
'url',
'softwareType'
]);
if (change.action === 'delete') {
remove(operation.c8y_SoftwareList, deviceSoftware);
}
if (change.action === 'install') {
const softwareItemToUpdateIdx = operation.c8y_SoftwareList.findIndex(item => item.name === change.name);
if (softwareItemToUpdateIdx > -1) {
// update software
operation.c8y_SoftwareList.splice(softwareItemToUpdateIdx, 1, deviceSoftware);
}
else {
// install software
operation.c8y_SoftwareList.push(deviceSoftware);
}
}
});
}
else if (device.c8y_SupportedOperations.includes('c8y_Software')) {
operation.c8y_Software = cloneDeep(await this.getCurrentSoftware(device, 'c8y_Software', {}));
changes.forEach(change => {
if (change.action === 'delete') {
delete operation.c8y_Software[change.name];
}
if (change.action === 'install') {
operation.c8y_Software[change.name] = change.version;
}
});
}
return operation;
}
/**
* Extracts the list of device software changes from given operation in the context of given device.
* @param operation The operation from which the list should be extracted.
* @param device The target device of the operation.
*/
async getDeviceSoftwareChangesFromOperation(operation, device) {
if (operation.c8y_SoftwareUpdate) {
return cloneDeep(operation.c8y_SoftwareUpdate);
}
if (operation.c8y_SoftwareList) {
return await this.getDeviceSoftwareChangesFromSoftwareListOperation(operation, device);
}
if (operation.c8y_Software) {
return await this.getDeviceSoftwareChangesFromSoftwareOperation(operation, device);
}
return [];
}
/**
* Prepares a firmware update operation for given device and the selected repository binary, and sends it to the device.
* @param device The device which the operation should be prepared for and sent to.
* @param selectedOption The selected repository binary option.
*/
async createFirmwareUpdateOperation(device, selectedOption) {
const operation = this.getFirmwareUpdateOperation(device, selectedOption);
return (await this.operation.create(operation)).data;
}
/**
* Prepares a firmware update operation for given device and selected version.
* Supports c8y_Firmware operation.
* @param device The device for which operation should be prepared.
* @param selectedOption Selected firmware version.
*/
getFirmwareUpdateOperation(device, selectedOption) {
delete selectedOption.id;
const operation = {
deviceId: device.id,
description: `Update firmware to: "${selectedOption.name}"${selectedOption.version ? ` (version: ${selectedOption.version})` : ''}`,
c8y_Firmware: { ...selectedOption }
};
return operation;
}
/**
* Prepares a configuration file upload operation for given device and configuration type.
* @param device The device for which operation should be prepared.
* @param configurationType Selected configuration type.
* @param isLegacy A legacy operation is created without a configurationType.
*/
getUploadConfigurationFileOperation(device, configurationType, isLegacy = false) {
if (isLegacy) {
return {
deviceId: device.id,
description: `Retrieve configuration snapshot from device ${device.name}`,
c8y_UploadConfigFile: {}
};
}
return {
deviceId: device.id,
description: `Retrieve ${configurationType} configuration snapshot from device ${device.name}`,
c8y_UploadConfigFile: {
type: configurationType
}
};
}
/**
* Prepares a configuration file download operation for given device and configuration type.
* @param device The device for which operation should be prepared.
* @param configurationType Selected configuration type.
* @param binaryUrl The url of a binary to be downloaded.
* @param isLegacy A legacy operation is created without a configurationType.
*/
getDownloadConfigurationFileOperation(device, configurationType, configSnapshot, isLegacy = false) {
if (isLegacy) {
return {
deviceId: device.id,
description: `Send configuration snapshot ${configSnapshot.name} to device ${device.name}`,
c8y_DownloadConfigFile: {
url: configSnapshot.binaryUrl,
c8y_ConfigurationDump: {
id: configSnapshot.id
}
}
};
}
return {
deviceId: device.id,
description: `Send configuration snapshot ${configSnapshot.name} of configuration type ${configurationType} to device ${device.name}`,
c8y_DownloadConfigFile: {
url: configSnapshot.binaryUrl,
type: configurationType
}
};
}
/**
* Gets the last firmware update operation for given device.
* Looks for c8y_Firmware operations.
* @param deviceId The ID of the device to find an operation for.
*/
async getLastFirmwareUpdateOperation(deviceId) {
const filters = {
deviceId,
dateFrom: new Date(0).toISOString(),
dateTo: new Date(Date.now()).toISOString(),
revert: true,
pageSize: 1
};
return this.getFirstMatchingOperation([{ ...filters, fragmentType: 'c8y_Firmware' }]);
}
/**
* Gets the last software update operation for given device.
* Looks for c8y_SoftwareUpdate, c8y_SoftwareList, or c8y_Software operations.
* @param deviceId The ID of the device to find an operation for.
*/
async getLastSoftwareUpdateOperation(deviceId) {
const filters = {
deviceId,
dateFrom: new Date(0).toISOString(),
dateTo: new Date(Date.now()).toISOString(),
revert: true,
pageSize: 1
};
return this.getLatestMatchingOperation([
{ ...filters, fragmentType: 'c8y_SoftwareUpdate' },
{ ...filters, fragmentType: 'c8y_SoftwareList' },
{ ...filters, fragmentType: 'c8y_Software' }
]);
}
/**
* Iterates over the list of filters and queries the operations.
* If a query returns at least one operation, the first one will be returned.
* Otherwise the next query will be performed.
* If none of the queries returns any operation, null will be returned.
* @param filtersList The list of filters for the queries.
*/
async getFirstMatchingOperation(filtersList) {
let matchingOperation = null;
for (const filters of filtersList) {
const operations = (await this.operation.list(filters)).data;
if (operations.length) {
matchingOperation = operations[0];
break;
}
}
return matchingOperation;
}
/**
* Iterates over the list of filters and queries the operations.
* It compares the operations retrieved by the queries by 'creationTime'
* and return the latest one.
* If none of the queries returns any operation, null will be returned.
* @param filtersList The list of filters for the queries.
*/
async getLatestMatchingOperation(filtersList) {
let matchingOperation = null;
for (const filters of filtersList) {
const operations = (await this.operation.list(filters)).data;
if (operations.length) {
if (matchingOperation) {
matchingOperation =
new Date(matchingOperation.creationTime).getTime() <
new Date(operations[0].creationTime).getTime()
? operations[0]
: matchingOperation;
}
else {
matchingOperation = operations[0];
}
}
}
return matchingOperation;
}
/**
* Creates the operation and returns an observable to track its progress.
* Fails the observable when the operation returns FAILED status.
* Completes the observable when the operation returns SUCCESSFUL status.
* @param operation The operation to create and track.
*/
createObservedOperation(operation) {
return from(this.operation.create(operation)).pipe(map(({ data }) => data), take(1), switchMap(createdOperation => this.observeOperation(createdOperation)));
}
/**
* Returns an observable to track progress of given operation.
* Fails the observable when the operation returns FAILED status.
* Completes the observable when the operation returns SUCCESSFUL status.
* @param operation The operation to be observed.
*/
observeOperation(operation) {
const observedOperation$ = of(operation);
const operationUpdates$ = observedOperation$.pipe(switchMap(observedOperation => this.operationRealtime.onAll$(observedOperation.deviceId)), map(({ data }) => data), withLatestFrom(observedOperation$), filter(([operationUpdate, observedOperation]) => operationUpdate.id === observedOperation.id), switchMap(([operationUpdate]) => {
if (operationUpdate.status === OperationStatus.FAILED) {
return throwError(operationUpdate);
}
return of(operationUpdate);
}), takeWhile(operationUpdate => operationUpdate.status !== OperationStatus.SUCCESSFUL, true));
return merge(observedOperation$, operationUpdates$);
}
/**
* Gets a single event with latest creationTime for the given device Id and event type.
* @param deviceId The device Id for which the events should be queried.
* @param type Event type.
*/
async getLatestConfigurationEvent(deviceId, type) {
const eventFilter = {
source: deviceId,
type,
dateFrom: this.dateFrom.toISOString(),
dateTo: this.dateTo.toISOString(),
pageSize: 1
};
const { data } = await this.event.list(eventFilter);
return data[0];
}
/**
* Gets a list of operations for the given device Id, and operation type.
* @param deviceId The device Id for which the operation should be queried.
* @param operationType Operation type fragment.
*/
async getConfigFileOperationList(deviceId, operationType) {
const operationFilter = {
deviceId,
fragmentType: operationType,
dateFrom: this.dateFrom.toISOString(),
dateTo: this.dateTo.toISOString(),
revert: true,
pageSize: 2000
};
return (await this.operation.list(operationFilter)).data;
}
/**
* Gets latest uploaded configuration snapshot for the given device, and configuration type.
* @param device The device for which the configuration snapshot was uploaded.
* @param configurationType Selected configuration type.
*/
async getConfigSnapshot(device, configurationType) {
const event = await this.getLatestConfigurationEvent(device.id, configurationType);
let configSnapshot;
if (event) {
configSnapshot = {
time: event.time,
name: event.text,
deviceType: device.type,
configurationType
};
try {
configSnapshot.binary = await (await this.eventBinary.download(event)).text();
if (event.c8y_IsBinary) {
configSnapshot.binaryType = event.c8y_IsBinary.type;
}
}
catch (ex) {
const msg = gettext('Could not get the binary.');
this.alert.danger(msg);
}
}
return configSnapshot;
}
async getLegacyConfigSnapshot(deviceId) {
let configSnapshot;
let mo;
const device = (await this.inventory.detail(deviceId, { withChildren: false })).data;
const snapshotId = device.c8y_ConfigurationDump && device.c8y_ConfigurationDump.id;
if (!snapshotId) {
return;
}
try {
mo = (await this.inventory.detail(snapshotId)).data;
}
catch (ex) {
// do nothing
}
if (mo) {
configSnapshot = {
time: mo.creationTime,
name: mo.name
};
configSnapshot.binary = await this.getBinaryText(mo.url, { allowExternal: false });
}
return configSnapshot;
}
/**
* Returns a binary object as text.
* @param binaryUrl The URL to find binary
* @param options The object with additional options:
* - **allowExternal** - `boolean` - allows downloading external binary file
* - **noAlerts** - `boolean` - do not display an alert message; defaults to `false`
*/
async getBinaryText(binaryUrl, options) {
const binaryId = this.inventoryBinary.getIdFromUrl(binaryUrl);
let res;
if (!binaryId) {
if (options.allowExternal) {
res = await this.getExternalBinaryResponse(binaryUrl, options);
}
}
else {
res = await this.getInternalBinaryResponse(binaryId, options);
}
if (!res) {
return null;
}
return res.text();
}
/**
* Returns a binary object as File.
* @param binaryUrl The URL to find binary
* @param options The object with additional options:
* - **allowExternal** - `boolean` - allows downloading external binary file
*/
async getBinaryFile(binaryUrl, options) {
const binaryId = this.inventoryBinary.getIdFromUrl(binaryUrl);
if (!binaryId && !options.allowExternal) {
return null;
}
// @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?
const { name, contentType } = (await this.inventory.detail(binaryId)).data;
const res = !!binaryId
? await this.getInternalBinaryResponse(binaryId)
: await this.getExternalBinaryResponse(binaryUrl);
const arrayBuffer = await res.arrayBuffer();
return new File([arrayBuffer], name, { type: contentType });
}
/**
* Gets the last configuration update operation for given device.
* Looks for c8y_Configuration and c8y_SendConfiguration operations.
* @param deviceId The ID of the device to find an operation for.
*/
async getLastConfigUpdateOperation(deviceId) {
const filters = {
deviceId,
dateFrom: new Date(0).toISOString(),
dateTo: new Date(Date.now()).toISOString(),
revert: true,
pageSize: 1
};
return this.getLatestMatchingOperation([
{ ...filters, fragmentType: 'c8y_Configuration' },
{ ...filters, fragmentType: 'c8y_SendConfiguration' }
]);
}
/**
* Prepares a configuration download operation for given device and its current configuration.
* Supports c8y_SendConfiguration operation.
* @param device The device for which operation should be prepared.
*/
createTextBasedConfigurationReloadOperation(device) {
return {
deviceId: device.id,
description: gettext('Requested current configuration'),
c8y_SendConfiguration: {}
};
}
/**
* Prepares a configuration update operation for the given device.
* Supports c8y_Configuration operation.
* @param device The device for which operation should be prepared.
* @param config The configuration which will update the existing one.
*/
createTextBasedConfigurationUpdateOperation(device, config) {
return {
deviceId: device.id,
description: gettext('Configuration update'),
c8y_Configuration: {
config
}
};
}
async getBinary(binaryId) {
try {
return await this.inventoryBinary.download(binaryId);
}
catch (ex) {
const msg = gettext('Could not get the binary.');
this.alert.danger(msg);
}
}
/**
* Gets all available snapshots from the repository for the given device.
* @param device The device for which the snapshots should be prepared.
* @param configurationType Selected configuration type.
*/
async getSnapshotsFromRepository(device, configurationType) {
const searchQuery = this.getConfigurationTypeQuery(device, configurationType);
const res = await this.listRepositoryEntries(RepositoryType.CONFIGURATION, {
query: searchQuery,
params: { pageSize: 100 }
});
return res.data;
}
/**
* Checks if a device already have a given software installed
* @param deviceId Id of the device to be checked
* @param software The software to be checked
*/
async isSoftwareInstalledOnDevice(deviceId, software) {
const isASMAvailable = await this.advancedSoftwareService?.isASMAvailable();
if (!isASMAvailable) {
return false;
}
const queryFilter = { deviceId };
if (software?.name) {
set(queryFilter, 'name', software.name);
}
if (software?.version) {
set(queryFilter, 'version', software.version);
}
return this.advancedSoftwareService.list(queryFilter).then(result => !!result.data?.length);
}
/**
* Returns a binary object.
* @param binaryId binary ID
* @param options The object with additional options:
* - **noAlerts** - `boolean` - do not display an alert message; defaults to `false`
*/
async getInternalBinaryResponse(binaryId, options = {}) {
let res;
try {
res = await this.inventoryBinary.download(binaryId);
}
catch (ex) {
if (!options.noAlerts) {
const msg = gettext('Could not get the binary.');
this.alert.danger(msg);
}
}
return res;
}
/**
* Returns a binary object.
* @param binaryUrl The URL to find binary
* @param options The object with additional options:
* - **noAlerts** - `boolean` - do not display an alert message; defaults to `false`
*/
async getExternalBinaryResponse(binaryUrl, options = {}) {
let res;
try {
const fetchRes = await fetch(binaryUrl);
if (fetchRes.status >= 400) {
throw res;
}
res = fetchRes;
}
catch {
if (!options.noAlerts) {
const msg = gettext('Could not get the external binary');
this.alert.danger(msg);
}
}
return res;
}
getBaseVersionResultListForLegacyEntry(entry) {
return Promise.resolve({
res: {},
data: [
{
...entry,
[entry.type]: {
version: entry.version,
url: entry.url
}
}
]
});
}
async getDeviceSoftwareChangesFromSoftwareListOperation(operation, device) {
const changes = [];
const deviceSoftwareList = await this.getCurrentSoftware(device, 'c8y_SoftwareList', []);
forEach(operation.c8y_SoftwareList, operationSoftware => {
const deviceSoftware = find(deviceSoftwareList, { name: operationSoftware.name });
if ((operationSoftware && operationSoftware.version) !==
(deviceSoftware && deviceSoftware.version)) {
changes.push({
...operationSoftware,
action: 'install'
});
}
});
forEach(deviceSoftwareList, deviceSoftware => {
const operationSoftware = find(operation.c8y_SoftwareList, { name: deviceSoftware.name });
if ((operationSoftware && operationSoftware.version) !==
(deviceSoftware && deviceSoftware.version)) {
const installChange = changes.find(change => deviceSoftware.name === change.name && change.action === 'install');
// check that this software is not an installation software change, otherwise it's an update and not a removal
if (!installChange) {
changes.push({
...deviceSoftware,
action: 'delete'
});
}
}
});
return changes;
}
async getDeviceSoftwareChangesFromSoftwareOperation(operation, device) {
const changes = [];
const deviceSoftware = await this.getCurrentSoftware(device, 'c8y_Software', {});
forEach(deviceSoftware, (deviceSoftwareVersion, deviceSoftwareName) => {
if (operation.c8y_Software[deviceSoftwareName] !== deviceSoftwareVersion) {
changes.push({
name: deviceSoftwareName,
version: deviceSoftwareVersion,
action: 'delete'
});
}
});
forEach(operation.c8y_Software, (operationSoftwareVersion, operationSoftwareName) => {
const deviceSoftwareVersion = deviceSoftware && deviceSoftware[operationSoftwareName];
if (deviceSoftwareVersion !== operationSoftwareVersion) {
changes.push({
name: operationSoftwareName,
version: operationSoftwareVersion,
action: 'install'
});
}
});
return changes;
}
async getCurrentSoftware(device, swFragment, defaultValue) {
const isASMAvailable = await this.advancedSoftwareService?.isASMAvailable();
if (isASMAvailable) {
let softwareResultList = await this.advancedSoftwareService.list({ deviceId: device.id, pageSize: 100 });
let list = (softwareResultList?.data || []).map(sw => pick(omitBy(sw, isNil), ['name', 'version', 'url', 'softwareType']));
while (softwareResultList.paging?.nextPage) {
softwareResultList = await softwareResultList.paging.next();
list = [
...list,
...(softwareResultList?.data || []).map(sw => pick(omitBy(sw, isNil), ['name', 'version', 'url', 'softwareType']))
];
}
if (!list?.length) {
return defaultValue;
}
return Array.isArray(defaultValue) ? list : this.softwareListToLegacy(list);
}
else {
return device[swFragment] || defaultValue;
}
}
softwareListToLegacy(list) {
return (list || []).reduce((prev, curr) => ({ ...prev, [curr.name]: curr.version }), {});
}
async getGlobalFragment(type) {
return (await this.globalConfigService.getGlobalParam(type)) ? { c8y_Global: {} } : undefined;
}
async removeOutdatedBinary(newBinaryURL, oldBinaryURL) {
const existingBinaryId = await this.inventoryBinary.getIdFromUrl(oldBinaryURL);
const newBinaryId = await this.inventoryBinary.getIdFromUrl(newBinaryURL);
if (existingBinaryId && existingBinaryId !== newBinaryId) {
await this.inventoryBinary.delete(existingBinaryId);
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RepositoryService, deps: [{ token: i1$1.InventoryService }, { token: i1$1.InventoryBinaryService }, { token: i1$1.OperationService }, { token: i1.AlertService }, { token: i1$1.EventService }, { token: i1.OperationRealtimeService }, { token: i1$1.EventBinaryService }, { token: i1.ServiceRegistry }, { token: i1.GlobalConfigService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RepositoryService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RepositoryService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1$1.InventoryService }, { type: i1$1.InventoryBinaryService }, { type: i1$1.OperationService }, { type: i1.AlertService }, { type: i1$1.EventService }, { type: i1.OperationRealtimeService }, { type: i1$1.EventBinaryService }, { type: i1.ServiceRegistry }, { type: i1.GlobalConfigService }] });
class FileCellRendererComponent {
constructor(context, inventoryBinaryService, repositoryService) {
this.context = context;
this.inventoryBinaryService = inventoryBinaryService;
this.repositoryService = repositoryService;
}
isBinaryFile() {
return this.context.item?.url
? !!this.inventoryBinaryService.getIdFromUrl(this.context.item.url)
: false;
}
getBinaryName(configuration) {
return this.repositoryService.getBinaryName$(configuration.url);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FileCellRendererComponent, deps: [{ token: i1.CellRendererContext }, { token: i1$1.InventoryBinaryService }, { token: RepositoryService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: FileCellRendererComponent, isStandalone: true, selector: "c8y-file-cell-renderer", providers: [RepositoryService], ngImport: i0, template: "<small\n title=\"{{ getBinaryName(context.item) | async }}\"\n *ngIf=\"isBinaryFile(); else noFile\"\n>\n {{ getBinaryName(context.item) | async }}\n</small>\n<ng-template #noFile>\n <small title=\"{{ context.item?.url }}\">\n {{ context.item?.url }}\n </small>\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }] }); }
}
__decorate([
memoize(property('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], FileCellRendererComponent.prototype, "getBinaryName", null);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FileCellRendererComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-file-cell-renderer', standalone: true, imports: [CommonModule], providers: [RepositoryService], template: "<small\n title=\"{{ getBinaryName(context.item) | async }}\"\n *ngIf=\"isBinaryFile(); else noFile\"\n>\n {{ getBinaryName(context.item) | async }}\n</small>\n<ng-template #noFile>\n <small title=\"{{ context.item?.url }}\">\n {{ context.item?.url }}\n </small>\n</ng-template>\n" }]
}], ctorParameters: () => [{ type: i1.CellRendererContext }, { type: i1$1.InventoryBinaryService }, { type: RepositoryService }], propDecorators: { getBinaryName: [] } });
class FileGridColumn extends BaseColumn {
constructor(initialColumnConfig) {
super(initialColumnConfig);
this.name = 'file';
this.header = gettext('File');
this.cellRendererComponent = FileCellRendererComponent;
this.filterable = false;
this.sortable = false;
}
}
class RepositoryItemNameCellRendererComponent {
constructor(context) {
this.context = context;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RepositoryItemNameCellRendererComponent, deps: [{ token: i1.CellRendererContext }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: RepositoryItemNameCellRendererComponent, isStandalone: true, selector: "c8y-repository-item-name-cell-renderer", ngImport: i0, template: `
<a
class="interact"
[title]="context.item.name"
*ngIf="context?.property?.callback; else router"
(click)="context.property.callback(context.item)"
>
{{ context.item.name }}
</a>
<ng-template #router>
<a class="interact" [title]="context.item.name" [routerLink]="[context.item.id]">
{{ context.item.name }}
</a>
</ng-template>
`, isInline: true, dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "ngmodule", type: DeviceGridModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "ngmodule", type: RouterModule }, { kind: "directive", type: i3.RouterLink, selector: "[routerLink]", inputs: ["target", "queryParams", "fragment", "queryParamsHandling", "state", "info", "relativeTo", "preserveFragment", "skipLocationChange", "replaceUrl", "routerLink"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RepositoryItemNameCellRendererComponent, decorators: [{
type: Component,
args: [{
template: `
<a
class="interact"
[title]="context.item.name"
*ngIf="context?.property?.callback; else router"
(click)="context.property.callback(context.item)"
>
{{ context.item.name }}
</a>
<ng-template #router>
<a class="interact" [title]="context.item.name" [routerLink]="[context.item.id]">
{{ context.item.name }}
</a>
</ng-template>
`,
selector: 'c8y-repository-item-name-cell-renderer',
standalone: true,
imports: [CommonModule, DeviceGridModule, TooltipModule, RouterModule]
}]
}], ctorParameters: () => [{ type: i1.CellRendererContext }] });
class RepositoryItemNameGridColumn extends BaseColumn {
constructor(initialColumnConfig) {
super(initialColumnConfig);
this.name = 'name';
this.path = 'name';
this.header = gettext('Name');
this.cellRendererComponent = RepositoryItemNameCellRendererComponent;
this.filterable = true;
this.filteringConfig = {
fields: getBasicInputArrayFormFieldConfig({
key: 'names',
label: initialColumnConfig?.filterLabel ?? gettext('Filter items by name'),
addText: gettext('Add next`name`'),
tooltip: gettext('Use * as a wildcard character'),
placeholder: initialColumnConfig?.placeholder ?? gettext('Cloud connectivity')
}),
getFilter(model) {
const filter = {};
if (model.names.length) {
filter.name = { __in: model.names };
}
return filter;
}
};
this.sortable = true;
this.sortingConfig = {
pathSortingConfigs: [{ path: this.path }]
};
}
}
class TypeCellRendererComponent {
constructor(context) {
this.context = context;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TypeCellRendererComponent, deps: [{ token: i1.CellRendererContext }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: TypeCellRendererComponent, isStandalone: true, selector: "c8y-type-cell-renderer", ngImport: i0, template: "<span\n class=\"label label-info\"\n *ngIf=\"!!context?.item?.[context?.property?.path]; else emptyText\"\n>\n {{ context.item[context.property.path] }}\n</span>\n<ng-template #emptyText>\n <small class=\"text-muted\">\n <em translate>Undefined`type`</em>\n </small>\n</ng-template>\n", dependencies: [{ kind: "ngmodule", type: CommonModule$1 }, { kind: "directive", type: i2.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TypeCellRendererComponent, decorators: [{
type: Component,
args: [{ standalone: true, selector: 'c8y-type-cell-renderer', imports: [CommonModule$1], template: "<span\n class=\"label label-info\"\n *ngIf=\"!!context?.item?.[context?.property?.path]; else emptyText\"\n>\n {{ context.item[context.property.path] }}\n</span>\n<ng-template #emptyText>\n <small class=\"text-muted\">\n <em translate>Undefined`type`</em>\n </small>\n</ng-template>\n" }]
}], ctorParameters: () => [{ type: i1.CellRendererContext }] });
class TypeFilteringFormRendererComponent {
constructor(context, changeDetectorRef, repositoryService, elementRef) {
this.context = context;
this.changeDetectorRef = changeDetectorRef;
this.repositoryService = repositoryService;
this.elementRef = elementRef;
this.types$ = NEVER;
this.search$ = new BehaviorSubject(null);
this.filterPipe = pipe(tap());
this.typeaheadPlaceholder = gettext('Start typing to search, for example, {{ example }}');
this.queriesUtil = new QueriesUtil();
this.types = new Set();
this.path = context.property.path;
this.types$ = this.search$.pipe(debounceTime(300), tap(() => this.types.clear()), switchMap((searchString) => {
let query = this.queriesUtil.prependOrderbys({}, [{ [this.path]: 1 }]);
const filter = !!searchString
? {
[this.path]: {
__eq: `*${searchString}*`
}
}
: {
__has: this.path
};
query = this.queriesUtil.addAndFilter(query, filter);
return this.repositoryService.listRepositoryEntries(this.context.property.repositoryType, {
skipDefaultOrder: true,
query,
params: {
pageSize: 200
}
});
}));
this.filterPipe = pipe(map(this.removeDuplicatesType.bind(this)), tap(() => setTimeout(() => this.changeDetectorRef.detectChanges(), 0)));
}
onEnterKeyUp(event) {
event.stopPropagation();
this.applyFilter();
}
onEscapeKeyDown(event) {
event.stopPropagation();
this.context.resetFilter();
}
ngOnInit() {
const column = this.context.property;
this.selectedType = cloneDeep(column.externalFilterQuery || {});
}
ngAfterViewInit() {
this.typeahead?.searchControl?.nativeElement?.focus();
try {
this.elementRef.nativeElement.parentElement.parentElement.style.overflow = 'visible';
}
catch (ex) {
// intentionally empty
}
}
applyFilter() {
this.context.applyFilter({
externalFilterQuery: {
model: this.selectedType,
chips: [
{
value: this.selectedType,
displayValue: this.selectedType?.[this.path],
path: [this.path],
columnName: this.context.property.name,
remove: () => {
return {
columnName: this.context.property.name,
externalFilterQuery: {
model: null,
chips: []
}
};
}
}
]
}
});
}
removeDuplicatesType(list) {
const uniqueByType = uniqBy(list, this.path).filter((mo) => !this.types.has(mo[this.path]));
uniqueByType.forEach((mo) => this.types.add(mo[this.path]));
return uniqueByType;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TypeFilteringFormRendererComponent, deps: [{ token: i1.FilteringFormRendererContext }, { token: i0.ChangeDetectorRef }, { token: RepositoryService }, { token: i0.ElementRef }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: TypeFilteringFormRendererComponent, isStandalone: true, selector: "c8y-type-filtering-form-renderer", host: { listeners: { "keyup.enter": "onEnterKeyUp($event)", "keydown.escape": "onEscapeKeyDown($event)" } }, viewQueries: [{ propertyName: "typeahead", first: true, predicate: TypeaheadComponent, descendants: true }], ngImport: i0, template: "<c8y-form-group>\n <label>\n {{ context?.property?.filterLabel | translate }}\n </label>\n <c8y-typeahead\n placeholder=\"{{ typeaheadPlaceholder | translate: { example: context?.property?.example } }}\"\n [name]=\"path\"\n [(ngModel)]=\"selectedType\"\n [displayProperty]=\"path\"\n (onSearch)=\"search$.next($event)\"\n >\n <c8y-li\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n *c8yFor=\"let type of types$; pipe: filterPipe; loadMore: 'auto'\"\n (click)=\"$event.stopPropagation(); selectedType = type; typeahead.dropdown.hide()\"\n [active]=\"selectedType?.[path] === type?.[path]\"\n >\n <c8y-highlight\n [text]=\"type?.[path] || '--'\"\n [pattern]=\"search$.value\"\n ></c8y-highlight>\n </c8y-li>\n </c8y-typeahead>\n</c8y-form-group>\n\n<div class=\"data-grid__dropdown__footer d-flex separator-top\">\n <button\n class=\"btn btn-default btn-sm m-r-8 flex-grow\"\n title=\"{{ 'Reset' | translate }}\"\n (click)=\"context.resetFilter()\"\n translate\n >\n Reset\n </button>\n\n <button\n class=\"btn btn-primary btn-sm flex-grow\"\n title=\"{{ 'Apply' | translate }}\"\n (click)=\"applyFilter()\"\n translate\n >\n Apply\n </button>\n</div>\n", dependencies: [{ kind: "ngmodule", type: CommonModule$1 }, { kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i1.ForOfDirective, selector: "[c8yFor]", inputs: ["c8yForOf", "c8yForLoadMore", "c8yForPipe", "c8yForNotFound", "c8yForMaxIterations", "c8yForLoadingTemplate", "c8yForLoadNextLabel", "c8yForLoadingLabel", "c8yForRealtime", "c8yForRealtimeOptions", "c8yForComparator", "c8yForEnableVirtualScroll", "c8yForVirtualScrollElementSize", "c8yForVirtualScrollStrategy", "c8yForVirtualScrollContainerHeight"], outputs: ["c8yForCount", "c8yForChange", "c8yForLoadMoreComponent"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "ngmodule", type: CoreModule }, { kind: "component", type: i1.HighlightComponent, selector: "c8y-highlight", inputs: ["pattern", "text", "elementClass", "shouldTrimPattern"] }, { kind: "component", type: i1.TypeaheadComponent, selector: "c8y-typeahead", inputs: ["required", "maxlength", "disabled", "allowFreeEntries", "placeholder", "displayProperty", "icon", "name", "autoClose", "hideNew", "container", "selected", "title", "highlightFirstItem"], outputs: ["onSearch", "onIconClick"] }, { kind: "component", type: i1.FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "component", type: i1.ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }], changeDetection: i0.ChangeDetectionStrategy.Default }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: TypeFilteringFormRendererComponent, decorators: [{
type: Component,
args: [{ standalone: true, changeDetection: ChangeDetectionStrategy.Default, selector: 'c8y-type-filtering-form-renderer', imports: [CommonModule$1, C8yTranslatePipe, CommonModule, FormsModule, CoreModule], template: "<c8y-form-group>\n <label>\n {{ context?.property?.filterLabel | translate }}\n </label>\n <c8y-typeahead\n placeholder=\"{{ typeaheadPlaceholder | translate: { example: context?.property?.example } }}\"\n [name]=\"path\"\n [(ngModel)]=\"selectedType\"\n [displayProperty]=\"path\"\n (onSearch)=\"search$.next($event)\"\n >\n <c8y-li\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n *c8yFor=\"let type of types$; pipe: filterPipe; loadMore: 'auto'\"\n (click)=\"$event.stopPropagation(); selectedType = type; typeahead.dropdown.hide()\"\n [active]=\"selectedType?.[path] === type?.[path]\"\n >\n <c8y-highlight\n [text]=\"type?.[path] || '--'\"\n [pattern]=\"search$.value\"\n ></c8y-highlight>\n </c8y-li>\n </c8y-typeahead>\n</c8y-form-group>\n\n<div class=\"data-grid__dropdown__footer d-flex separator-top\">\n <button\n class=\"btn btn-default btn-sm m-r-8 flex-grow\"\n title=\"{{ 'Reset' | translate }}\"\n (click)=\"context.resetFilter()\"\n translate\n >\n Reset\n </button>\n\n <button\n class=\"btn btn-primary btn-sm flex-grow\"\n title=\"{{ 'Apply' | translate }}\"\n (click)=\"applyFilter()\"\n translate\n >\n Apply\n </button>\n</div>\n" }]
}], ctorParameters: () => [{ type: i1.FilteringFormRendererContext }, { type: i0.ChangeDetectorRef }, { type: RepositoryService }, { type: i0.ElementRef }], propDecorators: { typeahead: [{
type: ViewChild,
args: [TypeaheadComponent, { static: false }]
}], onEnterKeyUp: [{
type: HostListener,
args: ['keyup.enter', ['$event']]
}], onEscapeKeyDown: [{
type: HostListener,
args: ['keydown.escape', ['$event']]
}] } });
class TypeGridColumn extends BaseColumn {
constructor(initialColumnConfig) {
super(initialColumnConfig);
this.name = 'type';
this.path = initialColumnConfig?.path ?? 'type';
this.header = initialColumnConfig?.header ?? gettext('Type');
this.repositoryType = initialColumnConfig?.repositoryType;
this.cellRendererComponent = TypeCellRendererComponent;
this.filterable = true;
this.filteringFormRendererComponent = TypeFilteringFormRendererComponent;
this.filteringConfig = {
getFilter: query => {
const filter = {};
if (query.model[this.path]) {
filter[this.path] = { __eq: query.model[this.path] };
}
return filter;
}
};
this.sortable = true;
this.sortingConfig = {
pathSortingConfigs: [{ path: this.path }]
};
}
}
class VersionsCellRendererComponent {
constructor(context, repositoryService) {
this.context = context;
this.repositoryService = repositoryService;
this.isLegacy = this.repositoryService.isLegacyEntry.bind(this.repositoryService);
this.item = context.item;
}
getBaseVersionsCount$(item) {
return this.repositoryService.getBaseVersionsCount$(item).pipe(shareReplay(1));
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: VersionsCellRendererComponent, deps: [{ token: i1.CellRendererContext }, { token: RepositoryService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.15", type: VersionsCellRendererComponent, isStandalone: true, selector: "c8y-versions-cell-renderer", ngImport: i0, template: "@if (isLegacy(item)) {\n <span\n class=\"label label-warning m-l-auto-sm\"\n translate\n >\n Legacy\n </span>\n} @else {\n @if (getBaseVersionsCount$(item) | async; as baseVersionsCount) {\n <span class=\"badge badge-info m-l-auto-sm\">\n {{ baseVersionsCount }}\n </span>\n } @else {\n <span class=\"badge badge-info m-l-auto-sm\">\n <i\n class=\"icon-spin\"\n c8yIcon=\"circle-o-notch\"\n ></i>\n </span>\n }\n}\n", dependencies: [{ kind: "ngmodule", type: CommonModule }, { kind: "directive", type: i1.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i1.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "ngmodule", type: DeviceGridModule }, { kind: "ngmodule", type: TooltipModule }, { kind: "ngmodule", type: RouterModule }, { kind: "pipe", type: i2.AsyncPipe, name: "async" }] }); }
}
__decorate([
memoize(property('id')),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", Observable)
], VersionsCellRendererComponent.prototype, "getBaseVersionsCount$", null);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: VersionsCellRendererComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-versions-cell-renderer', standalone: true, imports: [CommonModule, DeviceGridModule, TooltipModule, RouterModule], template: "@if (isLegacy(item)) {\n <span\n class=\"label label-warning m-l-auto-sm\"\n translate\n >\n Legacy\n </span>\n} @else {\n @if (getBaseVersionsCount$(item) | async; as baseVersionsCount) {\n <span class=\"badge badge-info m-l-auto-sm\">\n {{ baseVersionsCount }}\n </span>\n } @else {\n <span class=\"badge badge-info m-l-auto-sm\">\n <i\n class=\"icon-spin\"\n c8yIcon=\"circle-o-notch\"\n ></i>\n </span>\n }\n}\n" }]
}], ctorParameters: () => [{ type: i1.CellRendererContext }, { type: RepositoryService }], propDecorators: { getBaseVersionsCount$: [] } });
class VersionsGridColumn extends BaseColumn {
constructor(initialColumnConfig) {
super(initialColumnConfig);
this.name = 'versions';
this.header = gettext('Versions');
this.cellRendererComponent = VersionsCellRendererComponent;
this.sortable = false;
}
}
var LinkRenderType;
(function (LinkRenderType) {
LinkRenderType[LinkRenderType["DOWNLOAD"] = 0] = "DOWNLOAD";
LinkRenderType[LinkRenderType["LINK"] = 1] = "LINK";
LinkRenderType[LinkRenderType["TEXTONLY"] = 2] = "TEXTONLY";
})(LinkRenderType || (LinkRenderType = {}));
class FileDownloadComponent {
constructor(repositoryService, inventoryBinaryService, alertService) {
this.repositoryService = repositoryService;
this.inventoryBinaryService = inventoryBinaryService;
this.alertService = alertService;
this.linkRenderType = LinkRenderType;
this.isDownloading = false;
}
getBinaryName$(binaryUrl) {
return this.repositoryService.getBinaryName$(binaryUrl);
}
determineBehavior() {
let result;
if (this.inventoryBinaryService.getIdFromUrl(this.url)) {
result = LinkRenderType.DOWNLOAD;
}
else if (this.url.match(/\/\//g)) {
result = LinkRenderType.LINK;
}
else {
result = LinkRenderType.TEXTONLY;
}
return result;
}
async downloadFile() {
try {
this.isDownloading = true;
const binary = await this.repositoryService.getBinaryFile(this.url, {
allowExternal: false
});
this.isDownloading = false;
saveAs(binary);
}
catch (ex) {
this.isDownloading = false;
if (ex) {
this.alertService.addServerFailure(ex);
}
}
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FileDownloadComponent, deps: [{ token: RepositoryService }, { token: i1$1.InventoryBinaryService }, { token: i1.AlertService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: FileDownloadComponent, isStandalone: true, selector: "c8y-file-download", inputs: { url: "url" }, ngImport: i0, template: "<a\n *ngIf=\"determineBehavior() === linkRenderType.LINK\"\n href=\"{{ url }}\"\n class=\"pointer\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n>\n {{ getBinaryName$(url) | async }}\n</a>\n\n<span *ngIf=\"determineBehavior() === linkRenderType.TEXTONLY\">{{\n getBinaryName$(url) | async\n}}</span>\n\n<span *ngIf=\"determineBehavior() === linkRenderType.DOWNLOAD\">\n <a *ngIf=\"!isDownloading\" class=\"pointer\" (click)=\"downloadFile()\">\n {{ getBinaryName$(url) | async }}\n </a>\n\n <span *ngIf=\"isDownloading\">\n <i c8yIcon=\"spinner\" class=\"icon-spin\"></i> {{ 'Downloading\u2026' | translate }}\n </span>\n</span>\n", dependencies: [{ kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
}
__decorate([
memoize(),
__metadata("design:type", Function),
__metadata("design:paramtypes", [Object]),
__metadata("design:returntype", void 0)
], FileDownloadComponent.prototype, "getBinaryName$", null);
__decorate([
memoize(),
__metadata("design:type", Function),
__metadata("design:paramtypes", []),
__metadata("design:returntype", Number)
], FileDownloadComponent.prototype, "determineBehavior", null);
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: FileDownloadComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-file-download', imports: [NgIf, IconDirective, AsyncPipe, C8yTranslatePipe], template: "<a\n *ngIf=\"determineBehavior() === linkRenderType.LINK\"\n href=\"{{ url }}\"\n class=\"pointer\"\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n>\n {{ getBinaryName$(url) | async }}\n</a>\n\n<span *ngIf=\"determineBehavior() === linkRenderType.TEXTONLY\">{{\n getBinaryName$(url) | async\n}}</span>\n\n<span *ngIf=\"determineBehavior() === linkRenderType.DOWNLOAD\">\n <a *ngIf=\"!isDownloading\" class=\"pointer\" (click)=\"downloadFile()\">\n {{ getBinaryName$(url) | async }}\n </a>\n\n <span *ngIf=\"isDownloading\">\n <i c8yIcon=\"spinner\" class=\"icon-spin\"></i> {{ 'Downloading\u2026' | translate }}\n </span>\n</span>\n" }]
}], ctorParameters: () => [{ type: RepositoryService }, { type: i1$1.InventoryBinaryService }, { type: i1.AlertService }], propDecorators: { url: [{
type: Input
}], getBinaryName$: [], determineBehavior: [] } });
// MODAL STRUCTURE
// - selectModalObject (repository entry (repositoryCategory) -> type c8y_Firmware/c8y_Software)
// -- ISelectModalOption (repository binary entry (repositoryBinary) => type c8y_FirmwareBinary/c8y_SoftwareBinary)
// -- ISelectModalOption...
// - selectModalObject...
/**
* RepositorySelectModalComponent displays repository entries options and allows to select them.
*
* ```typescript
* import { take } from 'rxjs/operators';
* import { RepositorySelectModalComponent, ModalSelectionMode, RepositoryType } from '@c8y/ngx-components/repository/shared';
*
* const initialState = {
* 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
* };
*
* const modal = this.bsModal.show(RepositorySelectModalComponent, {
* ignoreBackdropClick: true,
* initialState
* });
*
* modal.content.load.next();
* modal.content.resultEmitter.pipe(take(1)).subscribe((firmware) => {
* })
* ```
*/
class RepositorySelectModalComponent {
constructor(repositoryService, translateService) {
this.repositoryService = repositoryService;
this.translateService = translateService;
this.PRODUCT_EXPERIENCE = PRODUCT_EXPERIENCE_REPOSITORY_SHARED;
/**
* Optional
* Allows to provide custom data.
* ```typescript
* import { from } from 'rxjs';
*
* const repositoryEntry = { name: 'ExampleEntry', type: 'c8y_Firmware' };
* const versions = [{ c8y_Firmware: { version: '1.0.0', url: 'http://example.com' } }];
*
* const initialState = {repositoryEntriesWithVersions$: from({ ...repositoryEntry, versions })};
* ```
*/
this.repositoryEntriesWithVersions$ = undefined;
/**
* Optional
* Allows to use custom badges templates.
* ```typescript
* import { gettext } from '@c8y/ngx-components/gettext';
*
* const badgeTemplates = { '=1': gettext('{{count}} version'), other: gettext('{{count}} versions') };
* const initialState = { badgeTemplates };
* ```
*/
this.badgeTemplates = { '=1': gettext('{{count}} version'), other: gettext('{{count}} versions') };
/**
* Optional
* Allows to provide custom modal title.
*/
this.title = gettext('Select repository entry');
/**
* Loads the content of the modal.
* Must be invoked by the modal's caller.
*/
this.load = new Subject();
/**
* Triggers an update of the item list emitted.
*/
this.updateInstallableList$ = new Subject();
/**
* Optional
* Emits a filter criteria object currently entered in the filter input.
* Use it to filter the items if you use custom repositoryEntriesWithVersions$.
*/
this.searchTerm = new BehaviorSubject({});
/**
* Optional
* Allows to provide device type query to restrict search criteria.
* Only takes effect when repositoryEntriesWithVersions$ is not provided,
* otherwise modal's caller have to provide already filtered data in the repositoryEntriesWithVersions$.
*/
this.deviceTypeQuery = {};
/**
* Optional
* Allows to provide query to restrict search criteria.
* Only takes effect when repositoryEntriesWithVersions$ is not provided,
* otherwise modal's caller have to provide already filtered data in the repositoryEntriesWithVersions$.
*/
this.searchQuery = {};
/**
* Optional
* Allows to provide custom labels for the buttons responsible for confirm/dismiss modal actions.
*/
this.labels = { ok: gettext('Save') };
/**
* Optional
* Allows to hide the name filter input field.
* By default, the filter input field is displayed.
*/
this.showFilter = true;
/**
* Optional
* Allows to show a warning that the search criteria should be narrowed down.
* By default, this warning is hidden.
*/
this.areMoreEntries = false;
/**
* Emits whenever a new repository binary have been selected in the modal.
*/
this.onChoiceUpdated = new EventEmitter();
/**
* Emits the list of selected options.
*/
this.resultEmitter = new EventEmitter();
/**
* Optional
* Allows to change selection mode.
* Supported options:
* * single: only single option can be selected.
* * multiple: multiple options can be selected.
*/
this.mode = ModalSelectionMode.SINGLE;
/**
* Allows to block selection of the other versions from the same repository entry.
*/
this.disableSelected = true;
this.filterCriteria = {};
this.repositoryEntries$ = this.load.pipe(switchMap(() => this.repositoryEntriesWithVersions$), mergeMap(mos => this.aggregate(mos)), tap(items => {
this.areMoreEntries = items.length >= this.PAGE_SIZE ? true : false;
}), tap(items => (this.repositoryEntries = items)));
this.modalEntries = merge(this.repositoryEntries$, this.updateInstallableList$.pipe(map((updateItemEvent) => {
const itemToUpdate = (this.repositoryEntries || []).find(item => item.groupId === updateItemEvent.object.groupId);
if (itemToUpdate) {
const optionToUpdate = (itemToUpdate.options || []).find(option => option.obj.id === updateItemEvent.object.selectedId);
if (optionToUpdate) {
optionToUpdate.template = updateItemEvent.template;
if (updateItemEvent.mapper) {
optionToUpdate.obj = updateItemEvent.mapper(optionToUpdate.obj);
}
}
}
return this.repositoryEntries;
})));
this.PAGE_SIZE = 100;
this.queriesUtil = new QueriesUtil();
}
ngOnInit() {
if (!this.repositoryType) {
throw new Error('Repository type must be defined');
}
if (!this.repositoryEntriesWithVersions$) {
this.repositoryEntriesWithVersions$ = of(1).pipe(mergeMap(() => this.repositoryService.listRepositoryEntries(this.repositoryType, {
query: this.queriesUtil.addAndFilter(this.deviceTypeQuery, has(this.searchQuery, 'name')
? { ...this.searchQuery, name: `*${this.searchQuery.name}*` }
: this.searchQuery),
params: { pageSize: this.PAGE_SIZE }
})), map(({ data }) => data), map(mos => this.getAndAssignRepositoryBinaries(mos)));
}
}
getAndAssignRepositoryBinaries(mos) {
mos.forEach(mo => {
mo.versions = this.repositoryService.listAllVersions(mo);
});
return mos;
}
search(filterCriteria) {
this.filterCriteria = omitBy({
...this.filterCriteria,
...filterCriteria
}, isEmpty);
if (!isEqual(this.filterCriteria, this.searchQuery)) {
this.searchTerm.next(this.filterCriteria);
this.searchQuery = this.filterCriteria;
this.load.next();
}
}
result(selectedItems) {
this.resultEmitter.emit(selectedItems);
}
async aggregate(mos) {
const repositoryType = this.repositoryType;
const selectedItems = this.selected;
return Promise.all(mos.map(async (repositoryEntry) => {
const options = this.getSelectModalOptions(await this.repositoryService.fetchAllItemsFromList(repositoryEntry.versions), selectedItems, repositoryEntry, repositoryType);
const selectModalObject = this.getSelectModalObject(repositoryEntry, options);
return selectModalObject;
}));
}
getSelectModalOptions(versions, selectedItems, repositoryEntry, repositoryType) {
const selectModalOptions = [];
versions.forEach(repositoryBinary => {
const isSelected = this.isBinaryRepositorySelected(selectedItems, repositoryEntry, repositoryBinary, repositoryType);
const { version } = repositoryBinary[`${repositoryType}`];
const bodyValue = version || `(${this.translateService.instant(gettext('not specified`version`'))})`;
const bodyClass = version ? '' : 'text-muted';
selectModalOptions.push({
body: [
{
value: bodyValue,
class: bodyClass
}
],
obj: {
id: repositoryBinary.id,
name: repositoryEntry.name,
version,
...(get(repositoryBinary, 'c8y_Patch.dependency') && {
dependency: get(repositoryBinary, 'c8y_Patch.dependency')
}),
...(get(repositoryBinary, 'c8y_Patch') && { isPatch: true }),
url: repositoryBinary[`${repositoryType}`].url,
softwareType: repositoryEntry.softwareType
},
selected: isSelected
});
});
return selectModalOptions;
}
isBinaryRepositorySelected(selectedItems, repositoryEntry, repositoryBinary, repositoryType) {
const isSelected = selectedItems
? selectedItems.filter(repositoryFragment => repositoryFragment.name === repositoryEntry.name &&
repositoryFragment.version === repositoryBinary[`${repositoryType}`].version).length > 0
: false;
return isSelected;
}
getSelectModalObject(repositoryEntry, options) {
const label = options.length === 1
? this.translateService.instant(this.badgeTemplates['=1'], { count: options.length })
: this.translateService.instant(this.badgeTemplates.other, { count: options.length });
const selectModalObject = {
groupId: repositoryEntry.id,
body: [
{ value: repositoryEntry.name, class: 'text-truncate' },
{ value: repositoryEntry.description, class: 'text-truncate text-muted' }
],
additionalInformation: { value: label, class: 'label label-info' },
options
};
return selectModalObject;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RepositorySelectModalComponent, deps: [{ token: RepositoryService }, { token: i2$1.TranslateService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: RepositorySelectModalComponent, isStandalone: true, selector: "c8y-repository-select-modal", providers: [
{
provide: PRODUCT_EXPERIENCE_EVENT_SOURCE,
useExisting: forwardRef(() => RepositorySelectModalComponent)
}
], ngImport: i0, template: "<c8y-select-modal\n [icon]=\"icon\"\n [title]=\"title\"\n [subTitle]=\"subTitle\"\n [items]=\"modalEntries | async\"\n [mode]=\"mode\"\n [disableSelected]=\"disableSelected\"\n [labels]=\"labels\"\n [showFilter]=\"showFilter\"\n [additionalFilterTemplate]=\"additionalFilterTemplate\"\n [areMoreEntries]=\"areMoreEntries\"\n [noItemsMessage]=\"noItemsMessage\"\n [hideEmptyItems]=\"hideEmptyItems\"\n (search)=\"search({ name: $event })\"\n (onChoiceUpdated)=\"onChoiceUpdated.emit($event)\"\n (result)=\"result($event)\"\n c8yProductExperience\n inherit\n suppressDataOverriding\n [actionData]=\"{ component: PRODUCT_EXPERIENCE.SHARED.COMPONENTS.REPOSITORY_SELECT_MODAL }\"\n></c8y-select-modal>\n", dependencies: [{ kind: "component", type: SelectModalComponent, selector: "c8y-select-modal", inputs: ["icon", "title", "subTitle", "items", "mode", "disableSelected", "showFilter", "additionalFilterTemplate", "areMoreEntries", "labels", "noItemsMessage", "hideEmptyItems"], outputs: ["result", "search", "onChoiceUpdated"] }, { kind: "directive", type: ProductExperienceDirective, selector: "[c8yProductExperience]", inputs: ["actionName", "actionData", "inherit", "suppressDataOverriding"] }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: RepositorySelectModalComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-repository-select-modal', providers: [
{
provide: PRODUCT_EXPERIENCE_EVENT_SOURCE,
useExisting: forwardRef(() => RepositorySelectModalComponent)
}
], imports: [SelectModalComponent, ProductExperienceDirective, AsyncPipe], template: "<c8y-select-modal\n [icon]=\"icon\"\n [title]=\"title\"\n [subTitle]=\"subTitle\"\n [items]=\"modalEntries | async\"\n [mode]=\"mode\"\n [disableSelected]=\"disableSelected\"\n [labels]=\"labels\"\n [showFilter]=\"showFilter\"\n [additionalFilterTemplate]=\"additionalFilterTemplate\"\n [areMoreEntries]=\"areMoreEntries\"\n [noItemsMessage]=\"noItemsMessage\"\n [hideEmptyItems]=\"hideEmptyItems\"\n (search)=\"search({ name: $event })\"\n (onChoiceUpdated)=\"onChoiceUpdated.emit($event)\"\n (result)=\"result($event)\"\n c8yProductExperience\n inherit\n suppressDataOverriding\n [actionData]=\"{ component: PRODUCT_EXPERIENCE.SHARED.COMPONENTS.REPOSITORY_SELECT_MODAL }\"\n></c8y-select-modal>\n" }]
}], ctorParameters: () => [{ type: RepositoryService }, { type: i2$1.TranslateService }] });
class SoftwareTypeComponent {
set presetSoftwareTypes(types) {
if (!isEmpty(types)) {
this.presetSoftwareTypes$ = toObservable(types).pipe(map(types => types.map(softwareType => typeof softwareType === 'string'
? { softwareType }
: softwareType)));
}
}
constructor(repositoryService, changeDetectorRef, translateService) {
this.repositoryService = repositoryService;
this.changeDetectorRef = changeDetectorRef;
this.translateService = translateService;
this.required = true;
this.placeholder = this.translateService.instant(gettext('e.g. {{ example }}'), {
example: 'yum'
});
this.emitResultsOnly = false;
this.showBtnInNotFoundMessage = true;
this.allowFreeEntries = true;
this.showClearSelectionOption = false;
this.clearSelectionOptionLabel = gettext('All software types');
this.onSelectSoftware = new EventEmitter();
this.filterPipe = pipe(tap());
this.search$ = new BehaviorSubject(null);
this.queriesUtil = new QueriesUtil();
this.softwareTypes = new Set();
this.filterPipe = pipe(map(this.removeDuplicatesBySoftwareType.bind(this)));
}
ngOnInit() {
this.softwaresResult$ = this.search$.pipe(debounce(() => interval(300)), tap(() => this.softwareTypes.clear()), switchMap((searchString) => {
if (!this.emitResultsOnly || !searchString) {
this.onSelectSoftware.emit(this.softwareTypeMO);
}
return this.getSoftwareByTypeResult(searchString);
}), shareReplay(1));
this.notFoundTemplateToUse = this.showBtnInNotFoundMessage
? this.notFoundTypeAddNewTemplate
: this.notFoundTypeTemplate;
}
getSoftwareByTypeResult(searchString) {
return this.presetSoftwareTypes$
? this.searchInPreset(searchString)
: this.searchInRepository(searchString);
}
selectSoftware(software) {
this.softwareTypeMO = software;
this.onSelectSoftware.emit(software);
this.deviceSoftwareTypeModel.searchControlModel.control.markAsDirty();
this.deviceSoftwareTypeModel.onChange(software);
}
clearSoftware() {
this.softwareTypeMO = undefined;
this.search$.next('');
this.onSelectSoftware.emit();
}
resetInput() {
this.deviceSoftwareTypeModel.reset();
}
writeValue(value) {
this.deviceSoftwareTypeModel.writeValue(value);
}
registerOnChange(fn) {
this.deviceSoftwareTypeModel.registerOnChange(fn);
}
registerOnTouched(fn) {
this.deviceSoftwareTypeModel.registerOnTouched(fn);
}
setDisabledState(isDisabled) {
this.deviceSoftwareTypeModel.setDisabledState(isDisabled);
}
validate(control) {
return this.deviceSoftwareTypeModel.validate(control);
}
searchInPreset(searchString) {
return this.presetSoftwareTypes$.pipe(map(types => ({
data: types.filter(type => !searchString || type.softwareType.indexOf(searchString) > -1),
res: undefined,
statistics: { currentPage: 1, pageSize: types?.length, totalPages: 1 }
})));
}
searchInRepository(searchString) {
let query = this.queriesUtil.prependOrderbys({}, [{ softwareType: 1 }]);
const filter = !!searchString
? {
softwareType: {
__eq: `*${searchString}*`
}
}
: {
__has: 'softwareType'
};
query = this.queriesUtil.addAndFilter(query, filter);
return this.repositoryService.listRepositoryEntries(RepositoryType.SOFTWARE, {
query,
params: {
pageSize: 200
}
});
}
removeDuplicatesBySoftwareType(list) {
const uniqueBySoftwareType = uniqBy(list, 'softwareType').filter((sw) => !this.softwareTypes.has(sw.softwareType));
uniqueBySoftwareType.forEach((sw) => this.softwareTypes.add(sw.softwareType));
return uniqueBySoftwareType;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SoftwareTypeComponent, deps: [{ token: RepositoryService }, { token: i0.ChangeDetectorRef }, { token: i2$1.TranslateService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SoftwareTypeComponent, isStandalone: true, selector: "c8y-software-type", inputs: { softwareTypeMO: "softwareTypeMO", disabled: "disabled", style: "style", required: "required", placeholder: "placeholder", emitResultsOnly: "emitResultsOnly", showBtnInNotFoundMessage: "showBtnInNotFoundMessage", allowFreeEntries: "allowFreeEntries", showClearSelectionOption: "showClearSelectionOption", clearSelectionOptionLabel: "clearSelectionOptionLabel", presetSoftwareTypes: "presetSoftwareTypes" }, outputs: { onSelectSoftware: "onSelectSoftware" }, providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => SoftwareTypeComponent)
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => SoftwareTypeComponent),
multi: true
}
], viewQueries: [{ propertyName: "deviceSoftwareTypeModel", first: true, predicate: ["deviceSoftwareTypeModel"], descendants: true }, { propertyName: "notFoundTypeAddNewTemplate", first: true, predicate: ["notFoundTypeAddNewTemplate"], descendants: true, static: true }, { propertyName: "notFoundTypeTemplate", first: true, predicate: ["notFoundTypeTemplate"], descendants: true, static: true }], ngImport: i0, template: "<c8y-typeahead\n [(ngModel)]=\"softwareTypeMO\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n name=\"softwareType\"\n [placeholder]=\"placeholder\"\n [allowFreeEntries]=\"allowFreeEntries\"\n #deviceSoftwareTypeModel\n (onSearch)=\"search$.next($event)\"\n displayProperty=\"softwareType\"\n [ngStyle]=\"style\"\n>\n <c8y-li\n *ngIf=\"showClearSelectionOption\"\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n (click)=\"clearSoftware()\"\n [active]=\"!softwareTypeMO?.softwareType\"\n >\n <span>{{ clearSelectionOptionLabel | translate }}</span>\n </c8y-li>\n <c8y-li\n *c8yFor=\"\n let software of softwaresResult$;\n pipe: filterPipe;\n loadMore: 'auto';\n notFound: notFoundTemplateToUse\n \"\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n (click)=\"selectSoftware(software)\"\n [active]=\"softwareTypeMO?.softwareType === software.softwareType\"\n >\n <c8y-highlight\n [text]=\"software.softwareType || '--'\"\n [pattern]=\"search$ | async\"\n ></c8y-highlight>\n </c8y-li>\n <ng-template #notFoundTypeAddNewTemplate>\n <c8y-li class=\"bg-level-2 p-8\" *ngIf=\"(search$ | async)?.length > 0\">\n <span translate>No match found.</span>\n <button\n title=\"{{ 'Add new`software type`' | translate }}\"\n type=\"button\"\n class=\"btn btn-primary btn-xs m-l-8\"\n translate\n >\n Add new`software type`\n </button>\n </c8y-li>\n </ng-template>\n <ng-template #notFoundTypeTemplate>\n <c8y-li\n class=\"bg-level-2 p-8\"\n *ngIf=\"(search$ | async)?.length > 0 && (softwaresResult$ | async)?.data?.length === 0\"\n >\n <span translate>No match found. Refine your search terms or check your spelling.</span>\n </c8y-li>\n </ng-template>\n</c8y-typeahead>\n", dependencies: [{ kind: "component", type: TypeaheadComponent, selector: "c8y-typeahead", inputs: ["required", "maxlength", "disabled", "allowFreeEntries", "placeholder", "displayProperty", "icon", "name", "autoClose", "hideNew", "container", "selected", "title", "highlightFirstItem"], outputs: ["onSearch", "onIconClick"] }, { kind: "ngmodule", type: FormsModule }, { kind: "directive", type: i3$1.NgControlStatus, selector: "[formControlName],[ngModel],[formControl]" }, { kind: "directive", type: i3$1.RequiredValidator, selector: ":not([type=checkbox])[required][formControlName],:not([type=checkbox])[required][formControl],:not([type=checkbox])[required][ngModel]", inputs: ["required"] }, { kind: "directive", type: i3$1.NgModel, selector: "[ngModel]:not([formControlName]):not([formControl])", inputs: ["name", "disabled", "ngModel", "ngModelOptions"], outputs: ["ngModelChange"], exportAs: ["ngModel"] }, { kind: "directive", type: NgStyle, selector: "[ngStyle]", inputs: ["ngStyle"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "directive", type: ForOfDirective, selector: "[c8yFor]", inputs: ["c8yForOf", "c8yForLoadMore", "c8yForPipe", "c8yForNotFound", "c8yForMaxIterations", "c8yForLoadingTemplate", "c8yForLoadNextLabel", "c8yForLoadingLabel", "c8yForRealtime", "c8yForRealtimeOptions", "c8yForComparator", "c8yForEnableVirtualScroll", "c8yForVirtualScrollElementSize", "c8yForVirtualScrollStrategy", "c8yForVirtualScrollContainerHeight"], outputs: ["c8yForCount", "c8yForChange", "c8yForLoadMoreComponent"] }, { kind: "component", type: HighlightComponent, selector: "c8y-highlight", inputs: ["pattern", "text", "elementClass", "shouldTrimPattern"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "pipe", type: AsyncPipe, name: "async" }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SoftwareTypeComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-software-type', providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => SoftwareTypeComponent)
},
{
provide: NG_VALIDATORS,
useExisting: forwardRef(() => SoftwareTypeComponent),
multi: true
}
], imports: [
TypeaheadComponent,
FormsModule,
NgStyle,
NgIf,
ListItemComponent,
ForOfDirective,
HighlightComponent,
C8yTranslateDirective,
AsyncPipe,
C8yTranslatePipe
], template: "<c8y-typeahead\n [(ngModel)]=\"softwareTypeMO\"\n [required]=\"required\"\n [disabled]=\"disabled\"\n name=\"softwareType\"\n [placeholder]=\"placeholder\"\n [allowFreeEntries]=\"allowFreeEntries\"\n #deviceSoftwareTypeModel\n (onSearch)=\"search$.next($event)\"\n displayProperty=\"softwareType\"\n [ngStyle]=\"style\"\n>\n <c8y-li\n *ngIf=\"showClearSelectionOption\"\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n (click)=\"clearSoftware()\"\n [active]=\"!softwareTypeMO?.softwareType\"\n >\n <span>{{ clearSelectionOptionLabel | translate }}</span>\n </c8y-li>\n <c8y-li\n *c8yFor=\"\n let software of softwaresResult$;\n pipe: filterPipe;\n loadMore: 'auto';\n notFound: notFoundTemplateToUse\n \"\n class=\"p-l-8 p-r-8 c8y-list__item--link\"\n (click)=\"selectSoftware(software)\"\n [active]=\"softwareTypeMO?.softwareType === software.softwareType\"\n >\n <c8y-highlight\n [text]=\"software.softwareType || '--'\"\n [pattern]=\"search$ | async\"\n ></c8y-highlight>\n </c8y-li>\n <ng-template #notFoundTypeAddNewTemplate>\n <c8y-li class=\"bg-level-2 p-8\" *ngIf=\"(search$ | async)?.length > 0\">\n <span translate>No match found.</span>\n <button\n title=\"{{ 'Add new`software type`' | translate }}\"\n type=\"button\"\n class=\"btn btn-primary btn-xs m-l-8\"\n translate\n >\n Add new`software type`\n </button>\n </c8y-li>\n </ng-template>\n <ng-template #notFoundTypeTemplate>\n <c8y-li\n class=\"bg-level-2 p-8\"\n *ngIf=\"(search$ | async)?.length > 0 && (softwaresResult$ | async)?.data?.length === 0\"\n >\n <span translate>No match found. Refine your search terms or check your spelling.</span>\n </c8y-li>\n </ng-template>\n</c8y-typeahead>\n" }]
}], ctorParameters: () => [{ type: RepositoryService }, { type: i0.ChangeDetectorRef }, { type: i2$1.TranslateService }], propDecorators: { softwareTypeMO: [{
type: Input
}], disabled: [{
type: Input
}], style: [{
type: Input
}], required: [{
type: Input
}], placeholder: [{
type: Input
}], emitResultsOnly: [{
type: Input
}], showBtnInNotFoundMessage: [{
type: Input
}], allowFreeEntries: [{
type: Input
}], showClearSelectionOption: [{
type: Input
}], clearSelectionOptionLabel: [{
type: Input
}], presetSoftwareTypes: [{
type: Input
}], deviceSoftwareTypeModel: [{
type: ViewChild,
args: ['deviceSoftwareTypeModel']
}], notFoundTypeAddNewTemplate: [{
type: ViewChild,
args: ['notFoundTypeAddNewTemplate', { static: true }]
}], notFoundTypeTemplate: [{
type: ViewChild,
args: ['notFoundTypeTemplate', { static: true }]
}], onSelectSoftware: [{
type: Output
}] } });
class SharedRepositoryModule {
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SharedRepositoryModule, deps: [], target: i0.ɵɵFactoryTarget.NgModule }); }
static { this.ɵmod = i0.ɵɵngDeclareNgModule({ minVersion: "14.0.0", version: "20.3.15", ngImport: i0, type: SharedRepositoryModule, imports: [RepositorySelectModalComponent, FileDownloadComponent, SoftwareTypeComponent], exports: [RepositorySelectModalComponent, FileDownloadComponent, SoftwareTypeComponent] }); }
static { this.ɵinj = i0.ɵɵngDeclareInjector({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SharedRepositoryModule, providers: [RepositoryService, OperationRealtimeService], imports: [RepositorySelectModalComponent, SoftwareTypeComponent] }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SharedRepositoryModule, decorators: [{
type: NgModule,
args: [{
imports: [RepositorySelectModalComponent, FileDownloadComponent, SoftwareTypeComponent],
providers: [RepositoryService, OperationRealtimeService],
exports: [RepositorySelectModalComponent, FileDownloadComponent, SoftwareTypeComponent]
}]
}] });
/**
* Generated bundle index. Do not edit.
*/
export { DescriptionGridColumn, DeviceConfigurationOperation, DeviceTypeCellRendererComponent, DeviceTypeGridColumn, FileCellRendererComponent, FileDownloadComponent, FileGridColumn, LinkRenderType, PRODUCT_EXPERIENCE_REPOSITORY_SHARED, REPOSITORY_BINARY_TYPES, RepositoryItemNameCellRendererComponent, RepositoryItemNameGridColumn, RepositorySelectModalComponent, RepositoryService, RepositoryType, SharedRepositoryModule, SoftwareTypeComponent, TypeCellRendererComponent, TypeFilteringFormRendererComponent, TypeGridColumn, VersionsCellRendererComponent, VersionsGridColumn };
//# sourceMappingURL=c8y-ngx-components-repository-shared.mjs.map