UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

1 lines • 218 kB
{"version":3,"file":"c8y-ngx-components-asset-properties.mjs","sources":["../../asset-properties/asset-properties.model.ts","../../asset-properties/computed-properties.service.ts","../../asset-properties/asset-properties.service.ts","../../asset-properties/asset-property-list/tree-data-source.ts","../../asset-properties/asset-property-list/asset-property-action.directive.ts","../../asset-properties/asset-property-list/asset-property-value.pipe.ts","../../asset-properties/asset-property-list/asset-property-icon.pipe.ts","../../asset-properties/asset-property-list/asset-property-icon-tooltip.pipe.ts","../../asset-properties/computed-properties-config/computed-properties-config.component.ts","../../asset-properties/computed-properties-config/computed-properties-config.component.html","../../asset-properties/asset-property-list/asset-property-collapse-button-title.pipe.ts","../../asset-properties/asset-property-list/asset-property-drag-drop.service.ts","../../asset-properties/asset-property-list/asset-property-list.component.ts","../../asset-properties/asset-property-list/asset-property-list.component.html","../../asset-properties/custom-properties-drawer/custom-properties-drawer.component.ts","../../asset-properties/custom-properties-drawer/custom-properties-drawer.component.html","../../asset-properties/custom-properties-drawer.service.ts","../../asset-properties/asset-property-selector-drawer/asset-property-selector-drawer.component.ts","../../asset-properties/asset-property-selector-drawer/asset-property-selector-drawer.component.html","../../asset-properties/c8y-ngx-components-asset-properties.ts"],"sourcesContent":["import { Injector, Type } from '@angular/core';\nimport { IAlarm, IEvent, IManagedObject } from '@c8y/client';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport { Observable } from 'rxjs';\n\ninterface ComputedPropertyContextMap {\n device: { type: 'device'; value: IManagedObject };\n group: { type: 'group'; value: IManagedObject };\n asset: { type: 'asset'; value: IManagedObject };\n alarm: { type: 'alarm'; value: IAlarm };\n event: { type: 'event'; value: IEvent };\n}\n\nexport type ValueCallbackMetadata = {\n /**\n * Mode of operation:\n * - 'singleValue': Get count only once (no real-time updates)\n * - 'realtime': Enable real-time updates (default)\n */\n mode?: 'singleValue' | 'realtime';\n /**\n * Observable to control real-time updates when mode is 'realtime'.\n * - true: Enable real-time updates (fetches current count from server and continues)\n * - false: Disable real-time updates (preserves current count, no server call)\n * If not provided, real-time runs continuously.\n */\n realtimeControl$?: Observable<boolean>;\n};\n\nexport type ComputedPropertyContextType = keyof ComputedPropertyContextMap;\nexport type ComputedPropertyContextValue =\n ComputedPropertyContextMap[keyof ComputedPropertyContextMap]['value'];\n\ntype ComputedPropertyDefinitionBase<\n TContext extends readonly ComputedPropertyContextType[] = ComputedPropertyContextType[],\n TConfig = any\n> = {\n name: string;\n prop: BaseProperty;\n contextType: TContext;\n value: ({\n config,\n context,\n metadata\n }: {\n config?: TConfig;\n context?: ComputedPropertyContextMap[TContext[number]]['value'];\n metadata?: ValueCallbackMetadata;\n }) => any | Observable<any> | Promise<any>;\n injector?: Injector;\n};\n\nexport type ComputedPropertyDefinition<\n TContext extends readonly ComputedPropertyContextType[] = ComputedPropertyContextType[],\n TConfig = any\n> = ComputedPropertyDefinitionBase<TContext, TConfig> &\n (\n | {\n configComponent?: never;\n loadConfigComponent?: never;\n }\n | {\n configComponent: Type<any>;\n loadConfigComponent?: never;\n }\n | {\n configComponent?: never;\n loadConfigComponent: () => Promise<Type<any>>;\n }\n );\n\nexport type ComputedPropertyComponent = {\n /**\n * The configuration which is shared between configuration component and display component.\n * Should be serializable to allow saving it to the API.\n */\n config: unknown;\n /**\n * The asset, device, group, alarm or event for which the property is displayed.\n */\n asset?: IManagedObject;\n};\n\nexport type AssetPropertyListConfig = {\n /**\n * List of properties that should be selected by default when the asset property list is loaded.\n */\n selectedProperties?: AssetPropertyType[];\n /**\n * Flag to enable or disable the filter functionality in the asset property list.\n * When enabled, a search input will be displayed to filter properties by their names.\n */\n filterable?: boolean;\n /**\n * The mode of the asset property list describing how the properties are selected.\n * - `single` - only one property can be selected at a time\n * - `multi` - multiple properties can be selected at a time\n * - `none` - no properties can be selected\n */\n selectMode?: 'single' | 'multi' | 'none';\n /**\n * The mode of the asset property list describing how the properties are expanded.\n * - `expandedByDefault` - all properties are expanded by default and collapse button is shown\n * - `collapsedByDefault` - all properties are collapsed by default and expand button is shown\n * - `nonCollapsible` - all properties are expanded all the time and collapse button is hidden\n */\n expansionMode?: 'expandedByDefault' | 'collapsedByDefault' | 'nonCollapsible';\n /**\n * Flag to show or hide the header of the asset property list.\n * Header includes select all checkbox (if applicable) and the title of the list columns.\n */\n showHeader?: boolean;\n /**\n * Flag to show or hide the value of the asset property.\n */\n showValue?: boolean;\n /**\n * Flag to show or hide the key of the asset property.\n * If set to true, the key of the property will be displayed alongside its value.\n */\n showKey?: boolean;\n /**\n * Defines what should be displayed when asset is not selected and there are no properties to show.\n * - `empty` - empty state with a message\n * - `default-properties` - default properties are shown (like ID, name, type, etc.)\n */\n emptyStateContent?: 'empty' | 'default-properties';\n /**\n * Flag to allow adding custom properties to the asset property list with additional dialog.\n */\n allowAddingCustomProperties?: boolean;\n /**\n * Asset property list allows to provide custom properties as an input.\n * This flag defines how the input properties are handled.\n * - `merge` - input properties are merged with properties from asset list\n * - `override` - input properties override properties from asset list, meaning that only input properties are shown\n */\n inputPropertiesHandle?: 'merge' | 'override';\n /**\n * Enable or disable drag and drop functionality for reordering properties.\n */\n allowDragAndDrop?: boolean;\n};\n\nexport const defaultAssetPropertyListConfig: AssetPropertyListConfig = {\n filterable: true,\n selectMode: 'none',\n expansionMode: 'expandedByDefault',\n showHeader: true,\n showValue: true,\n showKey: true,\n emptyStateContent: 'empty',\n allowAddingCustomProperties: false,\n inputPropertiesHandle: 'merge',\n allowDragAndDrop: false\n};\n\nexport const defaultAssetProperties: Array<BaseProperty> = [\n {\n c8y_JsonSchema: { properties: { name: { type: 'string', label: 'Name' } } },\n name: 'name',\n label: gettext('Name'),\n type: 'string',\n active: true,\n isEditable: true,\n isStandardProperty: true\n },\n {\n c8y_JsonSchema: { properties: { id: { type: 'string', label: 'ID' } } },\n name: 'id',\n label: gettext('ID'),\n type: 'string',\n active: true,\n isEditable: false,\n isStandardProperty: true\n },\n {\n c8y_JsonSchema: {\n properties: { type: { type: 'string', label: 'Type' } }\n },\n name: 'type',\n label: gettext('Type'),\n type: 'string',\n active: true,\n isEditable: false,\n isStandardProperty: true\n },\n {\n c8y_JsonSchema: {\n properties: { owner: { type: 'string', label: 'Owner' } }\n },\n name: 'owner',\n label: gettext('Owner'),\n type: 'string',\n isEditable: false,\n isStandardProperty: true\n },\n {\n c8y_JsonSchema: {\n properties: { lastUpdated: { type: 'string', label: 'Last updated' } }\n },\n name: 'lastUpdated',\n label: gettext('Last updated'),\n type: 'string',\n isEditable: false,\n isStandardProperty: true\n }\n];\n\nexport const deviceAssetProperties: Array<BaseProperty> = [\n {\n label: gettext('Active alarms status'),\n type: 'object',\n isEditable: false,\n isStandardProperty: true,\n name: 'c8y_ActiveAlarmsStatus',\n c8y_JsonSchema: {\n properties: {\n c8y_ActiveAlarmsStatus: {\n key: 'c8y_ActiveAlarmsStatus',\n type: 'object',\n label: 'Active alarms status',\n properties: {\n critical: {\n title: 'Critical',\n type: 'number'\n },\n major: {\n title: 'Major',\n type: 'number'\n },\n minor: {\n title: 'Minor',\n type: 'number'\n },\n warning: {\n title: 'Warning',\n type: 'number'\n }\n }\n }\n }\n }\n },\n {\n label: gettext('Address'),\n type: 'object',\n isEditable: true,\n isStandardProperty: true,\n name: 'c8y_Address',\n c8y_JsonSchema: {\n properties: {\n c8y_Address: {\n key: 'c8y_Address',\n type: 'object',\n label: 'Address',\n properties: {\n street: {\n title: 'Street',\n type: 'string'\n },\n city: {\n title: 'City',\n type: 'string'\n },\n cityCode: {\n title: 'City code',\n type: 'string'\n },\n territory: {\n title: 'Territory',\n type: 'string'\n },\n region: {\n title: 'Region',\n type: 'string'\n },\n country: {\n title: 'Country',\n type: 'string'\n }\n }\n }\n }\n }\n },\n {\n label: gettext('Agent'),\n type: 'object',\n isEditable: true,\n isStandardProperty: true,\n name: 'c8y_Agent',\n c8y_JsonSchema: {\n properties: {\n c8y_Agent: {\n key: 'c8y_Agent',\n type: 'object',\n label: 'Agent',\n properties: {\n name: {\n title: 'Name',\n type: 'string'\n },\n version: {\n title: 'Version',\n type: 'string'\n },\n url: {\n title: 'URL',\n type: 'string'\n },\n maintainer: {\n title: 'Maintainer',\n type: 'string'\n }\n }\n }\n }\n }\n },\n {\n label: gettext('Availability'),\n type: 'object',\n isEditable: false,\n isStandardProperty: true,\n name: 'c8y_Availability',\n c8y_JsonSchema: {\n properties: {\n c8y_Availability: {\n key: 'c8y_Availability',\n type: 'object',\n label: 'Availability',\n properties: {\n status: {\n title: 'Status',\n type: 'string'\n },\n lastMessage: {\n title: 'Last message',\n type: 'string',\n printFormat: 'datetime'\n }\n }\n }\n }\n }\n },\n {\n label: gettext('Connection'),\n type: 'object',\n isEditable: false,\n isStandardProperty: true,\n name: 'c8y_Connection',\n c8y_JsonSchema: {\n properties: {\n c8y_Connection: {\n key: 'c8y_Connection',\n type: 'object',\n label: 'Connection',\n properties: {\n status: {\n title: 'Status',\n type: 'string'\n }\n }\n }\n }\n }\n },\n {\n label: gettext('Communication mode'),\n type: 'object',\n isEditable: true,\n isStandardProperty: true,\n name: 'c8y_CommunicationMode',\n c8y_JsonSchema: {\n properties: {\n c8y_CommunicationMode: {\n key: 'c8y_CommunicationMode',\n type: 'object',\n label: 'Communication mode',\n properties: {\n mode: {\n title: 'Mode',\n type: 'string'\n }\n }\n }\n }\n }\n },\n {\n label: gettext('Firmware'),\n type: 'object',\n isEditable: false,\n isStandardProperty: true,\n name: 'c8y_Firmware',\n c8y_JsonSchema: {\n properties: {\n c8y_Firmware: {\n key: 'c8y_Firmware',\n type: 'object',\n label: 'Firmware',\n properties: {\n moduleVersion: {\n title: 'Module version',\n type: 'string'\n },\n name: {\n title: 'Name',\n type: 'string'\n },\n version: {\n title: 'Version',\n type: 'string'\n },\n url: {\n title: 'URL',\n type: ['string', 'null']\n }\n }\n }\n }\n }\n },\n {\n label: gettext('Hardware'),\n type: 'object',\n isEditable: true,\n isStandardProperty: true,\n name: 'c8y_Hardware',\n c8y_JsonSchema: {\n properties: {\n c8y_Hardware: {\n key: 'c8y_Hardware',\n type: 'object',\n label: 'Hardware',\n properties: {\n model: {\n title: 'Model',\n type: 'string'\n },\n serialNumber: {\n title: 'Serial number',\n type: 'string'\n },\n revision: {\n title: 'Revision',\n type: 'string'\n }\n }\n }\n }\n }\n },\n {\n label: gettext('LPWAN device'),\n type: 'object',\n isEditable: false,\n isStandardProperty: true,\n name: 'c8y_LpwanDevice',\n c8y_JsonSchema: {\n properties: {\n c8y_LpwanDevice: {\n key: 'c8y_LpwanDevice',\n type: 'object',\n label: 'LPWAN device',\n properties: {\n provisioned: {\n title: 'Provisioned',\n type: 'boolean'\n }\n }\n }\n }\n }\n },\n {\n label: gettext('Mobile'),\n type: 'object',\n isEditable: true,\n isStandardProperty: true,\n name: 'c8y_Mobile',\n c8y_JsonSchema: {\n properties: {\n c8y_Mobile: {\n key: 'c8y_Mobile',\n type: 'object',\n label: 'Mobile',\n properties: {\n cellId: {\n title: 'Cell ID',\n type: ['string', 'null']\n },\n connType: {\n title: 'Connection type',\n type: 'string',\n readOnly: true\n },\n currentOperator: {\n title: 'Current operator',\n type: 'string',\n readOnly: true\n },\n currentBand: {\n title: 'Current band',\n type: 'string',\n readOnly: true\n },\n ecn0: {\n title: 'ECN0',\n type: 'string',\n readOnly: true\n },\n iccid: {\n title: 'ICCID',\n type: ['string', 'null']\n },\n imei: {\n title: 'IMEI',\n type: ['string', 'null']\n },\n imsi: {\n title: 'IMSI',\n type: ['string', 'null']\n },\n lac: {\n title: 'LAC',\n type: ['string', 'null']\n },\n mcc: {\n title: 'MCC',\n type: ['string', 'null']\n },\n mnc: {\n title: 'MNC',\n type: ['string', 'null']\n },\n msisdn: {\n title: 'MSISDN',\n type: 'string'\n },\n rcsp: {\n title: 'RCSP',\n type: 'string',\n readOnly: true\n },\n rscp: {\n title: 'RSCP',\n type: 'string',\n readOnly: true\n },\n rsrp: {\n title: 'RSRP',\n type: 'string',\n readOnly: true\n },\n rsrq: {\n title: 'RSRQ',\n type: 'string',\n readOnly: true\n },\n rssi: {\n title: 'RSSI',\n type: 'string',\n readOnly: true\n }\n }\n }\n }\n }\n },\n {\n name: 'c8y_Notes',\n label: 'Notes',\n type: 'string',\n isEditable: true,\n isStandardProperty: true,\n c8y_JsonSchema: {\n properties: {\n c8y_Notes: {\n type: 'string',\n label: 'Notes',\n 'x-schema-form': {\n type: 'textarea'\n }\n }\n }\n }\n },\n {\n label: gettext('Position'),\n type: 'object',\n isEditable: true,\n isStandardProperty: true,\n name: 'c8y_Position',\n c8y_JsonSchema: {\n properties: {\n c8y_Position: {\n key: 'c8y_Position',\n type: 'object',\n label: 'Position',\n properties: {\n lat: {\n title: 'Latitude',\n type: 'number'\n },\n lng: {\n title: 'Longitude',\n type: 'number'\n },\n alt: {\n title: 'Altitude',\n type: 'number'\n }\n }\n }\n }\n }\n },\n {\n label: gettext('Required availability'),\n type: 'object',\n isEditable: true,\n isStandardProperty: true,\n name: 'c8y_RequiredAvailability',\n c8y_JsonSchema: {\n properties: {\n c8y_RequiredAvailability: {\n key: 'c8y_RequiredAvailability',\n type: 'object',\n label: 'Required availability',\n properties: {\n responseInterval: {\n title: 'Response interval',\n description:\n 'Takes a value between -32768 and 32767 minutes (a negative value indicates that the device is under maintenance).',\n type: 'integer',\n minimum: -32768,\n maximum: 32767\n }\n }\n }\n }\n }\n },\n {\n label: gettext('Software'),\n type: 'object',\n isEditable: false,\n isStandardProperty: true,\n name: 'c8y_Software',\n c8y_JsonSchema: {\n properties: {\n c8y_Software: {\n key: 'c8y_Software',\n type: 'object',\n label: 'Software',\n properties: {\n name: {\n title: 'Name',\n type: 'string'\n },\n version: {\n title: 'Version',\n type: 'string'\n },\n url: {\n title: 'URL',\n type: ['string', 'null']\n }\n }\n }\n }\n }\n },\n {\n label: gettext('Network'),\n type: 'object',\n isEditable: true,\n isStandardProperty: true,\n name: 'c8y_Network',\n c8y_JsonSchema: {\n properties: {\n c8y_Network: {\n key: 'c8y_Network',\n type: 'object',\n label: 'Network',\n properties: {\n c8y_DHCP: {\n title: 'DHCP',\n type: 'object',\n printFormat: 'hidden',\n name: 'c8y_DHCP',\n properties: {\n addressRange: {\n title: 'Address range',\n type: 'object',\n name: 'addressRange',\n printFormat: 'hidden',\n properties: {\n start: {\n title: 'Start',\n type: 'string'\n },\n end: {\n title: 'End',\n type: 'string'\n }\n }\n },\n dns1: {\n title: 'DNS 1',\n type: 'string'\n },\n dns2: {\n title: 'DNS 2',\n type: 'string'\n },\n enabled: {\n title: 'Enabled',\n type: 'integer'\n }\n }\n },\n c8y_LAN: {\n title: 'LAN',\n type: 'object',\n name: 'c8y_LAN',\n printFormat: 'hidden',\n properties: {\n enabled: {\n title: 'Enabled',\n type: 'integer'\n },\n ip: {\n title: 'IP',\n type: 'string'\n },\n mac: {\n title: 'MAC',\n type: 'string'\n },\n name: {\n title: 'Name',\n type: 'string'\n },\n netmask: {\n title: 'Netmask',\n type: 'string'\n }\n }\n },\n c8y_WAN: {\n title: 'WAN',\n type: 'object',\n name: 'c8y_WAN',\n printFormat: 'hidden',\n properties: {\n apn: {\n title: 'APN',\n type: 'string'\n },\n authType: {\n title: 'Auth type',\n type: 'string'\n },\n ip: {\n title: 'IP',\n type: 'string'\n },\n password: {\n title: 'Password',\n type: 'string'\n },\n simStatus: {\n title: 'SIM status',\n type: 'string'\n },\n username: {\n title: 'Username',\n type: 'string'\n }\n }\n }\n }\n }\n }\n }\n }\n];\n\nexport const RESULT_TYPES = {\n VALUE: { name: 'VALUE', value: 1, label: gettext('Only value') },\n VALUE_UNIT: { name: 'VALUE_UNIT', value: 2, label: gettext('Value and unit') },\n VALUE_UNIT_TIME: { name: 'VALUE_UNIT_TIME', value: 3, label: gettext('Value, unit and time') }\n};\nexport type C8yPropertyType =\n | 'string'\n | 'number'\n | 'integer'\n | 'boolean'\n | 'object'\n | 'array'\n | 'null'\n | 'date'\n | 'enum'\n | 'file'\n | 'c8y_JsonSchema'\n | ['string', 'null']\n | Array<C8yPropertyType>;\n\ntype C8yPropertyFormat = 'datetime' | 'hidden' | 'textarea';\n\nexport interface C8yJsonSchemaProperty {\n type: C8yPropertyType;\n title?: string;\n label?: string;\n description?: string;\n key?: string;\n name?: string;\n minimum?: number;\n maximum?: number;\n minLength?: number;\n maxLength?: number;\n pattern?: string;\n required?: boolean;\n readOnly?: boolean;\n printFormat?: C8yPropertyFormat;\n 'x-schema-form'?: {\n type: string;\n };\n properties?: Record<string, C8yJsonSchemaProperty>;\n items?: C8yJsonSchemaProperty;\n 'x-show-if-any-available'?: { contextPath: string }[];\n [key: string]: any;\n}\n\nexport interface C8yJsonSchema {\n properties: Record<string, C8yJsonSchemaProperty>;\n required?: string[];\n}\n\nexport interface BaseProperty {\n name: string;\n label: string;\n type: C8yPropertyType;\n isEditable?: boolean;\n temporary?: boolean;\n isStandardProperty?: boolean;\n c8y_JsonSchema?: C8yJsonSchema;\n active?: boolean;\n computed?: boolean;\n instanceId?: string;\n [key: string]: any;\n}\n\nexport interface NestedPropertyFields extends BaseProperty {\n title?: string;\n properties?: object;\n keyPath: string[];\n}\n\nexport type AssetPropertyType = BaseProperty | NestedPropertyFields;\n","import { inject, Injectable, InjectionToken, Injector } from '@angular/core';\nimport {\n ExtensionPointForPlugins,\n GenericHookOptions,\n GenericHookType,\n hookGeneric,\n PluginsResolveService,\n getInjectedHooks,\n stateToFactory,\n fromTriggerOnce,\n GroupService\n} from '@c8y/ngx-components';\nimport {\n ComputedPropertyContextType,\n ComputedPropertyContextValue,\n ComputedPropertyDefinition\n} from './asset-properties.model';\nimport { Router } from '@angular/router';\nimport {\n distinctUntilChanged,\n filter,\n firstValueFrom,\n map,\n mergeMap,\n Observable,\n shareReplay,\n take\n} from 'rxjs';\nimport { IAlarm, IEvent, IManagedObject } from '@c8y/client';\n\nexport const HOOK_COMPUTED_PROPERTY = new InjectionToken<ComputedPropertyDefinition[]>(\n 'HOOK_COMPUTED_PROPERTY'\n);\n\nexport function hookComputedProperty(\n property: GenericHookType<ComputedPropertyDefinition>,\n options?: Partial<GenericHookOptions>\n) {\n return hookGeneric<ComputedPropertyDefinition>(property, HOOK_COMPUTED_PROPERTY, options);\n}\n\n@Injectable({\n providedIn: 'root'\n})\nexport class ComputedPropertiesService extends ExtensionPointForPlugins<ComputedPropertyDefinition> {\n private groupService = inject(GroupService);\n\n constructor(\n rootInjector: Injector,\n private router: Router,\n plugins: PluginsResolveService\n ) {\n super(rootInjector, plugins);\n this.items$ = this.setupItemsObservable();\n }\n\n /**\n * Returns the current state.\n * @readonly\n * @returns The current set of computed properties.\n */\n get state(): Set<ComputedPropertyDefinition> {\n return this.state$.value;\n }\n\n add(propertyDef: ComputedPropertyDefinition) {\n this.state.add(propertyDef);\n this.emitNewState();\n }\n\n getByName(name: string): Promise<ComputedPropertyDefinition> {\n if (!name) {\n return Promise.resolve(undefined);\n }\n return firstValueFrom(\n this.items$.pipe(\n mergeMap((propertyDefs: ComputedPropertyDefinition[]) => propertyDefs),\n filter((propertyDef: ComputedPropertyDefinition) => propertyDef.name === name),\n take(1)\n )\n );\n }\n\n getByContext(asset: ComputedPropertyContextValue): Promise<ComputedPropertyDefinition[]> {\n if (!asset) {\n return Promise.resolve([]);\n }\n const computedPropertyContextType: ComputedPropertyContextType = this.getTypeOfContext(asset);\n\n return firstValueFrom(\n this.items$.pipe(\n map((propertyDefs: ComputedPropertyDefinition[]) =>\n propertyDefs.filter((propertyDef: ComputedPropertyDefinition) => {\n if (!propertyDef.contextType) {\n return true;\n }\n return propertyDef.contextType.includes(computedPropertyContextType);\n })\n )\n )\n );\n }\n\n private getTypeOfContext(context: ComputedPropertyContextValue): ComputedPropertyContextType {\n const isEvent = (item: IAlarm | IEvent): item is IEvent => !('severity' in item);\n const isAlarm = (item: IAlarm | IEvent): item is IAlarm => 'severity' in item;\n\n if (this.groupService.isDevice(context as IManagedObject)) {\n return 'device';\n } else if (\n this.groupService.isGroup(context as IManagedObject) &&\n !this.groupService.isAsset(context as IManagedObject)\n ) {\n return 'group';\n } else if (this.groupService.isAsset(context as IManagedObject)) {\n return 'asset';\n } else if (isAlarm(context as IAlarm | IEvent)) {\n return 'alarm';\n } else if (isEvent(context as IAlarm | IEvent)) {\n return 'event';\n }\n }\n\n protected setupItemsObservable(): Observable<ComputedPropertyDefinition[]> {\n return fromTriggerOnce<ComputedPropertyDefinition>(this.router, this.refresh$, [\n getInjectedHooks<ComputedPropertyDefinition>(HOOK_COMPUTED_PROPERTY, this.injectors),\n () => this.factories,\n stateToFactory(this.state$)\n ]).pipe(distinctUntilChanged(), shareReplay(1));\n }\n}\n","import { inject, Injectable } from '@angular/core';\nimport { IManagedObject, InventoryService, IResultList } from '@c8y/client';\nimport { AlertService, AssetTypesRealtimeService, GroupService } from '@c8y/ngx-components';\nimport { isObjectLike, isEmpty, forOwn, isArray, find } from 'lodash-es';\nimport { firstValueFrom } from 'rxjs';\nimport {\n AssetPropertyType,\n C8yJsonSchemaProperty,\n NestedPropertyFields\n} from './asset-properties.model';\nimport { ComputedPropertiesService } from './computed-properties.service';\n/**\n * Service for managing asset properties.\n */\n@Injectable({\n providedIn: 'root'\n})\nexport class AssetPropertiesService {\n private readonly FRAGMENTS_TO_OMIT = [\n 'additionParents',\n 'assetParents',\n 'deviceParents',\n 'childAdditions',\n 'childAssets',\n 'childDevices',\n 'c8y_IsDevice',\n '__children',\n 'c8y_ui',\n 'self',\n 'parent',\n 'c8y_DataPoint',\n 'c8y_Kpi_Migrated',\n /^c8y_Dashboard!\\d+/\n ];\n\n private inventoryService = inject(InventoryService);\n private assetTypesRealtimeService = inject(AssetTypesRealtimeService);\n private groupService = inject(GroupService);\n private alert = inject(AlertService);\n private computedPropertiesService = inject(ComputedPropertiesService);\n\n /**\n * Filters added properties to only include those compatible with the given asset.\n * Currently only checks compatibility for computed properties.\n * @param allAddedProperties All properties that have been added by the user\n * @param asset The current asset context\n * @returns Promise resolving to properties compatible with the asset\n */\n async filterCompatibleProperties(\n allAddedProperties: AssetPropertyType[],\n asset: IManagedObject\n ): Promise<AssetPropertyType[]> {\n if (!asset || !allAddedProperties.length) {\n return allAddedProperties;\n }\n\n let availableComputedPropertyNames: Set<string> | null = null;\n const computedProperties = allAddedProperties.filter(prop => prop.computed);\n\n if (computedProperties.length > 0) {\n const availableComputedProperties = await this.computedPropertiesService.getByContext(asset);\n availableComputedPropertyNames = new Set(\n availableComputedProperties.map(({ prop }) => prop.name)\n );\n }\n\n const compatibleProperties: AssetPropertyType[] = [];\n\n for (const property of allAddedProperties) {\n if (await this.isPropertyAvailable(property, availableComputedPropertyNames)) {\n compatibleProperties.push(property);\n }\n }\n\n return compatibleProperties;\n }\n\n /**\n * Retrieves properties for an asset from asset library.\n * @param asset The asset for which to retrieve custom properties.\n * @returns A promise resolving to the list of custom properties.\n */\n async requestAssetProperties(asset: IManagedObject): Promise<IManagedObject[]> {\n if (asset && asset.type) {\n const assetType = await firstValueFrom(\n this.assetTypesRealtimeService.getAssetTypeByName$(asset.type)\n );\n if (assetType) {\n const { data } = await this.inventoryService.childAdditionsList(assetType, {\n pageSize: 2000,\n query: \"$filter=(has('c8y_IsAssetProperty'))\"\n });\n return data;\n }\n }\n return [];\n }\n\n /**\n * Retrieves the initial set of properties for an asset, based on its type.\n * @param asset The asset for which to retrieve properties.\n * @returns A promise resolving to the list of initial properties.\n */\n async getInitialProperties(asset: IManagedObject): Promise<AssetPropertyType[]> {\n if (!asset) {\n return [];\n } else if (this.groupService.isDevice(asset)) {\n return await this.getDeviceProperties(asset);\n } else if (this.groupService.isGroup(asset) && !this.groupService.isAsset(asset)) {\n return await this.getGroupProperties(asset);\n } else if (this.groupService.isAsset(asset)) {\n return await this.getAssetProperties(asset);\n }\n return [];\n }\n\n /**\n * Retrieves properties for a device asset.\n * @param asset The device asset for which to retrieve properties.\n * @returns A promise resolving to the list of device properties.\n */\n async getDeviceProperties(asset: IManagedObject): Promise<AssetPropertyType[]> {\n return this.getManagedObjectProperties(asset);\n }\n\n /**\n * Retrieves properties for a group asset.\n * @param asset The group asset for which to retrieve properties.\n * @returns A promise resolving to the list of group properties.\n */\n async getGroupProperties(asset: IManagedObject): Promise<AssetPropertyType[]> {\n return this.getManagedObjectProperties(asset);\n }\n\n /**\n * Retrieves properties for a regular asset.\n * @param asset The asset for which to retrieve properties.\n * @returns A promise resolving to the list of asset properties.\n */\n async getAssetProperties(asset: IManagedObject): Promise<AssetPropertyType[]> {\n const customProperties =\n this.categorizeCustomProperties(await this.requestAssetProperties(asset)) || [];\n return customProperties;\n }\n\n /**\n * Categorizes custom properties into simple and complex types.\n * @param properties The custom properties to categorize.\n * @returns The categorized custom properties.\n */\n categorizeCustomProperties(properties: IManagedObject[]): AssetPropertyType[] {\n const { simple, complex } = properties.reduce(\n (acc, property) => {\n const schema = property.c8y_JsonSchema.properties[property.name];\n if (schema.type === 'object') {\n acc.complex.push(property);\n } else {\n acc.simple.push(property);\n }\n return acc;\n },\n { simple: [], complex: [] }\n );\n\n return [...simple, ...complex] as AssetPropertyType[];\n }\n\n /**\n * Categorizes and flattens hierarchical properties into simple and complex types.\n * @param properties The hierarchical properties to categorize and flatten.\n * @returns The categorized and flattened properties.\n */\n categorizeAndFlattenHierarchicalProperties(properties: AssetPropertyType[]): {\n computed: AssetPropertyType[];\n simple: AssetPropertyType[];\n complex: AssetPropertyType[];\n } {\n const sortedProperties = [...properties].sort((a, b) => {\n const aLabel = (a.label || a.name || '').toLowerCase();\n const bLabel = (b.label || b.name || '').toLowerCase();\n return aLabel.localeCompare(bLabel);\n });\n const result = sortedProperties.reduce(\n (acc, property) => {\n property.active = false;\n if (property.computed) {\n acc.computed.push(property);\n } else if (this.isComplexProperty(property)) {\n acc.complex.push(property);\n this.addNestedProperties(property, acc.complex, true);\n } else {\n acc.simple.push(property);\n }\n return acc;\n },\n { computed: [], simple: [], complex: [] }\n );\n return result;\n }\n\n /**\n * Checks if a property is complex (i.e., has nested properties).\n * @param property The property to check.\n * @returns True if the property is complex, false otherwise.\n */\n isComplexProperty(property: AssetPropertyType): boolean {\n return (\n property.c8y_JsonSchema?.properties[property.name]?.type === 'object' ||\n property.properties !== undefined\n );\n }\n\n /**\n * Checks if property is available based on provided available computed property names.\n * @param property The property to check.\n * @param availableComputedPropertyNames Set of available computed property names.\n * @returns True if the property is available, false otherwise.\n */\n async isPropertyAvailable(\n property: AssetPropertyType,\n availableComputedPropertyNames: Set<string>\n ): Promise<boolean> {\n if (property.computed) {\n if (!availableComputedPropertyNames) {\n return false;\n }\n return availableComputedPropertyNames.has(property.name);\n }\n\n return true;\n }\n\n /**\n * Checks if two properties match for selection purposes.\n * @param property1 First property to compare.\n * @param property2 Second property to compare.\n * @returns True if properties match.\n */\n propertiesMatch(property1: AssetPropertyType, property2: AssetPropertyType): boolean {\n if (property1.name === property2.name) {\n const keyPath1 = (property1 as NestedPropertyFields).keyPath;\n const keyPath2 = (property2 as NestedPropertyFields).keyPath;\n\n if (keyPath1 && keyPath2) {\n return keyPath1.join('.') === keyPath2.join('.');\n } else if (!keyPath1 && !keyPath2) {\n if (\n property1.computed &&\n property2.computed &&\n property1.instanceId &&\n property2.instanceId\n ) {\n return property1.instanceId === property2.instanceId;\n }\n return true;\n }\n }\n return false;\n }\n\n /**\n * Retrieves custom properties from the properties library, optionally filtered by search text.\n * @param searchText Optional search text to filter properties.\n * @returns A promise resolving to the list of properties and paging information.\n */\n async getPropertiesFromPropertiesLibrary(searchText?: string) {\n const propertiesFromLibrary: AssetPropertyType[] = [];\n const { data: propertiesMOs, paging } =\n await this.requestPropertiesFromPropertiesLibrary(searchText);\n propertiesMOs.forEach(prop => {\n const name = Object.keys(prop.c8y_JsonSchema.properties)[0];\n propertiesFromLibrary.push({\n c8y_JsonSchema: prop.c8y_JsonSchema,\n label: prop.name,\n name,\n type: prop.c8y_JsonSchema.properties[name].type\n });\n });\n return { propertiesFromLibrary, paging };\n }\n\n private async requestPropertiesFromPropertiesLibrary(\n searchText?: string\n ): Promise<IResultList<IManagedObject>> {\n let queryFilter = '(type eq c8y_JsonSchema) and (appliesTo.MANAGED_OBJECTS eq true)';\n\n if (searchText?.trim()) {\n const escapedSearchText = searchText.replace(/'/g, \"''\");\n queryFilter += ` and (name eq '*${escapedSearchText}*')`;\n }\n\n const query = `$filter=(${queryFilter})`;\n const filter = {\n pageSize: 20,\n revert: true,\n query,\n withTotalPages: true,\n withTotalElements: true\n };\n try {\n const results = await this.inventoryService.list(filter);\n return results;\n } catch (error) {\n this.alert.addServerFailure(error);\n return null;\n }\n }\n\n private addNestedProperties(\n parentProperty: AssetPropertyType,\n complexProperties: AssetPropertyType[],\n sortChildren = false\n ): void {\n const schema = parentProperty.c8y_JsonSchema?.properties[parentProperty.name];\n if (!schema?.properties) {\n return;\n }\n this.flattenProperties(schema, complexProperties, parentProperty.name, [], sortChildren);\n }\n\n private flattenProperties(\n schema: C8yJsonSchemaProperty,\n result: AssetPropertyType[],\n parentName: string,\n parentPath: string[] = [],\n sortChildren = false\n ): void {\n const properties = schema.properties?.[parentName]?.properties || schema.properties;\n let entries = Object.entries(properties);\n if (sortChildren) {\n entries = entries.sort((a, b) => {\n const aLabel = (a[1].title || a[1].label || a[1].name || a[0] || '').toLowerCase();\n const bLabel = (b[1].title || b[1].label || b[1].name || b[0] || '').toLowerCase();\n return aLabel.localeCompare(bLabel);\n });\n }\n entries.forEach(([key, property]) => {\n const path = parentPath.includes(parentName) ? parentPath : [...parentPath, parentName];\n result.push({ ...property, keyPath: [...path, key] } as AssetPropertyType);\n if (property.properties) {\n this.flattenProperties(\n { properties: { [key]: property } } as C8yJsonSchemaProperty,\n result,\n key,\n [...path, key],\n sortChildren\n );\n }\n });\n }\n\n private getManagedObjectProperties(asset: IManagedObject): AssetPropertyType[] {\n return this.extractFragments(asset);\n }\n\n private extractFragments(object: IManagedObject): AssetPropertyType[] {\n const properties = [];\n for (const [key, value] of Object.entries(object)) {\n if (\n this.shouldSkipFragment(key) ||\n isArray(value) ||\n (isObjectLike(value) && isEmpty(value))\n ) {\n continue;\n }\n const newProp = {\n label: key,\n name: key,\n type: isObjectLike(value) ? 'object' : 'string',\n isEditable: true,\n c8y_JsonSchema: {\n properties: {\n [key]: {\n key: key,\n type: isObjectLike(value) ? 'object' : 'string',\n label: key,\n properties: {}\n }\n }\n }\n };\n\n if (isObjectLike(value)) {\n this.addPropertyItem(newProp.c8y_JsonSchema.properties[key].properties, value);\n }\n\n properties.push(newProp);\n }\n\n return properties;\n }\n\n private shouldSkipFragment(key: string): boolean {\n return !!find(this.FRAGMENTS_TO_OMIT, (fragmentToOmit: string | RegExp) => {\n if (fragmentToOmit instanceof RegExp) {\n return fragmentToOmit.test(key);\n }\n return fragmentToOmit === key;\n });\n }\n\n private addPropertyItem(properties, object) {\n if (!properties) {\n return;\n }\n forOwn(object, (value, key) => {\n properties[key] = { title: key, type: value ? typeof value : 'string' };\n if (isObjectLike(value)) {\n properties[key].type = 'object';\n this.addPropertyItem(properties[key]['properties'], value);\n }\n });\n }\n}\n","import { DataSource } from '@angular/cdk/collections';\nimport { AssetPropertyType } from '../asset-properties.model';\nimport { BehaviorSubject, Observable } from 'rxjs';\n\nexport interface AssetPropertyFlatNode {\n expandable: boolean;\n level: number;\n property: AssetPropertyType;\n isVisible: boolean;\n indeterminate?: boolean;\n contextAssetId?: string;\n}\n\nexport class FlatTreeDataSource extends DataSource<AssetPropertyFlatNode> {\n private _dataChange = new BehaviorSubject<AssetPropertyFlatNode[]>([]);\n\n constructor() {\n super();\n }\n\n get data(): AssetPropertyFlatNode[] {\n return this._dataChange.value;\n }\n\n set data(value: AssetPropertyFlatNode[]) {\n this._dataChange.next(value);\n }\n\n connect(): Observable<AssetPropertyFlatNode[]> {\n return this._dataChange.asObservable();\n }\n\n disconnect(): void {\n // No need to unsubscribe from the _dataChange subject since it's a BehaviorSubject\n }\n}\n","import { Directive, ElementRef, TemplateRef, ViewContainerRef } from '@angular/core';\n\n@Directive({\n selector: '[c8yAssetPropertyAction]',\n standalone: true\n})\nexport class AssetPropertyActionDirective {\n constructor(\n public template: TemplateRef<unknown>,\n public elementRef: ElementRef,\n public viewContainer: ViewContainerRef\n ) {}\n}\n","import { inject, Injector, Pipe, runInInjectionContext } from '@angular/core';\nimport { AssetPropertyType, ComputedPropertyContextValue } from '../asset-properties.model';\nimport { AssetPropertiesService } from '../asset-properties.service';\nimport { get } from 'lodash-es';\nimport { ComputedPropertiesService } from '../computed-properties.service';\nimport { from, isObservable, map, Observable, of, switchMap } from 'rxjs';\n\n/**\n * Formats the value of an asset property.\n * If the property is complex, it will be stringified.\n * If the property has a keyPath, it will be used to retrieve the value from the asset.\n * Otherwise, the value will be taken directly from the asset.\n * If the value is null or undefined, a dash ('-') will be returned.\n */\n@Pipe({\n name: 'c8yAssetPropertyValue',\n standalone: true\n})\nexport class AssetPropertyValuePipe {\n private assetPropertiesService = inject(AssetPropertiesService);\n private computedPropertiesService = inject(ComputedPropertiesService);\n private injector = inject(Injector);\n\n transform(\n property: AssetPropertyType,\n context: ComputedPropertyContextValue\n ): Promise<string> | Observable<string> {\n if (property.computed) {\n return this.getCallbackComputedPropertyValue(property, context).pipe(\n map(val => this.formatPropertyValue(val))\n );\n }\n\n if (!property) {\n return of('-');\n }\n let value: string;\n if (this.assetPropertiesService.isComplexProperty(property)) {\n value = JSON.stringify(context[property.name]);\n } else if ('keyPath' in property) {\n value = this.formatPropertyValue(get(context, property.keyPath));\n } else {\n value = this.formatPropertyValue(context[property.name]);\n }\n return of(value ?? '-');\n }\n\n private formatPropertyValue(value: any): string {\n if (value == null) {\n return null;\n }\n return typeof value === 'string' ? value : JSON.stringify(value);\n }\n\n private getCallbackComputedPropertyValue(\n property: AssetPropertyType,\n context: ComputedPropertyContextValue\n ): any | Promise<any> | Observable<any> {\n return from(this.computedPropertiesService.getByName(property.name)).pipe(\n switchMap(definition => {\n let value: any | Observable<any> | Promise<any> = '-';\n runInInjectionContext(definition.injector || this.injector, () => {\n value = definition.value({ config: property.config, context });\n });\n\n if (isObservable(value) || value instanceof Promise) {\n return value;\n } else {\n return of(value);\n }\n })\n );\n }\n}\n","import { Pipe } from '@angular/core';\nimport { AssetPropertyType } from '../asset-properties.model';\n\n/**\n * Pipe to transform asset property types into icon names.\n * Maps various property types to corresponding icon names.\n */\n@Pipe({\n name: 'c8yAssetPropertyIcon',\n standalone: true\n})\nexport class AssetPropertyIconPipe {\n transform(assetProperty: AssetPropertyType): string {\n if (assetProperty.computed) {\n return 'bolt';\n }\n const type = assetProperty.type;\n switch (type) {\n case 'string':\n return 'string';\n case 'number':\n return 'hashtag';\n case 'boolean':\n return 'true-false';\n case 'date':\n return 'calendar';\n case 'object':\n case 'c8y_JsonSchema':\n return 'c8y-css';\n case 'enum':\n return 'content';\n case 'file':\n return 'file';\n case 'integer':\n return 'unit';\n default:\n return 'mix-words';\n }\n }\n}\n","import { inject, Pipe } from '@angular/core';\nimport { AssetPropertyType, C8yPropertyType } from '../asset-properties.model';\nimport { TranslateService } from '@ngx-translate/core';\nimport { gettext } from '@c8y/ngx-components/gettext';\n\n/**\n * A pipe that transforms an asset property type into a tooltip string.\n * If the property is temporary, it appends \"(temporary)\" to the type and handles translation.\n * Otherwise, it returns the type as is.\n */\n@Pipe({\n name: 'c8yAssetPropertyIconTooltip',\n standalone: true\n})\nexport class AssetPropertyIconTooltipPipe {\n private translateService = inject(TranslateService);\n\n transform(property: AssetPropertyType): string | C8yPropertyType {\n if (!property) {\n return '';\n } else if (property.temporary) {\n return this.translateService.instant(gettext('{{assetPropertyType}}\\n(temporary)'), {\n assetPropertyType: property.type\n });\n } else {\n return property.type;\n }\n }\n}\n","import {\n Component,\n ComponentRef,\n EnvironmentInjector,\n inject,\n Injector,\n Input,\n reflectComponentType,\n Type,\n effect,\n viewChildren,\n ViewContainerRef\n} from '@angular/core';\nimport {\n AssetPropertyType,\n ComputedPropertyComponent,\n ComputedPropertyDefinition\n} from '../asset-properties.model';\nimport { C8yTranslatePipe, IconDirective, isPromise, OnBeforeSave } from '@c8y/ngx-components';\nimport { BsModalRef } from 'ngx-bootstrap/modal';\nimport { FormsModule, NgForm } from '@angular/forms';\nimport { firstValueFrom, from, isObservable, Observable, of } from 'rxjs';\nimport { isUndefined } from 'lodash-es';\nimport { NgFor } from '@angular/common';\nimport { IManagedObject } from '@c8y/client';\n\ninterface PropertyConfig {\n property: AssetPropertyType;\n definition: ComputedPropertyDefinition;\n config: unknown;\n componentRef?: ComponentRef<Component>;\n componentInstance?: ComputedPropertyComponent;\n}\n\n@Component({\n selector: 'c8y-computed-properties-config',\n templateUrl: './computed-properties-config.component.html',\n standalone: true,\n imports: [C8yTranslatePipe, IconDirective, FormsModule, NgFor]\n})\nexport class ComputedPropertiesConfig