UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

1,293 lines (1,288 loc) 218 kB
import { gettext } from '@c8y/ngx-components/gettext'; import * as i0 from '@angular/core'; import { InjectionToken, inject, Injectable, Directive, Injector, runInInjectionContext, Pipe, viewChildren, ViewContainerRef, effect, reflectComponentType, Input, Component, viewChild, EventEmitter, ContentChild, Output, forwardRef } from '@angular/core'; import { InventoryService } from '@c8y/client'; import * as i2 from '@c8y/ngx-components'; import { hookGeneric, ExtensionPointForPlugins, GroupService, fromTriggerOnce, getInjectedHooks, stateToFactory, AssetTypesRealtimeService, AlertService, FeatureCacheService, AssetDefinitionsService, isPromise, IconDirective, C8yTranslatePipe, ListGroupModule, EmptyStateComponent, FormsModule as FormsModule$1, LoadMoreComponent, TabComponent, TabsOutletComponent, BottomDrawerRef, BottomDrawerService } from '@c8y/ngx-components'; import { isEqual, omit, isArray, isObjectLike, isEmpty, find, forOwn, get, isUndefined, cloneDeep } from 'lodash-es'; import { firstValueFrom, mergeMap, filter, take, map, distinctUntilChanged, shareReplay, BehaviorSubject, of, from, switchMap, isObservable, Subject, takeUntil, debounceTime } from 'rxjs'; import * as i1 from '@angular/router'; import { TranslateService } from '@ngx-translate/core'; import { NgFor, NgClass, NgTemplateOutlet, AsyncPipe, JsonPipe } from '@angular/common'; import { CdkDropList, CdkDrag } from '@angular/cdk/drag-drop'; import * as i1$1 from '@angular/forms'; import { NgForm, FormsModule, NG_VALUE_ACCESSOR } from '@angular/forms'; import * as i3 from 'ngx-bootstrap/tooltip'; import { TooltipModule } from 'ngx-bootstrap/tooltip'; import * as i4 from '@angular/cdk/tree'; import { CdkTreeModule } from '@angular/cdk/tree'; import { DataSource } from '@angular/cdk/collections'; import { BsModalRef, BsModalService } from 'ngx-bootstrap/modal'; import { MillerViewComponent } from '@c8y/ngx-components/assets-navigator'; const defaultAssetPropertyListConfig = { filterable: true, selectMode: 'none', expansionMode: 'expandedByDefault', showHeader: true, showValue: true, showKey: true, emptyStateContent: 'empty', allowAddingCustomProperties: false, inputPropertiesHandle: 'merge', allowDragAndDrop: false }; const defaultAssetProperties = [ { c8y_JsonSchema: { properties: { name: { type: 'string', label: 'Name' } } }, name: 'name', label: gettext('Name'), type: 'string', active: true, isEditable: true, isStandardProperty: true }, { c8y_JsonSchema: { properties: { id: { type: 'string', label: 'ID' } } }, name: 'id', label: gettext('ID'), type: 'string', active: true, isEditable: false, isStandardProperty: true }, { c8y_JsonSchema: { properties: { type: { type: 'string', label: 'Type' } } }, name: 'type', label: gettext('Type'), type: 'string', active: true, isEditable: false, isStandardProperty: true }, { c8y_JsonSchema: { properties: { owner: { type: 'string', label: 'Owner' } } }, name: 'owner', label: gettext('Owner'), type: 'string', isEditable: false, isStandardProperty: true }, { c8y_JsonSchema: { properties: { lastUpdated: { type: 'string', label: 'Last updated' } } }, name: 'lastUpdated', label: gettext('Last updated'), type: 'string', isEditable: false, isStandardProperty: true }, { label: gettext('Active alarms status'), type: 'object', isEditable: false, isStandardProperty: true, name: 'c8y_ActiveAlarmsStatus', c8y_JsonSchema: { properties: { c8y_ActiveAlarmsStatus: { key: 'c8y_ActiveAlarmsStatus', type: 'object', label: 'Active alarms status', properties: { critical: { title: 'Critical', type: 'number' }, major: { title: 'Major', type: 'number' }, minor: { title: 'Minor', type: 'number' }, warning: { title: 'Warning', type: 'number' } } } } } }, { label: gettext('Address'), type: 'object', isEditable: true, isStandardProperty: true, name: 'c8y_Address', c8y_JsonSchema: { properties: { c8y_Address: { key: 'c8y_Address', type: 'object', label: 'Address', properties: { street: { title: 'Street', type: 'string' }, city: { title: 'City', type: 'string' }, cityCode: { title: 'City code', type: 'string' }, territory: { title: 'Territory', type: 'string' }, region: { title: 'Region', type: 'string' }, country: { title: 'Country', type: 'string' } } } } } }, { label: gettext('Agent'), type: 'object', isEditable: true, isStandardProperty: true, name: 'c8y_Agent', c8y_JsonSchema: { properties: { c8y_Agent: { key: 'c8y_Agent', type: 'object', label: 'Agent', properties: { name: { title: 'Name', type: 'string' }, version: { title: 'Version', type: 'string' }, url: { title: 'URL', type: 'string' }, maintainer: { title: 'Maintainer', type: 'string' } } } } } }, { label: gettext('Availability'), type: 'object', isEditable: false, isStandardProperty: true, name: 'c8y_Availability', c8y_JsonSchema: { properties: { c8y_Availability: { key: 'c8y_Availability', type: 'object', label: 'Availability', properties: { status: { title: 'Status', type: 'string' }, lastMessage: { title: 'Last message', type: 'string', printFormat: 'datetime' } } } } } }, { label: gettext('Connection'), type: 'object', isEditable: false, isStandardProperty: true, name: 'c8y_Connection', c8y_JsonSchema: { properties: { c8y_Connection: { key: 'c8y_Connection', type: 'object', label: 'Connection', properties: { status: { title: 'Status', type: 'string' } } } } } }, { label: gettext('Communication mode'), type: 'object', isEditable: true, isStandardProperty: true, name: 'c8y_CommunicationMode', c8y_JsonSchema: { properties: { c8y_CommunicationMode: { key: 'c8y_CommunicationMode', type: 'object', label: 'Communication mode', properties: { mode: { title: 'Mode', type: 'string' } } } } } }, { label: gettext('Firmware'), type: 'object', isEditable: false, isStandardProperty: true, name: 'c8y_Firmware', c8y_JsonSchema: { properties: { c8y_Firmware: { key: 'c8y_Firmware', type: 'object', label: 'Firmware', properties: { moduleVersion: { title: 'Module version', type: 'string' }, name: { title: 'Name', type: 'string' }, version: { title: 'Version', type: 'string' }, url: { title: 'URL', type: ['string', 'null'] } } } } } }, { label: gettext('Hardware'), type: 'object', isEditable: true, isStandardProperty: true, name: 'c8y_Hardware', c8y_JsonSchema: { properties: { c8y_Hardware: { key: 'c8y_Hardware', type: 'object', label: 'Hardware', properties: { model: { title: 'Model', type: 'string' }, serialNumber: { title: 'Serial number', type: 'string' }, revision: { title: 'Revision', type: 'string' } } } } } }, { label: gettext('LPWAN device'), type: 'object', isEditable: false, isStandardProperty: true, name: 'c8y_LpwanDevice', c8y_JsonSchema: { properties: { c8y_LpwanDevice: { key: 'c8y_LpwanDevice', type: 'object', label: 'LPWAN device', properties: { provisioned: { title: 'Provisioned', type: 'boolean' } } } } } }, { label: gettext('Mobile'), type: 'object', isEditable: true, isStandardProperty: true, name: 'c8y_Mobile', c8y_JsonSchema: { properties: { c8y_Mobile: { key: 'c8y_Mobile', type: 'object', label: 'Mobile', properties: { cellId: { title: 'Cell ID', type: ['string', 'null'] }, connType: { title: 'Connection type', type: 'string', readOnly: true }, currentOperator: { title: 'Current operator', type: 'string', readOnly: true }, currentBand: { title: 'Current band', type: 'string', readOnly: true }, ecn0: { title: 'ECN0', type: 'string', readOnly: true }, iccid: { title: 'ICCID', type: ['string', 'null'] }, imei: { title: 'IMEI', type: ['string', 'null'] }, imsi: { title: 'IMSI', type: ['string', 'null'] }, lac: { title: 'LAC', type: ['string', 'null'] }, mcc: { title: 'MCC', type: ['string', 'null'] }, mnc: { title: 'MNC', type: ['string', 'null'] }, msisdn: { title: 'MSISDN', type: 'string' }, rcsp: { title: 'RCSP', type: 'string', readOnly: true }, rscp: { title: 'RSCP', type: 'string', readOnly: true }, rsrp: { title: 'RSRP', type: 'string', readOnly: true }, rsrq: { title: 'RSRQ', type: 'string', readOnly: true }, rssi: { title: 'RSSI', type: 'string', readOnly: true } } } } } }, { name: 'c8y_Notes', label: 'Notes', type: 'string', isEditable: true, isStandardProperty: true, c8y_JsonSchema: { properties: { c8y_Notes: { type: 'string', label: 'Notes', 'x-schema-form': { type: 'textarea' } } } } }, { label: gettext('Position'), type: 'object', isEditable: true, isStandardProperty: true, name: 'c8y_Position', c8y_JsonSchema: { properties: { c8y_Position: { key: 'c8y_Position', type: 'object', label: 'Position', properties: { lat: { title: 'Latitude', type: 'number' }, lng: { title: 'Longitude', type: 'number' }, alt: { title: 'Altitude', type: 'number' } } } } } }, { label: gettext('Required availability'), type: 'object', isEditable: true, isStandardProperty: true, name: 'c8y_RequiredAvailability', c8y_JsonSchema: { properties: { c8y_RequiredAvailability: { key: 'c8y_RequiredAvailability', type: 'object', label: 'Required availability', properties: { responseInterval: { title: 'Response interval', description: 'Takes a value between -32768 and 32767 minutes (a negative value indicates that the device is under maintenance).', type: 'integer', minimum: -32768, maximum: 32767 } } } } } }, { label: gettext('Software'), type: 'object', isEditable: false, isStandardProperty: true, name: 'c8y_Software', c8y_JsonSchema: { properties: { c8y_Software: { key: 'c8y_Software', type: 'object', label: 'Software', properties: { name: { title: 'Name', type: 'string' }, version: { title: 'Version', type: 'string' }, url: { title: 'URL', type: ['string', 'null'] } } } } } }, { label: gettext('Network'), type: 'object', isEditable: true, isStandardProperty: true, name: 'c8y_Network', c8y_JsonSchema: { properties: { c8y_Network: { key: 'c8y_Network', type: 'object', label: 'Network', properties: { c8y_DHCP: { title: 'DHCP', type: 'object', printFormat: 'hidden', name: 'c8y_DHCP', properties: { addressRange: { title: 'Address range', type: 'object', name: 'addressRange', printFormat: 'hidden', properties: { start: { title: 'Start', type: 'string' }, end: { title: 'End', type: 'string' } } }, dns1: { title: 'DNS 1', type: 'string' }, dns2: { title: 'DNS 2', type: 'string' }, enabled: { title: 'Enabled', type: 'integer' } } }, c8y_LAN: { title: 'LAN', type: 'object', name: 'c8y_LAN', printFormat: 'hidden', properties: { enabled: { title: 'Enabled', type: 'integer' }, ip: { title: 'IP', type: 'string' }, mac: { title: 'MAC', type: 'string' }, name: { title: 'Name', type: 'string' }, netmask: { title: 'Netmask', type: 'string' } } }, c8y_WAN: { title: 'WAN', type: 'object', name: 'c8y_WAN', printFormat: 'hidden', properties: { apn: { title: 'APN', type: 'string' }, authType: { title: 'Auth type', type: 'string' }, ip: { title: 'IP', type: 'string' }, password: { title: 'Password', type: 'string' }, simStatus: { title: 'SIM status', type: 'string' }, username: { title: 'Username', type: 'string' } } } } } } } } ]; const RESULT_TYPES = { VALUE: { name: 'VALUE', value: 1, label: gettext('Only value') }, VALUE_UNIT: { name: 'VALUE_UNIT', value: 2, label: gettext('Value and unit') }, VALUE_UNIT_TIME: { name: 'VALUE_UNIT_TIME', value: 3, label: gettext('Value, unit and time') } }; const HOOK_COMPUTED_PROPERTY = new InjectionToken('HOOK_COMPUTED_PROPERTY'); function hookComputedProperty(property, options) { return hookGeneric(property, HOOK_COMPUTED_PROPERTY, options); } class ComputedPropertiesService extends ExtensionPointForPlugins { constructor(rootInjector, router, plugins) { super(rootInjector, plugins); this.router = router; this.groupService = inject(GroupService); this.items$ = this.setupItemsObservable(); } /** * Returns the current state. * @readonly * @returns The current set of computed properties. */ get state() { return this.state$.value; } add(propertyDef) { this.state.add(propertyDef); this.emitNewState(); } getByName(name) { if (!name) { return Promise.resolve(undefined); } return firstValueFrom(this.items$.pipe(mergeMap((propertyDefs) => propertyDefs), filter((propertyDef) => propertyDef.name === name), take(1))); } getByContext(asset) { if (!asset) { return Promise.resolve([]); } const computedPropertyContextType = this.getTypeOfContext(asset); return firstValueFrom(this.items$.pipe(map((propertyDefs) => propertyDefs.filter((propertyDef) => { if (!propertyDef.contextType) { return true; } return propertyDef.contextType.includes(computedPropertyContextType); })))); } getTypeOfContext(context) { const isEvent = (item) => !('severity' in item); const isAlarm = (item) => 'severity' in item; if (this.groupService.isDevice(context)) { return 'device'; } else if (this.groupService.isGroup(context) && !this.groupService.isAsset(context)) { return 'group'; } else if (this.groupService.isAsset(context)) { return 'asset'; } else if (isAlarm(context)) { return 'alarm'; } else if (isEvent(context)) { return 'event'; } } setupItemsObservable() { return fromTriggerOnce(this.router, this.refresh$, [ getInjectedHooks(HOOK_COMPUTED_PROPERTY, this.injectors), () => this.factories, stateToFactory(this.state$) ]).pipe(distinctUntilChanged(), shareReplay(1)); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ComputedPropertiesService, deps: [{ token: i0.Injector }, { token: i1.Router }, { token: i2.PluginsResolveService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ComputedPropertiesService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: ComputedPropertiesService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i0.Injector }, { type: i1.Router }, { type: i2.PluginsResolveService }] }); /** * Service for managing asset properties. */ class AssetPropertiesService { constructor() { this.FRAGMENTS_TO_OMIT = [ 'additionParents', 'assetParents', 'deviceParents', 'childAdditions', 'childAssets', 'childDevices', 'c8y_IsDevice', '__children', 'c8y_ui', 'self', 'parent', 'c8y_DataPoint', 'c8y_Kpi_Migrated', /^c8y_Dashboard!\d+/ ]; this.MODEL_API_FEATURE_KEY = 'dtm.asset-api'; this.inventoryService = inject(InventoryService); this.assetTypesRealtimeService = inject(AssetTypesRealtimeService); this.groupService = inject(GroupService); this.alert = inject(AlertService); this.computedPropertiesService = inject(ComputedPropertiesService); this.featureCacheService = inject(FeatureCacheService); this.assetDefinitionsService = inject(AssetDefinitionsService); this.translateService = inject(TranslateService); } /** * Filters added properties to only include those compatible with the given asset. * Currently only checks compatibility for computed properties. * @param allAddedProperties All properties that have been added by the user * @param asset The current asset context * @returns Promise resolving to properties compatible with the asset */ async filterCompatibleProperties(allAddedProperties, asset) { if (!asset || !allAddedProperties.length) { return allAddedProperties; } let availableComputedPropertyNames = null; const computedProperties = allAddedProperties.filter(prop => prop.computed); if (computedProperties.length > 0) { const availableComputedProperties = await this.computedPropertiesService.getByContext(asset); availableComputedPropertyNames = new Set(availableComputedProperties.map(({ prop }) => prop.name)); } const compatibleProperties = []; for (const property of allAddedProperties) { if (await this.isPropertyAvailable(property, availableComputedPropertyNames)) { compatibleProperties.push(property); } } return compatibleProperties; } /** * Retrieves properties for an asset. * First, it tries to get properties from definitions API (if enabled), * then falls back to asset library. * @param asset The asset for which to retrieve custom properties. * @returns A promise resolving to the list of custom properties. */ async getAssetProperties(asset) { if (!asset?.type) { return []; } const isModelApiEnabled = await firstValueFrom(this.featureCacheService.getFeatureState(this.MODEL_API_FEATURE_KEY)); if (isModelApiEnabled) { try { const assetDefinition = await this.assetDefinitionsService.getByIdentifier(asset.type); if (assetDefinition) { return this.categorizePropertiesFromDefinition(assetDefinition); } } catch { // do nothing- fallback to asset library } } const assetType = await firstValueFrom(this.assetTypesRealtimeService.getAssetTypeByName$(asset.type)); if (assetType) { const { data } = await this.inventoryService.childAdditionsList(assetType, { pageSize: 2000, query: "$filter=(has('c8y_IsAssetProperty'))" }); return this.categorizeCustomProperties(data); } else { return []; } } /** * Retrieves the initial set of properties for an asset, based on its type. * @param asset The asset for which to retrieve properties. * @returns A promise resolving to the list of initial properties. */ async getInitialProperties(asset) { if (!asset) { return []; } else if (this.groupService.isDevice(asset)) { return await this.getDeviceProperties(asset); } else if (this.groupService.isGroup(asset) && !this.groupService.isAsset(asset)) { return await this.getGroupProperties(asset); } else if (this.groupService.isAsset(asset)) { return await this.getAssetProperties(asset); } return []; } /** * Retrieves properties for a device asset. * @param asset The device asset for which to retrieve properties. * @returns A promise resolving to the list of device properties. */ async getDeviceProperties(asset) { return this.getManagedObjectProperties(asset); } /** * Retrieves properties for a group asset. * @param asset The group asset for which to retrieve properties. * @returns A promise resolving to the list of group properties. */ async getGroupProperties(asset) { return this.getManagedObjectProperties(asset); } /** * Categorizes custom properties into simple and complex types. * @param properties The custom properties to categorize. * @returns The categorized custom properties. */ categorizeCustomProperties(properties) { const { simple, complex } = properties.reduce((acc, property) => { const schema = property.c8y_JsonSchema.properties[property.name]; if (schema.type === 'object') { acc.complex.push(property); } else { acc.simple.push(property); } return acc; }, { simple: [], complex: [] }); return [...simple, ...complex]; } /** * Categorizes and flattens hierarchical properties into simple and complex types. * @param properties The hierarchical properties to categorize and flatten. * @returns The categorized and flattened properties. */ categorizeAndFlattenHierarchicalProperties(properties) { const sortedProperties = [...properties].sort((a, b) => { const aLabel = (a.label || a.name || '').toLowerCase(); const bLabel = (b.label || b.name || '').toLowerCase(); return aLabel.localeCompare(bLabel); }); const result = sortedProperties.reduce((acc, property) => { property.active = false; if (property.computed) { acc.computed.push(property); } else if (this.isComplexProperty(property)) { acc.complex.push(property); this.addNestedProperties(property, acc.complex, true); } else { acc.simple.push(property); } return acc; }, { computed: [], simple: [], complex: [] }); return result; } /** * Checks if a property is complex (i.e., has nested properties). * @param property The property to check. * @returns True if the property is complex, false otherwise. */ isComplexProperty(property) { return (property.c8y_JsonSchema?.properties[property.name]?.type === 'object' || property.properties !== undefined); } /** * Checks if property is available based on provided available computed property names. * @param property The property to check. * @param availableComputedPropertyNames Set of available computed property names. * @returns True if the property is available, false otherwise. */ async isPropertyAvailable(property, availableComputedPropertyNames) { if (property.computed) { if (!availableComputedPropertyNames) { return false; } return availableComputedPropertyNames.has(property.name); } return true; } /** * Checks if two properties match for selection purposes. * @param property1 First property to compare. * @param property2 Second property to compare. * @param omitProperties Optional list of property keys to omit from comparison (e.g., ['instanceId','configuredAssetId']). * @returns True if properties match. */ propertiesMatch(property1, property2, omitProperties = []) { if (property1.name === property2.name) { const propertiesToOmit = ['active', ...omitProperties]; const areEqual = isEqual(omit(property1, propertiesToOmit), omit(property2, propertiesToOmit)); return areEqual; } return false; } /** * Retrieves custom properties from the properties library, optionally filtered by search text. * @param searchText Optional search text to filter properties. * @returns A promise resolving to the list of properties and paging information. */ async getPropertiesFromPropertiesLibrary(searchText) { const propertiesFromLibrary = []; const { data: propertiesMOs, paging } = await this.requestPropertiesFromPropertiesLibrary(searchText); propertiesMOs.forEach(prop => { const name = Object.keys(prop.c8y_JsonSchema.properties)[0]; propertiesFromLibrary.push({ c8y_JsonSchema: prop.c8y_JsonSchema, label: prop.name, name, type: prop.c8y_JsonSchema.properties[name].type }); }); return { propertiesFromLibrary, paging }; } /** * Filters properties with hierarchical search logic: * - Simple properties: match label or name * - Child properties: match child label/name AND if matches- match also parent * - Complex properties: match parent label/name AND if matches- all children * * @example * // Search "or" → matches children "Major", "Minor" → includes parent + matching children * filterPropertiesWithHierarchy([ * { name: 'c8y_ActiveAlarmsStatus', label: 'Active alarms status', type: 'object', ... }, * { name: 'major', label: 'Major', keyPath: ['c8y_ActiveAlarmsStatus', 'major'], ... }, * { name: 'minor', label: 'Minor', keyPath: ['c8y_ActiveAlarmsStatus', 'minor'], ... }, * { name: 'critical', label: 'Critical', keyPath: ['c8y_ActiveAlarmsStatus', 'critical'], ... } * ], 'or') * // Returns: [ * { name: 'c8y_ActiveAlarmsStatus', label: 'Active alarms status', type: 'object', ... }, * { name: 'major', label: 'Major', keyPath: ['c8y_ActiveAlarmsStatus', 'major'], ... }, * { name: 'minor', label: 'Minor', keyPath: ['c8y_ActiveAlarmsStatus', 'minor'], ... } * ] * * @example * // Search "address" → matches parent → includes parent + all children * filterPropertiesWithHierarchy([ * { name: 'c8y_Address', label: 'Address', type: 'object', ... }, * { name: 'street', label: 'Street', keyPath: ['c8y_Address', 'street'], ... }, * { name: 'city', label: 'City', keyPath: ['c8y_Address', 'city'], ... } * ], 'address') * // Returns: [ * { name: 'c8y_Address', label: 'Address', type: 'object', ... }, * { name: 'street', label: 'Street', keyPath: ['c8y_Address', 'street'], ... }, * { name: 'city', label: 'City', keyPath: ['c8y_Address', 'city'], ... } * ] * * @param flattenedProperties All flattened properties (simple and complex) * @param searchTerm Search term (case-insensitive, already lowercased) * @returns Filtered properties matching the search term */ filterPropertiesWithHierarchy(flattenedProperties, searchTerm) { if (!searchTerm) { return flattenedProperties; } const lowercasedSearchTerm = searchTerm.toLowerCase(); // Extract complex properties for parent lookups const complexProperties = flattenedProperties.filter(prop => this.isComplexProperty(prop)); const parentMap = new Map(complexProperties.map(p => [p.name, p])); return flattenedProperties .map(prop => { if (!this.isComplexProperty(prop) && !prop.keyPath) { return this.matchesSimpleProperty(prop, lowercasedSearchTerm); } else if (!this.isComplexProperty(prop) && prop.keyPath) { return this.matchesChildProperty(prop, lowercasedSearchTerm, parentMap); } else { return this.matchesComplexProperty(prop, lowercasedSearchTerm); } }) .filter(prop => prop !== null); } /** * Checks if a simple property (which is not a child of a complex property) matches the search term. * @param prop The property to check. * @param searchTerm The search term (already lowercased). * @returns The property if it matches, otherwise null. */ matchesSimpleProperty(prop, searchTerm) { const matches = prop.name.toLowerCase().includes(searchTerm) || prop.label.toLowerCase().includes(searchTerm) || this.translateService.instant(prop.label).toLowerCase().includes(searchTerm); return matches ? prop : null; } /** * Checks if a simple property (which is a child of a complex property) matches the search term and if not- check if parent complex property matches. * @param prop The property to check. * @param searchTerm The search term (already lowercased). * @param parentMap A map of parent complex properties. * @returns The property if it matches, otherwise null. */ matchesChildProperty(prop, searchTerm, parentMap) { // Check if child property matches const childMatches = prop.name.toLowerCase().includes(searchTerm) || prop.label.toLowerCase().includes(searchTerm) || this.translateService.instant(prop.label).toLowerCase().includes(searchTerm); if (childMatches) { return prop; } // Check if parent complex property matches const parentName = prop.keyPath[0]; const parentProp = parentMap.get(parentName); if (parentProp) { const parentMatches = parentProp.name.toLowerCase().includes(searchTerm) || parentProp.label.toLowerCase().includes(searchTerm) || this.translateService.instant(parentProp.label).toLowerCase().includes(searchTerm); return parentMatches ? prop : null; } return null; } /** * Checks if a complex property matches the search term or if any of its child properties match the search term. * @param prop The complex property to check. * @param searchTerm The search term (already lowercased). * @returns The property if it matches, otherwise null. */ matchesComplexProperty(prop, searchTerm) { // Complex property const parentMatches = prop.name.toLowerCase().includes(searchTerm) || prop.label.toLowerCase().includes(searchTerm) || this.translateService.instant(prop.label).toLowerCase().includes(searchTerm); if (parentMatches) { return prop; } // If parent doesn't match, check children properties- if any of the children match, then we include the parent complex property // Get nested properties from the schema const nestedPropertiesObj = prop.c8y_JsonSchema?.properties[prop.name]?.properties; if (nestedPropertiesObj) { // Check if any nested property matches the search term const matchingNestedProperty = Object.keys(nestedPropertiesObj).find(key => { const nestedProp = nestedPropertiesObj[key]; return (key.toLowerCase().includes(searchTerm) || (nestedProp.title || '').toLowerCase().includes(searchTerm) || (nestedProp.title && this.translateService .instant(nestedProp.title || '') .toLowerCase() .includes(searchTerm))); }); if (matchingNestedProperty) { return prop; } else { return null; } } else { return null; } } async requestPropertiesFromPropertiesLibrary(searchText) { let queryFilter = '(type eq c8y_JsonSchema) and (appliesTo.MANAGED_OBJECTS eq true)'; if (searchText?.trim()) { const escapedSearchText = searchText.replace(/'/g, "''"); queryFilter += ` and (name eq '*${escapedSearchText}*')`; } const query = `$filter=(${queryFilter})`; const filter = { pageSize: 20, revert: true, query, withTotalPages: true, withTotalElements: true }; try { const results = await this.inventoryService.list(filter); return results; } catch (error) { this.alert.addServerFailure(error); return null; } } addNestedProperties(parentProperty, complexProperties, sortChildren = false) { const schema = parentProperty.c8y_JsonSchema?.properties[parentProperty.name]; if (!schema?.properties) { return; } this.flattenProperties(schema, complexProperties, parentProperty.name, [], sortChildren); } flattenProperties(schema, result, parentName, parentPath = [], sortChildren = false) { const properties = schema.properties?.[parentName]?.properties || schema.properties; let entries = Object.entries(properties); if (sortChildren) { entries = entries.sort((a, b) => { const aLabel = (a[1].title || a[1].label || a[1].name || a[0] || '').toLowerCase(); const bLabel = (b[1].title || b[1].label || b[1].name || b[0] || '').toLowerCase(); return aLabel.localeCompare(bLabel); }); } entries.forEach(([key, property]) => { const path = parentPath.includes(parentName) ? parentPath : [...parentPath, parentName]; result.push({ ...property, name: key, label: property.label || property.title || key, keyPath: [...path, key] }); if (property.properties) { this.flattenProperties({ properties: { [key]: property } }, result, key, [...path, key], sortChildren); } }); } getManagedObjectProperties(asset) { return this.extractFragments(asset); } extractFragments(object) { const properties = []; for (const [key, value] of Object.entries(object)) { if (this.shouldSkipFragment(key) || isArray(value) || (isObjectLike(value) && isEmpty(value))) { continue; } const newProp = { label: key, name: key, type: isObjectLike(value) ? 'object' : 'string', isEditable: true, c8y_JsonSchema: { properties: { [key]: { key: key, type: isObjectLike(value) ? 'object' : 'string', label: key, properties: {} } } } }; if (isObjectLike(value)) { this.addPropertyItem(newProp.c8y_JsonSchema.properties[key].properties, value); } properties.push(newProp); } return properties; } shouldSkipFragment(key) { return !!find(this.FRAGMENTS_TO_OMIT, (fragmentToOmit) => { if (fragmentToOmit instanceof RegExp) { return fragmentToOmit.test(key); } return fragmentToOmit === key; }); } addPropertyItem(properties, object) { if (!properties) { return; } forOwn(object, (value, key) => { properties[key] = { title: key, type: value ? typeof value : 'string' }; if (isObjectLike(value)) { properties[key].type = 'object'; this.addPropertyItem(properties[key]['properties'], value); } }); } /** * Converts asset definition from model API into AssetPropertyType array. * @param assetDefinition The asset definition to convert. * @returns The converted custom properties. */ categorizePropertiesFromDefinition(assetDefinition) { if (!assetDefinition?.jsonSchema?.properties) { return []; } const { properties, definitions } = assetDefinition.jsonSchema; const propertyObjects = []; for (const [propertyName, propertySchema] of Object.entries(properties)) { let resolvedSchema = propertySchema; // Resolve $ref if present if (propertySchema.$ref && definitions) { const refPath = propertySchema.$ref.replace('#/definitions/', ''); resolvedSchema = definitions[refPath] || propertySchema; } if (!resolvedSchema || !resolvedSchema.type) { continue; } const propertyObject = { name: propertyName, label: resolvedSchema.title || propertyName, c8y_JsonSchema: { properties: { [propertyName]: resolvedSchema } }, type: resolvedSchema.type }; propertyObjects.push(propertyObject); } return this.categorizeCustomProperties(propertyObjects); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AssetPropertiesService, deps: [], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AssetPropertiesService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.19", ngImport: i0, type: AssetPropertiesService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }] }); class FlatTreeDataSource extends DataSource { constructor() { super(); this._dataChange = new BehaviorSubject([]); } get data() { return this._dataChange.value; } set data(value) { this._dataChange.next(value); } connect() { return this._dataChange.asObservable(); } disconnect() { // No need to unsubscribe from the _dataChange subject since it's a BehaviorSubject } } class AssetPropertyActionDirective { constructor(template, elementRef, viewContainer) { this.template = template; this.elementRef = elementRef; this.viewContainer = viewContainer; } static { this.ɵfac =