@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
602 lines (597 loc) • 144 kB
JavaScript
import * as i0 from '@angular/core';
import { Injectable, EventEmitter, forwardRef, Component, Input, inject, DestroyRef, NgModule } from '@angular/core';
import { gettext } from '@c8y/ngx-components/gettext';
import * as i2 from '@c8y/ngx-components';
import { NavigatorNode, SelectModalComponent, ProductExperienceDirective, PRODUCT_EXPERIENCE_EVENT_SOURCE, ModalSelectionMode, TitleComponent, BreadcrumbComponent, BreadcrumbItemComponent, FormGroupComponent, C8yTranslateDirective, RequiredInputPlaceholderDirective, IconDirective, ListGroupComponent, ListItemComponent, ListItemIconComponent, ListItemBodyComponent, EmptyStateComponent, C8yTranslatePipe, BuiltInActionType, Status, ActionBarItemComponent, HelpComponent, DataGridComponent, EmptyStateContextDirective, ColumnDirective, MessagesComponent, MessageDirective, RealtimeButtonComponent, TypeaheadComponent, ForOfDirective, HighlightComponent, ManagedObjectRealtimeService, ViewContext, CoreModule, CommonModule, hookNavigator, hookRoute } from '@c8y/ngx-components';
import * as i1 from '@c8y/client';
import { QueriesUtil, OperationStatus } from '@c8y/client';
import { get, isEmpty, mapValues, omitBy, keyBy, uniqBy, keys, pickBy, sortBy, toArray, isNil, assign, concat, uniqWith, isEqual, has, cloneDeep } from 'lodash-es';
import * as i1$1 from '@c8y/ngx-components/repository/shared';
import { PRODUCT_EXPERIENCE_REPOSITORY_SHARED, RepositoryType, RepositorySelectModalComponent, RepositoryItemNameGridColumn, DeviceTypeGridColumn, SharedRepositoryModule } from '@c8y/ngx-components/repository/shared';
import { Subject, from, BehaviorSubject, pipe, combineLatest } from 'rxjs';
import { switchMap, map, take, distinctUntilChanged, shareReplay, filter, tap } from 'rxjs/operators';
import { AsyncPipe, NgIf, NgFor, NgTemplateOutlet, NgClass } from '@angular/common';
import * as i5 from '@angular/router';
import { RouterModule } from '@angular/router';
import * as i2$1 from 'ngx-bootstrap/dropdown';
import { BsDropdownModule } from 'ngx-bootstrap/dropdown';
import { PopoverDirective, PopoverModule } from 'ngx-bootstrap/popover';
import { TooltipDirective, TooltipModule } from 'ngx-bootstrap/tooltip';
import { ButtonsModule } from 'ngx-bootstrap/buttons';
import * as i7 from '@angular/forms';
import { FormsModule, ReactiveFormsModule } from '@angular/forms';
import * as i4 from 'ngx-bootstrap/modal';
import * as i4$1 from '@ngx-translate/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { OperationDetailsComponent, OperationDetailsModule } from '@c8y/ngx-components/operations/operation-details';
class DeviceProfileNavigationFactory {
async get() {
if (!this.nodeItem) {
this.nodeItem = new NavigatorNode({
label: gettext('Device profiles'),
path: '/device-profiles',
icon: 'c8y-device-profile',
parent: gettext('Management')
});
}
return this.nodeItem;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DeviceProfileNavigationFactory, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DeviceProfileNavigationFactory }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DeviceProfileNavigationFactory, decorators: [{
type: Injectable
}] });
class DeviceProfileService {
constructor(inventoryService, operationService, alertService) {
this.inventoryService = inventoryService;
this.operationService = operationService;
this.alertService = alertService;
this.dateFrom = new Date(0);
this.dateTo = new Date(Date.now() + 86400000); // 1 day in the future
this.NOT_INSTALLED_WARNING = gettext('Not installed on the device');
this.VERSION_MISSMATCH_WARNING = gettext('Version mismatch');
this.SAME_URL_WARNING = gettext('Installed configuration has the same URL but different name or type than the one in the profile');
this.queriesUtil = new QueriesUtil();
}
createDeviceProfile(deviceProfile) {
if (get(deviceProfile, 'c8y_Filter.type') === '') {
delete deviceProfile.c8y_Filter.type;
}
return this.inventoryService.create(deviceProfile);
}
/**
* Determines the available device profiles for a given device by considering device type
* and the supported software types declared by the devices. Because of limitations in the
* Inventory Query API the methods return profile that contain at least one of the supported
* software types and omits profiles having only non-supported software types. Resulting device
* profiles need to be further filtered on the client side to exclude the ones that contain
* non-supported software types next to the supported ones.
*
* @param device A device MO
* @param name Optional device profile name filter
* @returns Candidate device profiles that contain at least on software with supported type.
*/
getDeviceProfilesByDevice(device, name = null) {
const deviceTypeFilter = {
__or: [
{ 'c8y_Filter.type': device.type },
{ 'c8y_Filter.type': '' },
{ __not: { __has: 'c8y_Filter.type' } }
]
};
let softwareTypeFilter = {};
if (!isEmpty(device.c8y_SupportedSoftwareTypes)) {
softwareTypeFilter = {
__hasany: device.c8y_SupportedSoftwareTypes.map((type) => `softwareType!${type}`)
};
}
let query = this.queriesUtil.addAndFilter(deviceTypeFilter, softwareTypeFilter);
let profileNameFilter = {};
if (!isEmpty(name)) {
profileNameFilter = { name: `*${name}*` };
}
query = this.queriesUtil.addAndFilter(query, profileNameFilter);
return this.getDeviceProfiles(query);
}
/**
* @deprecated Use `getDeviceProfilesByDevice` instead as it also considers the supported software types.
*/
getDeviceProfilesByDeviceType(deviceType) {
const deviceTypeFilter = {
__or: [
{ 'c8y_Filter.type': deviceType },
{ 'c8y_Filter.type': '' },
{ __not: { __has: 'c8y_Filter.type' } }
]
};
return this.getDeviceProfiles(deviceTypeFilter);
}
getDeviceProfiles(andQuery) {
let query = {
type: 'c8y_Profile'
};
const filter = {
pageSize: 100,
withTotalPages: true
};
query = this.queriesUtil.addAndFilter(query, andQuery || {});
return this.inventoryService.listQuery(query, filter);
}
async getProfileOperation(deviceId) {
const filter = {
deviceId,
fragmentType: 'c8y_DeviceProfile',
dateFrom: this.dateFrom.toISOString(),
dateTo: this.dateTo.toISOString(),
revert: true,
pageSize: 1
};
const operation = (await this.operationService.list(filter)).data[0];
return operation && operation.status !== OperationStatus.SUCCESSFUL ? operation : undefined;
}
async createProfileOperation(device, deviceProfile) {
let operation;
const operationCfg = {
deviceId: device.id,
profileId: deviceProfile.id,
profileName: deviceProfile.name,
c8y_DeviceProfile: deviceProfile.c8y_DeviceProfile,
description: `Assign device profile ${deviceProfile.name} to device ${device.name}`
};
try {
const { data } = await this.operationService.create(operationCfg);
operation = data;
}
catch (ex) {
this.alertService.addServerFailure(ex);
}
return operation;
}
getFirmwareItems(device, selectedProfile) {
const deviceFirmware = device.c8y_Firmware;
const profileFirmware = get(selectedProfile, 'c8y_DeviceProfile.firmware');
const deviceItems = [];
const profileItems = [];
if (deviceFirmware) {
deviceItems.push(deviceFirmware);
}
if (profileFirmware) {
profileItems.push(profileFirmware);
}
return this.createProfileComparison(deviceItems, profileItems, 'name', 'version', null, this.getAlert('firmware'));
}
getSoftwareItems(device, selectedProfile) {
const deviceSoftware = device.c8y_SoftwareList;
const profileSoftware = get(selectedProfile, 'c8y_DeviceProfile.software');
return this.createProfileComparison(deviceSoftware, profileSoftware, 'name', 'version', 'softwareType', this.getAlert('software'));
}
getConfigurationItems(device, selectedProfile) {
const deviceConfiguration = [];
Object.keys(device).forEach(key => {
if (key.slice(0, 18) === 'c8y_Configuration_') {
deviceConfiguration.push(device[key]);
}
});
const profileConfiguration = get(selectedProfile, 'c8y_DeviceProfile.configuration');
return this.createProfileComparison(deviceConfiguration, profileConfiguration, 'url', null, 'type', this.getAlert('configuration'));
}
/**
* Aligns device profile managed object's `softwareType!*` fragments to the software items
* included in the device profile. Removes obsolete software type fragments and adds new.
*
* @param profilePartial The device profile managed object which `softwareType!*` fragments will be adjusted.
* @returns The adjusted device profile managed object.
*/
alignSoftwareTypeFragments(profilePartial, profile) {
if (!profilePartial?.c8y_DeviceProfile?.software || !profile) {
return profilePartial;
}
const removedSoftwareTypes = mapValues(omitBy(profile, (_, key) => typeof key === 'string' && !key.startsWith('softwareType!')), () => null);
const softwareTypesToAdd = mapValues(keyBy(uniqBy(profilePartial.c8y_DeviceProfile.software, 'softwareType'), (profile) => `softwareType!${profile.softwareType}`), () => ({}));
const result = { ...profilePartial, ...removedSoftwareTypes, ...softwareTypesToAdd };
return result;
}
getSoftwareTypes(profile) {
return keys(pickBy(profile, (_, key) => typeof key === 'string' && key.startsWith('softwareType!'))).map(softwareType => softwareType.substr('softwareType!'.length));
}
getAlert(itemType) {
const notInstalled = (comparisionResult) => {
return !comparisionResult.device ? this.NOT_INSTALLED_WARNING : '';
};
switch (itemType) {
case 'firmware':
case 'software':
return (comparisionResult) => {
return comparisionResult.device &&
comparisionResult.profile &&
comparisionResult.device.itemDetails !== comparisionResult.profile.itemDetails
? this.VERSION_MISSMATCH_WARNING
: notInstalled(comparisionResult);
};
case 'configuration':
return (comparisionResult) => {
return comparisionResult.device &&
comparisionResult.profile &&
(comparisionResult.device.itemName !== comparisionResult.profile.itemName ||
comparisionResult.device.itemDetails !== comparisionResult.profile.itemDetails)
? this.SAME_URL_WARNING
: notInstalled(comparisionResult);
};
default:
return notInstalled;
}
}
createProfileComparison(deviceItems = [], profileItems = [], mergeByProperty, propertyNameWithDetails, propertyNameWithType, getAlert) {
const comparisonObj = this.createProfileComparisonFromDeviceItems(deviceItems, mergeByProperty, propertyNameWithDetails, propertyNameWithType);
const extendedComparisonObj = this.extendProfileComparisonWithProfileItems(comparisonObj, profileItems, mergeByProperty, propertyNameWithDetails, propertyNameWithType, getAlert);
return sortBy(toArray(extendedComparisonObj), 'name');
}
createProfileComparisonFromDeviceItems(deviceItems, mergeByProperty, propertyNameWithDetails, propertyNameWithType) {
return deviceItems.reduce((comapritionItem, deviceItem) => Object.assign(comapritionItem, {
[deviceItem[mergeByProperty]]: {
device: omitBy({
itemName: deviceItem.name,
itemDetails: deviceItem[propertyNameWithDetails],
itemType: deviceItem[propertyNameWithType],
itemUrl: deviceItem.url
}, isNil),
profile: undefined
}
}), {});
}
extendProfileComparisonWithProfileItems(comparisonObj, profileItems, mergeByProperty, propertyNameWithDetails, propertyNameWithType, getAlert) {
profileItems.forEach(profileItem => {
const comparisionResult = {
profile: omitBy({
itemName: profileItem.name,
itemDetails: profileItem[propertyNameWithDetails],
itemType: profileItem[propertyNameWithType],
itemUrl: profileItem.url
}, isNil),
device: comparisonObj[profileItem[mergeByProperty]]
? comparisonObj[profileItem[mergeByProperty]].device
: undefined
};
comparisionResult.comparisonAlert = getAlert(comparisionResult);
comparisonObj[profileItem[mergeByProperty]] = comparisionResult;
});
return comparisonObj;
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DeviceProfileService, deps: [{ token: i1.InventoryService }, { token: i1.OperationService }, { token: i2.AlertService }], target: i0.ɵɵFactoryTarget.Injectable }); }
static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DeviceProfileService }); }
}
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DeviceProfileService, decorators: [{
type: Injectable
}], ctorParameters: () => [{ type: i1.InventoryService }, { type: i1.OperationService }, { type: i2.AlertService }] });
class SelectConfigurationModalComponent {
constructor(repositoryService) {
this.repositoryService = repositoryService;
this.PRODUCT_EXPERIENCE = PRODUCT_EXPERIENCE_REPOSITORY_SHARED;
this.title = gettext('Select configuration');
this.load = new Subject();
this.configurations = this.load.pipe(switchMap(() => this.getItems()), map(({ data }) => this.aggregate(data)));
this.resultEmitter = new EventEmitter();
this.deviceTypeQuery = {};
this.searchQuery = {};
this.labels = { ok: gettext('Save') };
this.queriesUtil = new QueriesUtil();
}
search(searchTerm) {
if (!searchTerm) {
this.searchQuery = {};
}
else {
this.searchQuery = this.queriesUtil.addOrFilter({ name: `*${searchTerm}*` }, { configurationType: `*${searchTerm}*` });
}
this.load.next();
}
result(selectedItems) {
this.resultEmitter.emit(selectedItems);
}
getItems() {
return this.repositoryService.listRepositoryEntries(RepositoryType.CONFIGURATION, {
query: this.queriesUtil.addOrFilter(this.deviceTypeQuery, this.searchQuery),
params: { pageSize: 100 }
});
}
aggregate(mos) {
const selectedItems = this.selected;
return mos.reduce((acc, curr) => {
curr.configurationType = curr.configurationType || curr.name;
const selected = selectedItems && selectedItems.filter(val => val.url === curr.url).length > 0;
const selectModalOption = {
body: [{ value: curr.name }],
obj: curr,
selected
};
let selectModalObject = acc.find(val => val.body[0].value === curr.configurationType);
if (selectModalObject) {
selectModalObject.options.push(selectModalOption);
}
else {
selectModalObject = {
groupId: curr.id,
body: [{ value: curr.configurationType }],
options: [selectModalOption]
};
acc.push(selectModalObject);
}
return acc;
}, []);
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SelectConfigurationModalComponent, deps: [{ token: i1$1.RepositoryService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SelectConfigurationModalComponent, isStandalone: true, selector: "c8y-select-configuration-modal", providers: [
{
provide: PRODUCT_EXPERIENCE_EVENT_SOURCE,
useExisting: forwardRef(() => SelectConfigurationModalComponent)
}
], ngImport: i0, template: "<c8y-select-modal\n [icon]=\"'gears'\"\n [title]=\"title\"\n [items]=\"configurations | async\"\n [mode]=\"'multi'\"\n (result)=\"result($event)\"\n (search)=\"search($event)\"\n [disableSelected]=\"true\"\n [labels]=\"labels\"\n c8yProductExperience\n inherit\n suppressDataOverriding\n [actionData]=\"{ component: PRODUCT_EXPERIENCE.SHARED.COMPONENTS.SELECT_CONFIGURATION_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: SelectConfigurationModalComponent, decorators: [{
type: Component,
args: [{ selector: 'c8y-select-configuration-modal', providers: [
{
provide: PRODUCT_EXPERIENCE_EVENT_SOURCE,
useExisting: forwardRef(() => SelectConfigurationModalComponent)
}
], imports: [SelectModalComponent, ProductExperienceDirective, AsyncPipe], template: "<c8y-select-modal\n [icon]=\"'gears'\"\n [title]=\"title\"\n [items]=\"configurations | async\"\n [mode]=\"'multi'\"\n (result)=\"result($event)\"\n (search)=\"search($event)\"\n [disableSelected]=\"true\"\n [labels]=\"labels\"\n c8yProductExperience\n inherit\n suppressDataOverriding\n [actionData]=\"{ component: PRODUCT_EXPERIENCE.SHARED.COMPONENTS.SELECT_CONFIGURATION_MODAL }\"\n></c8y-select-modal>\n" }]
}], ctorParameters: () => [{ type: i1$1.RepositoryService }] });
var DeviceProfileOperation;
(function (DeviceProfileOperation) {
DeviceProfileOperation["APPLY_PROFILE"] = "c8y_DeviceProfile";
})(DeviceProfileOperation || (DeviceProfileOperation = {}));
const PRODUCT_EXPERIENCE_DEVICE_PROFILE = {
EVENTS: {
REPOSITORY: 'deviceProfileRepository',
DEVICE_TAB: 'deviceProfileTab'
},
COMPONENTS: {
DEVICE_PROFILE_LIST: 'device-profile-list',
ADD_DEVICE_PROFILE: 'add-device-profile',
DEVICE_PROFILE: 'device-profile',
DEVICE_TAB_PROFILE: 'device-tab-profile'
},
ACTIONS: {
CANCEL: 'cancel',
CREATE: 'create',
REMOVE: 'remove',
ADD: 'add',
SAVE: 'save',
ASSIGN_DEVICE_PROFILE: 'assignDeviceProfile'
},
RESULTS: {
ADD_SOFTWARE: 'addSoftware'
},
FRAGMENTS: {
FIRMWARE: 'firmware',
SOFTWARE: 'software',
CONFGIURATION: 'configuration'
}
};
class DeviceProfileComponent {
constructor(route, alertService, inventoryService, bsModal, repositoryService, deviceProfileService) {
this.route = route;
this.alertService = alertService;
this.inventoryService = inventoryService;
this.bsModal = bsModal;
this.repositoryService = repositoryService;
this.deviceProfileService = deviceProfileService;
this.DEVICE_TYPE_POPOVER = gettext('The device profile will be available for assignments on devices of the specified type. Otherwise, it will be available for all device types.');
this.DEVICE_TYPE_DISABLED_POPOVER = gettext('Device type cannot be changed on profiles with already defined firmware, software or configuration since they may not be applicable to devices of the new device type.');
this.PRODUCT_EXPERIENCE = PRODUCT_EXPERIENCE_DEVICE_PROFILE;
this.productExperienceEvent = {
eventName: PRODUCT_EXPERIENCE_DEVICE_PROFILE.EVENTS.REPOSITORY,
data: {
component: PRODUCT_EXPERIENCE_DEVICE_PROFILE.COMPONENTS.DEVICE_PROFILE
}
};
this.queriesUtil = new QueriesUtil();
}
async ngOnInit() {
const profileId = this.route.snapshot.paramMap.get('id');
this.deviceProfile = (await this.getDeviceProfile(profileId));
if (this.deviceProfile) {
this.profileName = this.deviceProfile.name;
if (!this.deviceProfile.c8y_DeviceProfile.software) {
this.deviceProfile.c8y_DeviceProfile.software = [];
}
if (!this.deviceProfile.c8y_DeviceProfile.configuration) {
this.deviceProfile.c8y_DeviceProfile.configuration = [];
}
}
}
addFirmware() {
const initialState = {
deviceTypeQuery: this.getDeviceTypeQuery(RepositoryType.FIRMWARE),
repositoryType: RepositoryType.FIRMWARE,
repositoryEntriesWithVersionsFn$: modalDialog => this.getRepositoryEntriesWithVersions$(modalDialog.content.searchTerm, RepositoryType.FIRMWARE),
icon: 'c8y-firmware',
title: gettext('Select firmware'),
mode: ModalSelectionMode.SINGLE,
productExperienceEvent: this.productExperienceEvent
};
const modal = this.bsModal.show(RepositorySelectModalComponent, {
ignoreBackdropClick: true,
keyboard: false,
ariaLabelledBy: 'modal-title',
initialState
});
if (initialState.repositoryEntriesWithVersionsFn$) {
modal.content.repositoryEntriesWithVersions$ =
initialState.repositoryEntriesWithVersionsFn$(modal);
}
modal.content.load.next();
modal.content.resultEmitter.pipe(take(1)).subscribe(firmwareList => {
const [firmware] = firmwareList;
if (!firmware) {
return;
}
const deviceProfilePartial = {
c8y_DeviceProfile: this.deviceProfile.c8y_DeviceProfile || {}
};
assign(deviceProfilePartial.c8y_DeviceProfile, {
firmware: {
name: firmware.name,
version: firmware.version,
url: firmware.url,
isPatch: firmware.isPatch,
patchDependency: firmware.patchDependency
}
});
this.updateDeviceProfile(deviceProfilePartial);
});
}
getRepositoryEntriesWithVersions$(searchTerm$, repoType) {
return searchTerm$.pipe(distinctUntilChanged(), switchMap(searchTerm => this.repositoryService.listRepositoryEntries(repoType, {
query: this.getDeviceTypeQuery(repoType),
partialName: searchTerm.name,
params: { pageSize: 100 },
skipLegacy: true
})), map(({ data }) => data), map(mos => this.getAndAssignRepositoryBinaries(mos)), shareReplay(1));
}
getAndAssignRepositoryBinaries(mos) {
mos.forEach(mo => {
mo.versions = this.repositoryService.listBaseVersions(mo);
});
return mos;
}
addConfiguration() {
const modal = this.bsModal.show(SelectConfigurationModalComponent, {
ignoreBackdropClick: true,
keyboard: false,
ariaLabelledBy: 'modal-title',
initialState: {
productExperienceEvent: this.productExperienceEvent
}
});
modal.content.deviceTypeQuery = this.getDeviceTypeQuery(RepositoryType.CONFIGURATION);
modal.content.selected = this.deviceProfile.c8y_DeviceProfile.configuration;
modal.content.load.next();
modal.content.resultEmitter.pipe(take(1)).subscribe(selectedConfigurations => {
const selectedMapped = selectedConfigurations.map(selectedItem => {
return assign({
url: selectedItem.url,
name: selectedItem.name
}, selectedItem.configurationType ? { type: selectedItem.configurationType } : {});
});
const merged = concat(selectedMapped, this.deviceProfile.c8y_DeviceProfile.configuration || []);
const configuration = uniqWith(merged, (arrVal, othVal) => {
return arrVal.type && othVal.type && arrVal.type === othVal.type;
});
const deviceProfilePartial = {
c8y_DeviceProfile: this.deviceProfile.c8y_DeviceProfile || {}
};
assign(deviceProfilePartial.c8y_DeviceProfile, { configuration });
this.updateDeviceProfile(deviceProfilePartial);
});
}
addSoftware() {
const initialState = {
deviceTypeQuery: this.getDeviceTypeQuery(RepositoryType.SOFTWARE),
repositoryType: RepositoryType.SOFTWARE,
repositoryEntriesWithVersionsFn$: modalDialog => this.getRepositoryEntriesWithVersions$(modalDialog.content.searchTerm, RepositoryType.SOFTWARE),
selected: this.deviceProfile.c8y_DeviceProfile.software,
icon: 'c8y-tools',
title: gettext('Select software'),
mode: ModalSelectionMode.MULTI,
productExperienceEvent: this.productExperienceEvent
};
const modal = this.bsModal.show(RepositorySelectModalComponent, {
ignoreBackdropClick: true,
keyboard: false,
ariaLabelledBy: 'modal-title',
initialState
});
if (initialState.repositoryEntriesWithVersionsFn$) {
modal.content.repositoryEntriesWithVersions$ =
initialState.repositoryEntriesWithVersionsFn$(modal);
}
modal.content.load.next();
modal.content.resultEmitter.pipe(take(1)).subscribe(selectedSoftware => {
const selectedMapped = selectedSoftware.map(selectedItem => {
return {
name: selectedItem.name,
version: selectedItem.version,
url: selectedItem.url,
softwareType: selectedItem.softwareType,
action: 'install'
};
});
const merged = concat(selectedMapped, this.deviceProfile.c8y_DeviceProfile.software || []);
const software = uniqWith(merged, (arrVal, othVal) => {
return arrVal.name && othVal.name && arrVal.name === othVal.name;
});
const deviceProfilePartial = {
c8y_DeviceProfile: this.deviceProfile.c8y_DeviceProfile || {}
};
assign(deviceProfilePartial.c8y_DeviceProfile, { software });
this.updateDeviceProfile(deviceProfilePartial);
});
}
get isDeviceProfileEmpty() {
const isSoftware = this.deviceProfile.c8y_DeviceProfile.software &&
this.deviceProfile.c8y_DeviceProfile.software.length > 0;
const isFirmware = Boolean(this.deviceProfile.c8y_DeviceProfile.firmware);
const isConfiguration = this.deviceProfile.c8y_DeviceProfile.configuration &&
this.deviceProfile.c8y_DeviceProfile.configuration.length > 0;
return isSoftware || isFirmware || isConfiguration;
}
removeItem(removedItem, category) {
const deviceProfilePartial = {
c8y_DeviceProfile: this.deviceProfile.c8y_DeviceProfile
};
const filtered = deviceProfilePartial.c8y_DeviceProfile[category].filter(item => !isEqual(removedItem, item));
deviceProfilePartial.c8y_DeviceProfile[category] = filtered;
this.updateDeviceProfile(deviceProfilePartial);
}
removeFirmware() {
delete this.deviceProfile.c8y_DeviceProfile.firmware;
this.updateDeviceProfile({ c8y_DeviceProfile: this.deviceProfile.c8y_DeviceProfile });
}
async updateDeviceProfile(partialDeviceProfile) {
if (partialDeviceProfile.c8y_Filter && partialDeviceProfile.c8y_Filter.type === '') {
delete partialDeviceProfile.c8y_Filter.type;
}
Object.assign(partialDeviceProfile, { id: this.deviceProfile.id });
try {
const profileWithSoftwareTypeFragments = this.deviceProfileService.alignSoftwareTypeFragments(partialDeviceProfile, this.deviceProfile);
const { data } = await this.inventoryService.update(profileWithSoftwareTypeFragments);
this.deviceProfile = data;
this.profileName = this.deviceProfile.name;
this.alertService.success(gettext('Device profile changed.'));
}
catch (ex) {
this.alertService.addServerFailure(ex);
}
}
async getDeviceProfile(profileId) {
try {
const { data } = await this.inventoryService.detail(profileId);
return data;
}
catch (ex) {
this.alertService.addServerFailure(ex);
}
}
getDeviceTypeQuery(repositoryType) {
if (has(this.deviceProfile, 'c8y_Filter.type') &&
!isEmpty(this.deviceProfile.c8y_Filter.type)) {
if (repositoryType === RepositoryType.CONFIGURATION) {
return this.queriesUtil.addOrFilter({ deviceType: this.deviceProfile.c8y_Filter.type }, { __not: { __has: `deviceType` } });
}
else {
return this.queriesUtil.addOrFilter({ 'c8y_Filter.type': this.deviceProfile.c8y_Filter.type }, {
__or: [{ 'c8y_Filter.type': '' }, { __not: { __has: `c8y_Filter.type` } }]
});
}
}
return {};
}
static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: DeviceProfileComponent, deps: [{ token: i5.ActivatedRoute }, { token: i2.AlertService }, { token: i1.InventoryService }, { token: i4.BsModalService }, { token: i1$1.RepositoryService }, { token: DeviceProfileService }], target: i0.ɵɵFactoryTarget.Component }); }
static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: DeviceProfileComponent, isStandalone: true, selector: "c8y-device-profile", providers: [
{
provide: PRODUCT_EXPERIENCE_EVENT_SOURCE,
useExisting: forwardRef(() => DeviceProfileComponent)
}
], ngImport: i0, template: "<c8y-title>{{ profileName }}</c8y-title>\n\n<c8y-breadcrumb>\n <c8y-breadcrumb-item\n icon=\"c8y-management\"\n label=\"{{ 'Management' | translate }}\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n [icon]=\"'c8y-device-profile'\"\n [label]=\"'Device profiles' | translate\"\n [path]=\"'device-profiles'\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n icon=\"c8y-device-profile\"\n label=\"{{ profileName }}\"\n ></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<div\n class=\"row\"\n *ngIf=\"deviceProfile\"\n>\n <div class=\"col-lg-12 col-lg-max\">\n <div\n class=\"card card--fullpage\"\n *ngIf=\"deviceProfile\"\n >\n <div class=\"card-block bg-level-1 flex-no-shrink p-t-24 p-b-24 overflow-visible\">\n <div class=\"content-flex-70\">\n <div class=\"text-center\">\n <i class=\"c8y-icon-duocolor icon-48 c8y-icon c8y-icon-device-profile\"></i>\n <p>\n <small class=\"label label-info\">{{ 'Device profile' | translate }}</small>\n </p>\n </div>\n <div class=\"flex-grow col-10\">\n <div class=\"row\">\n <div class=\"col-md-4\">\n <form #editNameForm=\"ngForm\">\n <c8y-form-group>\n <label\n class=\"control-label\"\n translate\n >\n Name\n </label>\n <div class=\"input-group input-group-editable\">\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g. My device profile' | translate }}\"\n name=\"name\"\n type=\"text\"\n required\n [(ngModel)]=\"deviceProfile.name\"\n data-cy=\"device-profile--add-device-profile-name\"\n size=\"{{ deviceProfile.name?.length || 1 }}\"\n />\n <span></span>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Save' | translate }}\"\n type=\"button\"\n data-cy=\"device-profile--save\"\n (click)=\"\n updateDeviceProfile({ name: deviceProfile.name });\n editNameForm.form.markAsPristine()\n \"\n [disabled]=\"editNameForm.form.invalid\"\n c8yProductExperience\n inherit\n [actionData]=\"{ action: PRODUCT_EXPERIENCE.ACTIONS.SAVE }\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n </div>\n </c8y-form-group>\n </form>\n </div>\n <div class=\"col-md-4\">\n <form #editTypeForm=\"ngForm\">\n <c8y-form-group>\n <label class=\"control-label\">\n {{ 'Device type' | translate }}\n <button\n class=\"btn-help\"\n [attr.aria-label]=\"'Help' | translate\"\n [popover]=\"\n (DEVICE_TYPE_POPOVER | translate) +\n (isDeviceProfileEmpty\n ? ' ' + (DEVICE_TYPE_DISABLED_POPOVER | translate)\n : '')\n \"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </label>\n <div class=\"input-group input-group-editable\">\n <input\n class=\"form-control\"\n placeholder=\"{{ 'e.g.' | translate }} c8y_Linux\"\n name=\"type\"\n type=\"text\"\n [(ngModel)]=\"deviceProfile.c8y_Filter.type\"\n data-cy=\"device-profile--device-type\"\n size=\"{{ deviceProfile.c8y_Filter.type?.length || 14 }}\"\n [disabled]=\"isDeviceProfileEmpty\"\n />\n <span></span>\n <div class=\"input-group-btn\">\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Save' | translate }}\"\n type=\"button\"\n (click)=\"\n updateDeviceProfile({\n c8y_Filter: { type: deviceProfile.c8y_Filter.type }\n });\n editTypeForm.form.markAsPristine()\n \"\n [disabled]=\"isDeviceProfileEmpty\"\n c8yProductExperience\n inherit\n [actionData]=\"{ action: PRODUCT_EXPERIENCE.ACTIONS.SAVE }\"\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n </div>\n </c8y-form-group>\n </form>\n </div>\n </div>\n </div>\n </div>\n </div>\n <div class=\"inner-scroll\">\n <div class=\"card-header separator-top-bottom bg-content sticky-top\">\n <div class=\"card-icon\">\n <i\n class=\"c8y-icon-duocolor\"\n [c8yIcon]=\"'c8y-firmware'\"\n ></i>\n </div>\n <div\n class=\"card-title\"\n translate\n >\n Firmware\n </div>\n </div>\n <div\n class=\"card-block p-t-0\"\n *ngIf=\"deviceProfile.c8y_DeviceProfile.firmware\"\n >\n <c8y-list-group>\n <c8y-li>\n <c8y-li-icon>\n <i [c8yIcon]=\"'c8y-firmware'\"></i>\n </c8y-li-icon>\n <c8y-li-body class=\"content-flex-50 m-l-4\">\n <div class=\"col-4\">\n <span\n class=\"text-truncate\"\n title=\"{{ deviceProfile.c8y_DeviceProfile.firmware.name }}\"\n >\n <span\n class=\"text-label-small m-r-8\"\n translate\n >\n Name\n </span>\n {{ deviceProfile.c8y_DeviceProfile.firmware.name }}\n </span>\n </div>\n <div class=\"col-4\"></div>\n <div class=\"col-3 d-flex a-i-center\">\n <span\n class=\"text-truncate\"\n title=\"{{ deviceProfile.c8y_DeviceProfile.firmware.version }}\"\n >\n <span\n class=\"text-label-small m-r-4\"\n translate\n >\n Version\n </span>\n {{ deviceProfile.c8y_DeviceProfile.firmware.version }}\n </span>\n <button\n class=\"btn btn-danger btn-xs visible-xs m-l-auto m-r-8 m-t-8\"\n title=\"{{ 'Remove`firmware`' | translate }}\"\n type=\"button\"\n (click)=\"removeFirmware()\"\n c8yProductExperience\n inherit\n [actionData]=\"{\n action: PRODUCT_EXPERIENCE.ACTIONS.REMOVE,\n fragment: PRODUCT_EXPERIENCE.FRAGMENTS.FIRMWARE\n }\"\n >\n <i c8yIcon=\"minus-circle\"></i>\n {{ 'Remove`firmware`' | translate }}\n </button>\n </div>\n <div class=\"m-l-auto p-r-8 hidden-xs\">\n <button\n class=\"btn btn-dot showOnHover text-danger\"\n [attr.aria-label]=\"'Remove`firmware`' | translate\"\n tooltip=\"{{ 'Remove`firmware`' | translate }}\"\n placement=\"right\"\n type=\"button\"\n [delay]=\"500\"\n (click)=\"removeFirmware()\"\n c8yProductExperience\n inherit\n [actionData]=\"{\n action: PRODUCT_EXPERIENCE.ACTIONS.REMOVE,\n fragment: PRODUCT_EXPERIENCE.FRAGMENTS.FIRMWARE\n }\"\n >\n <i c8yIcon=\"minus-circle\"></i>\n </button>\n </div>\n </c8y-li-body>\n </c8y-li>\n </c8y-list-group>\n </div>\n <div\n class=\"card-block p-t-16\"\n *ngIf=\"!deviceProfile.c8y_DeviceProfile.firmware\"\n >\n <c8y-ui-empty-state\n class=\"p-t-16 d-block\"\n icon=\"c8y-firmware\"\n [title]=\"'No firmware defined.' | translate\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n </div>\n <div\n class=\"card-footer p-t-0\"\n *ngIf=\"!deviceProfile.c8y_DeviceProfile.firmware\"\n >\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Add firmware' | translate }}\"\n type=\"button\"\n data-cy=\"device-profile--Add-firmware-button\"\n (click)=\"addFirmware()\"\n c8yProductExperience\n inherit\n [actionData]=\"{\n action: PRODUCT_EXPERIENCE.ACTIONS.ADD,\n fragment: PRODUCT_EXPERIENCE.FRAGMENTS.FIRMWARE\n }\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Add firmware' | translate }}\n </button>\n </div>\n\n <div class=\"card-header separator-top-bottom sticky-top bg-component\">\n <div class=\"card-icon\">\n <i\n class=\"c8y-icon-duocolor\"\n [c8yIcon]=\"'c8y-tools'\"\n ></i>\n </div>\n <div\n class=\"card-title\"\n translate\n >\n Software\n </div>\n </div>\n <div\n class=\"card-block p-t-0\"\n *ngIf=\"deviceProfile.c8y_DeviceProfile.software?.length > 0\"\n >\n <c8y-list-group>\n <c8y-li *ngFor=\"let software of deviceProfile.c8y_DeviceProfile.software\">\n <c8y-li-icon>\n <i [c8yIcon]=\"'c8y-tools'\"></i>\n </c8y-li-icon>\n <c8y-li-body class=\"content-flex-50 m-l-4\">\n <div class=\"col-4\">\n <span\n class=\"text-truncate-wrap\"\n title=\"{{ software.name }}\"\n >\n <span\n class=\"text-label-small m-r-8\"\n translate\n >\n Name\n </span>\n {{ software.name }}\n </span>\n </div>\n <div class=\"col-4\">\n <span\n class=\"text-truncate-wrap\"\n title=\"{{ software.name }}\"\n >\n <span\n class=\"text-label-small m-r-8\"\n translate\n >\n Type\n </span>\n <span\n class=\"label label-info m-l-4\"\n *ngIf=\"!!software.softwareType\"\n >\n {{ software.softwareType }}\n </span>\n </span>\n </div>\n <div class=\"col-3 d-flex a-i-center\">\n <span\n class=\"text-truncate-wrap\"\n title=\"{{ software.version }}\"\n >\n <span\n class=\"text-label-small m-r-8\"\n translate\n >\n Version\n </span>\n {{ software.version }}\n </span>\n <button\n class=\"btn btn-danger btn-xs visible-xs m-l-auto m-r-8 m-t-8\"\n title=\"{{ 'Remove`software`' | translate }}\"\n type=\"button\"\n ((click)=\"removeItem(software, 'software')\"\n c8yProductExperience\n inherit\n [actionData]=\"{\n action: PRODUCT_EXPERIENCE.ACTIONS.REMOVE,\n fragment: PRODUCT_EXPERIENCE.FRAGMENTS.SOFTWARE\n }\"\n >\n <i c8yIcon=\"minus-circle\"></i>\n {{ 'Remove`software`' | translate }}\n </button>\n </div>\n <div class=\"m-l-auto p-r-8 hidden-xs\">\n <button\n class=\"btn btn-dot showOnHover text-danger\"\n [attr.aria-label]=\"'Remove`software`' | translate\"\n tooltip=\"{{ 'Remove`software`' | translate }}\"\n placement=\"right\"\n type=\"button\"\n [delay]=\"500\"\n (click)=\"removeItem(software, 'software')\"\n c8yProductExperience\n inherit\n [actionData]=\"{\n action: PRODUCT_EXPERIENCE.ACTIONS.REMOVE,\n fragment: PRODUCT_EXPERIENCE.FRAGMENTS.SOFTWARE\n }\"\n >\n <i\n c8yIcon=\"minus-circle\"\n data-cy=\"device-profile--Remove-icon\"\n ></i>\n </button>\n </div>\n </c8y-li-body>\n </c8y-li>\n </c8y-list-group>\n </div>\n <div\n class=\"card-block p-t-16\"\n *ngIf=\"deviceProfile.c8y_DeviceProfile.software?.length === 0\"\n >\n <c8y-ui-empty-state\n icon=\"c8y-tools\"\n [title]=\"'No software defined.' | translate\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n </div>\n <div class=\"card-footer p-t-0\">\n <button\n class=\"btn btn-default m-b-0\"\n title=\"{{ 'Add software' | translate }}\"\n type=\"button\"\n data-cy=\"device-profile--Add-software-button\"\n (click)=\"addSoftware()\"\n c8yProductExperience\n inherit\n [actionData]=\"{\n action: PRODUCT_EXPERIENCE.ACTIONS.ADD,\n fragment: PRODUCT_EXPERIENCE.FRAGMENTS.SOFTWARE\n }\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Add software' | translate }}\n </button>\n </div>\n\n <div class=\"card-header separator-top-bottom bg-component sticky-top\">\n <div class=\"card-icon\">\n <i\n class=\"c8y-icon-duocolor\"\n [c8yIcon]=\"'gears'\"\n ></i>\n </div>\n <div\n class=\"card-title\"\n translate\n >\n Configuration\n </div>\n </div>\n <div\n class=\"card-block p-t-0\"\n *ngIf=\"deviceProfile.c8y_DeviceProfile.configuration?.length > 0\"\n >\n <c8y-list-group>\n <c8y-li *ngFor=\"let configuration of deviceProfile.c8y_DeviceProfile.configuration\">\n <c8y-li-icon>\n <i [c8yIcon]=\"'gears'\"></i>\n </c8y-li-icon>\n <c8y-li-body class=\"content-flex-50\">\n <div class=\"col-4\">\n <span\n class=\"text-truncate\"\n title=\"{{ configuration.name }}\"\n >\n <span\n class=\"text-label-small m-r-8\"\n translate\n >\n Name\n </span>\n {{ configuration.name }}\n </span>\n </div>\n <div class=\"col-4\">\n <span\n class=\"text-label-small m-r-8\"\n translate\n >\n Type\n </span>\n <span class=\"label label-info\">{{ configuration.type }}</span>\n <button\n class=\"btn btn-danger btn-xs visible-xs m-l-auto m-r-8 m-t-8\"\n title=\"{{ 'Remove`configuration`' | translate }}\"\n type=\"button\"\n (click)=\"removeItem(configuration, 'configuration')\"\n c8yProductExperience\n inherit\n [actionData]=\"{\n action: PRODUCT_EXPERIENCE.ACTIONS.REMOVE,\n fragment: PRODUCT_EXPERIENCE.FRAGMENTS.CONFGIURATION\n }\"\n >\n <i c8yIcon=\"minus-circle\"></i>\n {{ 'Remove`configuration`' | translate }}\n </b