@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
1 lines • 226 kB
Source Map (JSON)
{"version":3,"file":"c8y-ngx-components-sub-assets.mjs","sources":["../../sub-assets/shared/sub-assets.model.ts","../../sub-assets/sub-assets.service.ts","../../sub-assets/add-group/add-group.service.ts","../../sub-assets/delete-assets-modal/delete-assets-modal.component.ts","../../sub-assets/delete-assets-modal/delete-assets-modal.component.html","../../sub-assets/smart-group-grid-configuration-strategy.service.ts","../../sub-assets/sub-assets-grid-configuration-strategy.service.ts","../../sub-assets/unassign-assets-modal/unassign-modal.component.ts","../../sub-assets/unassign-assets-modal/unassign-modal.component.html","../../sub-assets/sub-assets-grid.component.ts","../../sub-assets/sub-assets-grid.component.html","../../sub-assets/assign-devices/assign-child-devices.component.ts","../../sub-assets/assign-devices/assign-child-devices.component.html","../../sub-assets/add-group/add-group.component.ts","../../sub-assets/add-group/add-group.component.html","../../sub-assets/assign-devices/assign-devices.component.ts","../../sub-assets/assign-devices/assign-devices.component.html","../../sub-assets/sub-assets-grids.module.ts","../../sub-assets/add-group/add-group.module.ts","../../sub-assets/asset-properties-item.component.ts","../../sub-assets/asset-properties-item.component.html","../../sub-assets/fullscreen.util.ts","../../sub-assets/location/asset-location.component.ts","../../sub-assets/location/asset-location.component.html","../../sub-assets/asset-properties.component.ts","../../sub-assets/asset-properties.component.html","../../sub-assets/sub-assets.model.ts","../../sub-assets/group-info.component.ts","../../sub-assets/group-info.component.html","../../sub-assets/groups.component.ts","../../sub-assets/groups.component.html","../../sub-assets/sub-assets.component.ts","../../sub-assets/sub-assets.component.html","../../sub-assets/sub-assets.module.ts","../../sub-assets/c8y-ngx-components-sub-assets.ts"],"sourcesContent":["export const PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED = {\n EVENT: 'subAssets',\n DELETE_ASSET: {\n COMPONENTS: { DELETE_ASSETS_MODAL: 'delete-assets-modal' },\n ACTIONS: { CASCADE_DELETE: 'cascadeDelete', DELETE_DEVICE_OWNER: 'deleteDeviceOwner' },\n RESULTS: { CANCELED: 'canceled', DELETED: 'deleted' }\n },\n ASSIGN_DEVICES: {\n COMPONENTS: { ASSIGN_DEVICES: 'assign-devices' },\n ACTIONS: { ASSIGN: 'assign', CANCEL: 'cancel', DISPLAY_CHILD_DEVICES: 'displayChildDevices' }\n },\n UNASSIGN_MODAL: {\n COMPONENTS: { UNASSIGN_MODAL: 'unassign-modal' },\n RESULTS: { ASSET_UNASSIGNED: 'asset-unassigned' },\n ACTIONS: { CANCEL: 'cancel' }\n },\n GROUP_INFO: {\n COMPONENTS: { GROUP_INFO: 'group-info' },\n ACTIONS: { EDIT: 'edit' },\n RESULTS: { EDIT_SAVED: 'edit-saved' },\n PROPERTIES: { NAME: 'name', DESCRIPTION: 'description' }\n },\n ADD_GROUP: {\n COMPONENTS: { ADD_GROUP: 'add-group' },\n ACTIONS: { ADD: 'add' },\n RESULTS: { ADD_SUCCESS: 'group-added' }\n }\n} as const;\n","import { Injectable } from '@angular/core';\nimport {\n IManagedObject,\n IRule,\n InventoryService,\n QueriesUtil,\n SmartGroupsService,\n SmartRulesService,\n UserService\n} from '@c8y/client';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport {\n ActionControl,\n AlertService,\n AppStateService,\n AssetTypesRealtimeService,\n BulkActionControl,\n Column,\n DataGridService,\n Pagination,\n Permissions,\n PreviewService,\n UserPreferencesService,\n WILDCARD_SEARCH_FEATURE_KEY\n} from '@c8y/ngx-components';\nimport { AssetNodeService } from '@c8y/ngx-components/assets-navigator';\nimport { AssetTypeGridColumn } from '@c8y/ngx-components/data-grid-columns/asset-type';\nimport {\n AlarmsDeviceGridColumn,\n ImeiDeviceGridColumn,\n ModelDeviceGridColumn,\n NameDeviceGridColumn,\n RegistrationDateDeviceGridColumn,\n SerialNumberDeviceGridColumn,\n SystemIdDeviceGridColumn\n} from '@c8y/ngx-components/device-grid';\nimport { TranslateService } from '@ngx-translate/core';\nimport { catchError, firstValueFrom, of } from 'rxjs';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class SubAssetsService extends DataGridService {\n queriesUtil: QueriesUtil;\n protected GRID_CONFIG_DEFAULT_STORAGE_KEY = 'sub-assets-grid-config';\n private IS_DEVICE_GROUP_FRAGMENT = 'c8y_IsDeviceGroup';\n private IS_DYNAMIC_GROUP_FRAGMENT = 'c8y_IsDynamicGroup';\n\n constructor(\n protected translateService: TranslateService,\n protected inventoryService: InventoryService,\n protected appState: AppStateService,\n protected user: UserService,\n protected assetNodeService: AssetNodeService,\n protected smartGroupsService: SmartGroupsService,\n protected smartRulesService: SmartRulesService,\n protected alertService: AlertService,\n protected permissionsService: Permissions,\n protected assetTypes: AssetTypesRealtimeService,\n protected userPreferencesService: UserPreferencesService,\n protected previewService: PreviewService\n ) {\n super(userPreferencesService);\n }\n\n async getCustomProperties(group: IManagedObject): Promise<IManagedObject[]> {\n const assetType$ = this.assetTypes.getAssetTypeByName$(group.type);\n const assetType = await firstValueFrom(assetType$);\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 return [];\n }\n\n getDefaultColumns(_filterable = true, _sortable = true): Column[] {\n const defaultColumns = [\n new AssetTypeGridColumn({ sortOrder: 'desc' }),\n new NameDeviceGridColumn({ sortOrder: 'asc' }),\n new ModelDeviceGridColumn(),\n new SerialNumberDeviceGridColumn({ visible: false }),\n new RegistrationDateDeviceGridColumn({ visible: false }),\n new SystemIdDeviceGridColumn({ visible: false }),\n new ImeiDeviceGridColumn({ visible: false }),\n new AlarmsDeviceGridColumn()\n ];\n return defaultColumns;\n }\n\n getDefaultPagination(): Pagination {\n return {\n pageSize: 25,\n currentPage: 1\n };\n }\n\n getDefaultActionControls(): ActionControl[] {\n return [];\n }\n\n async unassignAsset(asset: IManagedObject, parentRef: IManagedObject) {\n const { id: assetId } = asset;\n const { id: parentId } = parentRef;\n\n if (this.isDevice(asset)) {\n try {\n await this.inventoryService.childAssetsRemove(assetId, parentId);\n const alertMessage = this.translateService.instant(gettext('Device unassigned.'));\n this.alertService.success(alertMessage);\n } catch (error) {\n const alertMessage = this.translateService.instant(gettext('Could not unassign device.'));\n this.alertService.danger(alertMessage, error);\n }\n await this.deactivateSmartrulesForAsset(asset, parentRef);\n }\n }\n\n isDevice(asset: IManagedObject): boolean {\n return (\n !asset.hasOwnProperty(this.IS_DEVICE_GROUP_FRAGMENT) &&\n !asset.hasOwnProperty(this.IS_DYNAMIC_GROUP_FRAGMENT)\n );\n }\n\n async deleteAsset(asset: IManagedObject, parentRef: IManagedObject, params = {}) {\n const isGroup =\n asset.hasOwnProperty(this.IS_DEVICE_GROUP_FRAGMENT) ||\n this.smartGroupsService.isSmartGroup(asset);\n\n if (isGroup) {\n await this.deleteGroup(asset, params);\n } else {\n await this.deleteDevice(asset, params);\n }\n\n if (parentRef && !this.smartGroupsService.isSmartGroup(asset)) {\n await this.deactivateSmartrulesForAsset(asset, parentRef);\n }\n }\n\n shouldShowWithDeviceUserCheckbox(asset: IManagedObject): boolean {\n const { owner, c8y_IsDevice: isRootDevice } = asset;\n const hasDeviceUserAsOwner = asset.owner && this.isDeviceUser(owner);\n\n return Boolean(isRootDevice && hasDeviceUserAsOwner);\n }\n\n getDefaultBulkActionControls(): BulkActionControl[] {\n return [];\n }\n\n async getData(\n columns: Column[],\n pagination: Pagination,\n parentReference?: IManagedObject,\n baseQuery: any = {},\n text: string = null\n ) {\n const isRoot = !parentReference;\n const isWildcardSearchEnabled = await this.isWildcardSearchEnabled();\n if (isRoot) {\n const query = this.buildCombinedRootQueryFilter(\n columns,\n pagination,\n baseQuery,\n isWildcardSearchEnabled,\n text\n );\n return this.assetNodeService.getRootNodes({\n ...pagination,\n ...(text && !isWildcardSearchEnabled && { text }),\n query\n });\n }\n const filters = {\n ...this.getAssetsFilters(columns, pagination, baseQuery, text, isWildcardSearchEnabled),\n withParents: false\n };\n if (this.assetNodeService.isGroup(parentReference)) {\n return this.assetNodeService.getGroupItems(parentReference.id, filters);\n }\n if (this.assetNodeService.isDynamicGroup(parentReference)) {\n return this.assetNodeService.getDynamicGroupItems(\n parentReference.c8y_DeviceQueryString,\n filters\n );\n }\n if (this.assetNodeService.isDevice(parentReference)) {\n return this.assetNodeService.getDeviceChildren(parentReference.id, filters);\n }\n }\n\n async getCount(\n columns: Column[],\n pagination: Pagination,\n parentReference?: IManagedObject,\n baseQuery: any = {},\n text: string = null\n ): Promise<number> {\n const defaultFilters = {\n pageSize: 1,\n withChildren: false\n };\n const isWildcardSearchEnabled = await this.isWildcardSearchEnabled();\n const filters = !parentReference\n ? {\n query: this.buildCombinedRootQueryFilter(\n columns,\n pagination,\n baseQuery,\n isWildcardSearchEnabled,\n text\n ),\n ...defaultFilters\n }\n : {\n ...this.getAssetsFilters(columns, pagination, baseQuery, text, isWildcardSearchEnabled),\n ...defaultFilters\n };\n return this.getAssetsStatistics(parentReference, filters);\n }\n\n getTotal(parentReference: IManagedObject, baseQuery: any = {}): Promise<number> {\n const queryFilter = this.assetNodeService.rootQueryFilter();\n const query = !parentReference\n ? this.queriesUtil.addAndFilter(queryFilter, baseQuery)\n : baseQuery;\n const filters = {\n query: this.queriesUtil.buildQuery(query),\n withChildren: false,\n withTotalPages: true,\n pageSize: 1\n };\n return this.getAssetsStatistics(parentReference, filters);\n }\n\n async canEditGroup(group: IManagedObject): Promise<boolean> {\n return await this.permissionsService.canEdit(\n [Permissions.ROLE_INVENTORY_ADMIN, Permissions.ROLE_MANAGED_OBJECT_ADMIN],\n group\n );\n }\n\n canCreateGroup(): boolean {\n const currentUser = this.appState.currentUser.value;\n const hasAdminRole = this.user.hasAnyRole(currentUser, [\n Permissions.ROLE_INVENTORY_ADMIN,\n Permissions.ROLE_INVENTORY_CREATE,\n Permissions.ROLE_MANAGED_OBJECT_ADMIN,\n Permissions.ROLE_MANAGED_OBJECT_CREATE\n ]);\n return hasAdminRole;\n }\n\n async canAssignDevice(group: IManagedObject): Promise<boolean> {\n return await this.permissionsService.canEdit(\n [Permissions.ROLE_INVENTORY_ADMIN, Permissions.ROLE_MANAGED_OBJECT_ADMIN],\n group\n );\n }\n\n canEditSmartGroup(): boolean {\n const SMART_GROUPS_ROLES_EDIT = [\n Permissions.ROLE_SMARTGROUP_UPDATE,\n Permissions.ROLE_SMARTGROUP_ADMIN\n ];\n return this.permissionsService.hasAnyRole(SMART_GROUPS_ROLES_EDIT);\n }\n\n canDeleteSmartGroup(): boolean {\n const SMART_GROUPS_ROLES_DELETE = [\n Permissions.ROLE_SMARTGROUP_ADMIN,\n Permissions.ROLE_INVENTORY_ADMIN,\n Permissions.ROLE_MANAGED_OBJECT_ADMIN\n ];\n return this.permissionsService.hasAnyRole(SMART_GROUPS_ROLES_DELETE);\n }\n\n isSmartGroup(group: IManagedObject): boolean {\n return this.smartGroupsService.isSmartGroup(group);\n }\n\n isUsingInventoryRoles() {\n const currentUser = this.appState.currentUser.value;\n const hasAnyInventoryRole = this.user.hasAnyRole(currentUser, [\n Permissions.ROLE_INVENTORY_ADMIN,\n Permissions.ROLE_INVENTORY_READ,\n Permissions.ROLE_INVENTORY_CREATE,\n Permissions.ROLE_MANAGED_OBJECT_ADMIN,\n Permissions.ROLE_MANAGED_OBJECT_CREATE,\n Permissions.ROLE_MANAGED_OBJECT_READ\n ]);\n return !hasAnyInventoryRole;\n }\n\n protected async getAssetsStatistics(\n parentReference: IManagedObject,\n filters: object\n ): Promise<number> {\n const isRoot = !parentReference;\n if (isRoot) {\n return (await this.assetNodeService.getRootNodes(filters)).paging.totalPages;\n }\n if (this.assetNodeService.isGroup(parentReference)) {\n return (await this.assetNodeService.getGroupItems(parentReference.id, filters)).paging\n .totalPages;\n }\n if (this.assetNodeService.isDynamicGroup(parentReference)) {\n return (\n await this.assetNodeService.getDynamicGroupItems(\n parentReference.c8y_DeviceQueryString,\n filters\n )\n ).paging.totalPages;\n }\n if (this.assetNodeService.isDevice(parentReference)) {\n return (await this.assetNodeService.getDeviceChildren(parentReference.id, filters)).paging\n .totalPages;\n }\n }\n\n protected buildCombinedRootQueryFilter(\n columns,\n pagination,\n baseQuery: { text?: string } = {},\n isWildcardSearchEnabled = true,\n text?: string\n ) {\n const userQuery = this.getQueryObj(columns, pagination);\n const rootQuery = this.assetNodeService.rootQueryFilter();\n const orderedRootQuery = this.queriesUtil.addOrderbys(rootQuery, userQuery.__orderby, 'append');\n const rootAndUserQuery = this.queriesUtil.addAndFilter(orderedRootQuery, userQuery.__filter);\n let fullQuery = this.queriesUtil.addAndFilter(rootAndUserQuery, baseQuery);\n if (text && isWildcardSearchEnabled) {\n fullQuery = this.queriesUtil.addAndFilter(fullQuery, {\n name: `*${text.trim().replace(/\\s+/g, '*')}*`\n });\n }\n\n return this.queriesUtil.buildQuery(fullQuery);\n }\n\n private async deleteGroup(group: IManagedObject, params: any = {}) {\n const { cascade } = params;\n\n try {\n this.smartGroupsService.isSmartGroup(group)\n ? await this.smartGroupsService.delete(group, { cascade })\n : await this.inventoryService.delete(group, { cascade });\n\n const alertMessage = this.translateService.instant(gettext('\"{{ name }}\" deleted.'), {\n name: group.name\n });\n this.alertService.success(alertMessage);\n } catch (error) {\n const alertMessage = this.translateService.instant(\n gettext('Could not delete \"{{ name }}\".'),\n {\n name: group.name\n }\n );\n this.alertService.danger(alertMessage, error);\n }\n }\n\n private async isWildcardSearchEnabled(): Promise<boolean> {\n return firstValueFrom(\n this.previewService.getState$(WILDCARD_SEARCH_FEATURE_KEY).pipe(catchError(() => of(true)))\n );\n }\n\n private async deleteDevice(device: IManagedObject, params: any = {}) {\n const { cascade, withDeviceUser } = params;\n try {\n const { owner } = device;\n const shouldRemoveOwner = withDeviceUser && owner && this.isDeviceUser(owner);\n\n shouldRemoveOwner\n ? await this.deleteDeviceWithUser(device, cascade)\n : await this.inventoryService.delete(device, { cascade });\n\n const alertMessage = this.translateService.instant(gettext('Device deleted.'));\n this.alertService.success(alertMessage);\n } catch (error) {\n const alertMessage = this.translateService.instant(gettext('Could not delete device.'));\n this.alertService.danger(alertMessage, error);\n }\n }\n\n private async deactivateSmartrulesForAsset(asset: IManagedObject, parentRef: IManagedObject) {\n const { id: assetId } = asset;\n const { id: parentId } = parentRef;\n const rules: IRule[] = (await this.smartRulesService.listByContext(parentId)).data;\n\n const upateSmartrulesPromises = rules.map(rule =>\n this.smartRulesService.bulkDeactivateEnabledSources(rule, [assetId])\n );\n\n try {\n await Promise.all(upateSmartrulesPromises);\n } catch (error) {\n const alertMessage = this.translateService.instant(\n gettext('Could not deactivate smart rules.')\n );\n this.alertService.danger(alertMessage);\n }\n }\n\n private isDeviceUser(userId: string) {\n return userId.match(/^device_/);\n }\n\n private async deleteDeviceWithUser(device: IManagedObject, cascade: boolean) {\n const params = { cascade, withDeviceUser: true };\n try {\n return await this.inventoryService.delete(device, params);\n } catch (error) {\n return await this.inventoryService.delete(device, { cascade });\n }\n }\n\n private getAssetsFilters(\n columns: Column[],\n pagination: Pagination,\n baseQuery,\n text?: string,\n isWildcardSearchEnabled = true\n ) {\n let query = this.queriesUtil.addAndFilter(this.getQueryObj(columns), baseQuery);\n if (text && isWildcardSearchEnabled) {\n query = this.queriesUtil.addAndFilter(query, {\n name: `*${text.trim().replace(/\\s+/g, '*')}*`\n });\n }\n return {\n ...(text && !isWildcardSearchEnabled && { text }),\n query: this.queriesUtil.buildQuery(query),\n pageSize: pagination.pageSize || this.DEFAULT_PAGE_SIZE,\n currentPage: pagination.currentPage,\n withTotalPages: true\n };\n }\n}\n","import { Injectable } from '@angular/core';\nimport { IIdentified, IManagedObject, InventoryService } from '@c8y/client';\nimport { GroupForm } from './add-group.model';\nimport { GroupFragment } from '@c8y/ngx-components';\n\n@Injectable()\nexport class AddGroupService {\n private GROUP_FRAGMENT_TYPE = 'c8y_IsDeviceGroup';\n\n constructor(private inventoryService: InventoryService) {}\n\n async createGroupAndAssignDevices(\n groupForm: GroupForm,\n groupContextId: string | number,\n selectedDevices: string[]\n ): Promise<IManagedObject | IIdentified> {\n let group: IManagedObject | IIdentified;\n const { name, description } = groupForm;\n const newGroupMO = this.getGroupMO(name, description, groupContextId);\n\n if (groupContextId) {\n group = (await this.inventoryService.childAssetsCreate(newGroupMO, groupContextId)).data;\n } else {\n group = (await this.inventoryService.create(newGroupMO)).data;\n }\n\n if (selectedDevices.length > 0) {\n await this.assignDevices(group.id, selectedDevices);\n }\n\n return group;\n }\n\n private getGroupMO(\n name: string,\n description = '',\n groupContextId: string | number\n ): Partial<IManagedObject> {\n const group = {\n type: this.getGroupType(groupContextId),\n [this.GROUP_FRAGMENT_TYPE]: {},\n name,\n c8y_Notes: description\n };\n\n return group;\n }\n\n private getGroupType(\n groupContextId: string | number\n ): GroupFragment.subGroupType | GroupFragment.groupType {\n return groupContextId ? GroupFragment.subGroupType : GroupFragment.groupType;\n }\n\n private async assignDevices(id: string | number, selectedDevices: string[]) {\n const promises = [];\n\n selectedDevices.forEach(moId => {\n promises.push(this.inventoryService.childAssetsAdd(moId, id));\n });\n\n return await Promise.all(promises);\n }\n}\n","import { Component, Input, ViewChild } from '@angular/core';\nimport { IManagedObject } from '@c8y/client';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport {\n ConfirmModalComponent,\n GainsightService,\n ModalLabels,\n Status,\n StatusType,\n FormGroupComponent,\n ProductExperienceDirective,\n C8yTranslatePipe\n} from '@c8y/ngx-components';\nimport { TranslateService } from '@ngx-translate/core';\nimport { Subject } from 'rxjs';\nimport { PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED } from '../shared/sub-assets.model';\nimport { DeleteModalCheckboxes } from './delete-assets-modal.model';\n\nimport { FormsModule } from '@angular/forms';\nimport { NgIf } from '@angular/common';\n\n@Component({\n selector: 'c8y-delete-assets-modal',\n templateUrl: './delete-assets-modal.component.html',\n imports: [\n ConfirmModalComponent,\n FormsModule,\n NgIf,\n FormGroupComponent,\n ProductExperienceDirective,\n C8yTranslatePipe\n ]\n})\nexport class DeleteAssetsModalComponent {\n CURRENT_LOCATION = location.href;\n PRODUCT_EXPERIENCE = PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED;\n\n @Input() showWithCascadeCheckbox = true;\n @Input() showWithDeviceUserCheckbox = false;\n @Input() asset: IManagedObject;\n @ViewChild('modalRef', { static: false }) modalRef: ConfirmModalComponent;\n closeSubject: Subject<DeleteModalCheckboxes> = new Subject();\n labels: ModalLabels = { ok: gettext('Delete'), cancel: gettext('Cancel') };\n title: string = gettext('Delete');\n status: StatusType = Status.DANGER;\n config: DeleteModalCheckboxes = {\n cascade: false,\n withDeviceUser: false\n };\n message: string;\n deleteGroupSubAssetsMsg: string;\n\n constructor(\n private translateService: TranslateService,\n private gainsightService: GainsightService\n ) {}\n\n ngOnInit() {\n this.setModalTexts();\n }\n\n async ngAfterViewInit() {\n try {\n await this.modalRef.result;\n this.onClose();\n } catch (error) {\n this.onDismiss();\n }\n }\n\n onClose() {\n this.gainsightService.triggerEvent(PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED.EVENT, {\n component: PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED.DELETE_ASSET.COMPONENTS.DELETE_ASSETS_MODAL,\n result: PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED.DELETE_ASSET.RESULTS.DELETED,\n url: this.CURRENT_LOCATION\n });\n this.closeSubject.next(this.config);\n this.closeSubject.complete();\n }\n\n onDismiss() {\n this.gainsightService.triggerEvent(PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED.EVENT, {\n component: PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED.DELETE_ASSET.COMPONENTS.DELETE_ASSETS_MODAL,\n result: PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED.DELETE_ASSET.RESULTS.CANCELED,\n url: this.CURRENT_LOCATION\n });\n this.closeSubject.complete();\n }\n\n private setModalTexts() {\n this.message = this.translateService.instant(\n gettext(\n 'You are about to delete: \"{{name}}\". This operation is irreversible. Do you want to proceed?'\n ),\n { name: this.asset.name }\n );\n this.deleteGroupSubAssetsMsg = this.translateService.instant(\n gettext('Also delete all devices inside \"{{name}}\" and its subassets.'),\n { name: this.asset.name }\n );\n }\n}\n","<c8y-confirm-modal [title]=\"title\" [status]=\"status\" [labels]=\"labels\" #modalRef>\n <form #assetsForm=\"ngForm\">\n <p class=\"text-wrap m-b-16\">\n {{ message | translate }}\n </p>\n <c8y-form-group *ngIf=\"showWithCascadeCheckbox\" class=\"m-b-0\">\n <label title=\"{{ 'Delete devices' | translate }}\" class=\"c8y-checkbox\">\n <input\n type=\"checkbox\"\n name=\"cascade\"\n [(ngModel)]=\"config.cascade\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.EVENT\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.DELETE_ASSET.COMPONENTS.DELETE_ASSETS_MODAL,\n action: PRODUCT_EXPERIENCE.DELETE_ASSET.ACTIONS.CASCADE_DELETE\n }\"\n [disabled]=\"config?.withDeviceUser\"\n />\n <span></span>\n <span class=\"text-break-word\">\n {{ deleteGroupSubAssetsMsg | translate }}\n </span>\n </label>\n </c8y-form-group>\n <c8y-form-group *ngIf=\"showWithDeviceUserCheckbox\" class=\"m-b-0\">\n <label title=\"{{ 'Delete associated device owner' | translate }}\" class=\"c8y-checkbox\">\n <input\n type=\"checkbox\"\n name=\"withDeviceUser\"\n [(ngModel)]=\"config.withDeviceUser\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.EVENT\"\n [actionData]=\"{\n component: PRODUCT_EXPERIENCE.DELETE_ASSET.COMPONENTS.DELETE_ASSETS_MODAL,\n action: PRODUCT_EXPERIENCE.DELETE_ASSET.ACTIONS.DELETE_DEVICE_OWNER\n }\"\n [disabled]=\"config?.cascade\"\n />\n <span></span>\n <span>\n {{ 'Also delete associated device owner.' | translate }}\n </span>\n </label>\n </c8y-form-group>\n </form>\n</c8y-confirm-modal>\n","import { Inject, Injectable, Optional } from '@angular/core';\nimport {\n AbstractConfigurationStrategy,\n DATA_GRID_CONFIGURATION_CONTEXT,\n DATA_GRID_CONFIGURATION_CONTEXT_PROVIDER,\n GridConfig,\n GridConfigContext,\n GridConfigContextProvider,\n UserPreferencesConfigurationStrategy\n} from '@c8y/ngx-components';\nimport { cloneDeep } from 'lodash-es';\nimport { Observable, of } from 'rxjs';\nimport { SubAssetsGridConfigContext } from './sub-assets-grid-configuration-strategy.service';\n\n@Injectable({ providedIn: 'root' })\nexport class SmartGroupGridConfigurationStrategy extends AbstractConfigurationStrategy {\n constructor(\n protected userPreferencesConfigurationStrategy: UserPreferencesConfigurationStrategy,\n @Inject(DATA_GRID_CONFIGURATION_CONTEXT)\n @Optional()\n protected context: GridConfigContext,\n @Inject(DATA_GRID_CONFIGURATION_CONTEXT_PROVIDER)\n @Optional()\n protected contextProvider: GridConfigContextProvider\n ) {\n super(context, contextProvider);\n }\n\n getConfig$(context?: SubAssetsGridConfigContext): Observable<GridConfig> {\n const group = cloneDeep(this.retrieveContext(context)?.group);\n\n if (group?.c8y_DeviceColumnsConfig?.columns?.length) {\n group.c8y_DeviceColumnsConfig.columns = group.c8y_DeviceColumnsConfig.columns.map(column => {\n delete column.filter;\n return column;\n });\n }\n\n return of(group?.c8y_DeviceColumnsConfig);\n }\n\n saveConfig$(config: GridConfig, _context?: SubAssetsGridConfigContext): Observable<GridConfig> {\n return of(config);\n }\n}\n","import { Inject, Injectable, Optional } from '@angular/core';\nimport { IManagedObject } from '@c8y/client';\nimport {\n AbstractConfigurationStrategy,\n DataGridConfigurationStrategy,\n DATA_GRID_CONFIGURATION_CONTEXT,\n DATA_GRID_CONFIGURATION_CONTEXT_PROVIDER,\n GridConfig,\n GridConfigContext,\n GridConfigContextProvider,\n UserPreferencesConfigurationStrategy,\n UserPreferencesGridConfigContext\n} from '@c8y/ngx-components';\nimport { AssetNodeService } from '@c8y/ngx-components/assets-navigator';\nimport { Observable } from 'rxjs';\nimport { SmartGroupGridConfigurationStrategy } from './smart-group-grid-configuration-strategy.service';\n\nexport interface SubAssetsGridConfigContext extends UserPreferencesGridConfigContext {\n group: IManagedObject;\n}\n\n@Injectable({ providedIn: 'root' })\nexport class SubAssetsGridConfigurationStrategy extends AbstractConfigurationStrategy {\n constructor(\n protected userPreferencesConfigurationStrategy: UserPreferencesConfigurationStrategy,\n protected smartGroupGridConfigurationStrategy: SmartGroupGridConfigurationStrategy,\n protected assetNodeService: AssetNodeService,\n @Inject(DATA_GRID_CONFIGURATION_CONTEXT)\n @Optional()\n protected context: GridConfigContext,\n @Inject(DATA_GRID_CONFIGURATION_CONTEXT_PROVIDER)\n @Optional()\n protected contextProvider: GridConfigContextProvider\n ) {\n super(context, contextProvider);\n }\n\n getConfig$(context?: SubAssetsGridConfigContext): Observable<GridConfig> {\n return this.getStrategy(context).getConfig$(context);\n }\n\n saveConfig$(config: GridConfig, context?: SubAssetsGridConfigContext): Observable<GridConfig> {\n return this.getStrategy(context).saveConfig$(config, context);\n }\n\n private getStrategy(ctx?: SubAssetsGridConfigContext): DataGridConfigurationStrategy {\n const context = this.retrieveContext(ctx) as SubAssetsGridConfigContext;\n return !!context?.group &&\n this.assetNodeService.isDynamicGroup(context?.group) &&\n context?.group?.c8y_DeviceColumnsConfig\n ? this.smartGroupGridConfigurationStrategy\n : this.userPreferencesConfigurationStrategy;\n }\n}\n","import { Component, Input, ViewChild } from '@angular/core';\nimport { IManagedObject } from '@c8y/client';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport {\n ConfirmModalComponent,\n GainsightService,\n ModalLabels,\n Status,\n StatusType,\n C8yTranslatePipe\n} from '@c8y/ngx-components';\nimport { TranslateService } from '@ngx-translate/core';\nimport { Subject } from 'rxjs';\nimport { PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED } from '../shared/sub-assets.model';\n\n@Component({\n selector: 'c8y-unassign-modal',\n templateUrl: './unassign-modal.component.html',\n imports: [ConfirmModalComponent, C8yTranslatePipe]\n})\nexport class UnassignModalComponent {\n CURRENT_LOCATION = location.href;\n @Input() asset: IManagedObject;\n @ViewChild('modalRef', { static: false }) modalRef: ConfirmModalComponent;\n message: string;\n closeSubject: Subject<boolean> = new Subject();\n labels: ModalLabels = { ok: gettext('Unassign'), cancel: gettext('Cancel') };\n title = gettext('Unassign');\n status: StatusType = Status.WARNING;\n\n constructor(\n private translateService: TranslateService,\n private gainsightService: GainsightService\n ) {}\n\n ngOnInit() {\n this.message = this.translateService.instant(\n gettext('You are about to unassign \"{{name}}\". Do you want to proceed?'),\n { name: this.asset.name }\n );\n }\n\n async ngAfterViewInit() {\n try {\n await this.modalRef.result;\n this.onClose();\n } catch (error) {\n this.onDismiss();\n }\n }\n\n onClose() {\n this.gainsightService.triggerEvent(PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED.EVENT, {\n component: PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED.UNASSIGN_MODAL.COMPONENTS.UNASSIGN_MODAL,\n result: PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED.UNASSIGN_MODAL.RESULTS.ASSET_UNASSIGNED,\n url: this.CURRENT_LOCATION\n });\n this.closeSubject.next(true);\n this.closeSubject.complete();\n }\n\n onDismiss() {\n this.gainsightService.triggerEvent(PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED.EVENT, {\n component: PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED.UNASSIGN_MODAL.COMPONENTS.UNASSIGN_MODAL,\n action: PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED.UNASSIGN_MODAL.ACTIONS.CANCEL,\n url: this.CURRENT_LOCATION\n });\n this.closeSubject.complete();\n }\n}\n","<c8y-confirm-modal [title]=\"title\" [status]=\"status\" [labels]=\"labels\" #modalRef>\n <span>{{ message | translate }}</span>\n</c8y-confirm-modal>\n","import {\n Component,\n EventEmitter,\n Input,\n OnDestroy,\n OnInit,\n Output,\n SimpleChanges,\n ViewChild\n} from '@angular/core';\nimport { IManagedObject, SmartGroupsService } from '@c8y/client';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport {\n ActionControl,\n BuiltInActionType,\n BulkActionControl,\n Column,\n DataGridComponent,\n DataSourceModifier,\n DATA_GRID_CONFIGURATION_CONTEXT_PROVIDER,\n DATA_GRID_CONFIGURATION_STRATEGY,\n DisplayOptions,\n GridConfigContextProvider,\n Pagination,\n Row,\n ServerSideDataCallback,\n ServerSideDataResult,\n UserPreferencesConfigurationStrategy,\n ProductExperienceDirective,\n EmptyStateContextDirective,\n EmptyStateComponent,\n ColumnDirective,\n C8yTranslatePipe\n} from '@c8y/ngx-components';\nimport { AssetNodeService } from '@c8y/ngx-components/assets-navigator';\nimport { DeviceListExtensionService } from '@c8y/ngx-components/device-list';\nimport { BsModalService } from 'ngx-bootstrap/modal';\nimport { of, Subject } from 'rxjs';\nimport { takeUntil } from 'rxjs/operators';\nimport { DeleteModalCheckboxes } from './delete-assets-modal';\nimport { DeleteAssetsModalComponent } from './delete-assets-modal/delete-assets-modal.component';\nimport { PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED } from './shared/sub-assets.model';\nimport { SmartGroupGridConfigurationStrategy } from './smart-group-grid-configuration-strategy.service';\nimport {\n SubAssetsGridConfigContext,\n SubAssetsGridConfigurationStrategy\n} from './sub-assets-grid-configuration-strategy.service';\nimport { SubAssetsService } from './sub-assets.service';\nimport { UnassignModalComponent } from './unassign-assets-modal/unassign-modal.component';\n\nimport { NgFor } from '@angular/common';\n\n@Component({\n selector: 'c8y-sub-assets-grid',\n templateUrl: './sub-assets-grid.component.html',\n providers: [\n {\n provide: UserPreferencesConfigurationStrategy,\n useClass: UserPreferencesConfigurationStrategy\n },\n {\n provide: SmartGroupGridConfigurationStrategy,\n useClass: SmartGroupGridConfigurationStrategy\n },\n {\n provide: DATA_GRID_CONFIGURATION_STRATEGY,\n useClass: SubAssetsGridConfigurationStrategy\n },\n {\n provide: DATA_GRID_CONFIGURATION_CONTEXT_PROVIDER,\n useExisting: SubAssetsGridComponent\n }\n ],\n imports: [\n DataGridComponent,\n ProductExperienceDirective,\n EmptyStateContextDirective,\n EmptyStateComponent,\n NgFor,\n ColumnDirective,\n C8yTranslatePipe\n ]\n})\nexport class SubAssetsGridComponent implements OnInit, OnDestroy, GridConfigContextProvider {\n PRODUCT_EXPERIENCE = PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED;\n @Input('parent-group') parentGroup: IManagedObject;\n @Input() refresh: EventEmitter<void>;\n @Input() title: string = gettext('Subassets');\n @Input() emptyStateText: string = gettext(\n 'Add your first group or assign devices using the buttons on the action bar.'\n );\n @Input() loadingItemsLabel: string = gettext('Loading assets…');\n /** The name of the key where columns configuration will be stored. */\n @Input() columnsConfigKey: string;\n\n get columns() {\n return this._columns;\n }\n @Input() set columns(value: Column[]) {\n this._columns = value ?? this.subAssetsGridService.getDefaultColumns();\n }\n @Input('pagination') set _pagination(value: Pagination) {\n if (value) {\n this.pagination = value;\n } else {\n this.pagination = this.subAssetsGridService.getDefaultPagination();\n }\n }\n @Input('actionControls') set _actionControls(value: ActionControl[]) {\n if (value) {\n this.actionControls = value;\n } else {\n this.actionControls = this.subAssetsGridService.getDefaultActionControls();\n }\n }\n @Input() selectable = false;\n @Input() baseQuery: object = {};\n @Input('bulkActionControls') set _bulkActionControls(value: BulkActionControl[]) {\n if (value) {\n this.bulkActionControls = value;\n } else {\n this.bulkActionControls = this.subAssetsGridService.getDefaultBulkActionControls();\n }\n }\n @Input() filterable = true;\n @Input() sortable = true;\n @Output() onColumnsChange: EventEmitter<Column[]> = new EventEmitter<Column[]>();\n @Output() itemsSelect: EventEmitter<string[]> = new EventEmitter<string[]>();\n\n pagination: Pagination = this.subAssetsGridService.getDefaultPagination();\n showCounterWarning = false;\n actionControls: ActionControl[];\n bulkActionControls: BulkActionControl[] =\n this.subAssetsGridService.getDefaultBulkActionControls();\n serverSideDataCallback: ServerSideDataCallback;\n\n @ViewChild(DataGridComponent, { static: true })\n dataGrid: DataGridComponent;\n\n displayOptions: DisplayOptions = {\n striped: true,\n bordered: false,\n gridHeader: true,\n filter: true,\n hover: true\n };\n\n showSearch = false;\n\n noResultsMessage = gettext('No matching items.');\n noDataMessage = gettext('No items to display.');\n noResultsSubtitle = gettext('Refine your search terms or check your spelling.');\n\n private _columns: Column[];\n private destroyed$ = new Subject<void>();\n\n get isRootGroup() {\n return !this.parentGroup;\n }\n\n get getInfiniteScrollMode() {\n return this.isRootGroup && this.subAssetsGridService.isUsingInventoryRoles()\n ? 'auto'\n : undefined;\n }\n\n @Input('displayOptions')\n set _displayOptions(displayOptions) {\n this.displayOptions = { ...this.displayOptions, ...displayOptions };\n }\n\n constructor(\n public subAssetsGridService: SubAssetsService,\n private bsModalService: BsModalService,\n private smartGroupsService: SmartGroupsService,\n private deviceListExtensionService: DeviceListExtensionService,\n private assetNodeService: AssetNodeService\n ) {\n this.serverSideDataCallback = this.onDataSourceModifier.bind(this);\n }\n\n getGridConfigContext(): SubAssetsGridConfigContext {\n if (!!this.columnsConfigKey) {\n return { key: this.columnsConfigKey, group: this.parentGroup };\n }\n }\n\n ngOnInit() {\n const isDynamicGroup =\n !!this.parentGroup && this.assetNodeService.isDynamicGroup(this.parentGroup);\n if (!this.isRootGroup) {\n (isDynamicGroup\n ? this.deviceListExtensionService.items$\n : of(this.subAssetsGridService.getDefaultColumns(this.filterable, this.sortable))\n )\n .pipe(takeUntil(this.destroyed$))\n .subscribe(columns => (this.columns = columns));\n }\n if (!this.filterable || !this.sortable) {\n this.displayOptions.filter = this.filterable;\n this.columns.forEach(column => {\n column.filterable = this.filterable;\n column.sortable = this.sortable;\n });\n }\n this.setActionControls();\n this.showSearch = isDynamicGroup || !this.parentGroup;\n }\n\n setActionControls() {\n const actionControls: ActionControl[] = [];\n\n const unassignAction: ActionControl = {\n type: 'UNASSIGN',\n icon: 'unlink',\n text: gettext('Unassign'),\n priority: 1000,\n callback: (asset: Row) => this.onUnassignAsset(asset as IManagedObject, this.parentGroup),\n showIf: (asset: Row) =>\n this.subAssetsGridService.isDevice(asset as IManagedObject) &&\n !this.subAssetsGridService.isSmartGroup(this.parentGroup as IManagedObject)\n };\n\n actionControls.push(unassignAction);\n\n const deleteAction: ActionControl = {\n type: BuiltInActionType.Delete,\n priority: -Infinity,\n callback: (asset: Row) => this.onDeleteAsset(asset as IManagedObject, this.parentGroup),\n showIf: (asset: Row) => {\n if (this.smartGroupsService.isSmartGroup(asset as IManagedObject)) {\n return this.subAssetsGridService.canDeleteSmartGroup();\n }\n\n return true;\n }\n };\n\n actionControls.push(deleteAction);\n\n if (!this.actionControls) {\n this.actionControls = actionControls;\n }\n }\n\n onUnassignAsset(asset: IManagedObject, parentRef: IManagedObject) {\n const initialState = {\n asset\n };\n const modalRef = this.bsModalService.show(UnassignModalComponent, { initialState });\n\n modalRef.content.closeSubject.subscribe(async (result: boolean) => {\n if (result) {\n await this.subAssetsGridService.unassignAsset(asset, parentRef);\n this.refresh.emit();\n }\n });\n }\n\n async onDeleteAsset(asset: IManagedObject, parentRef: IManagedObject) {\n const initialState = {\n showWithDeviceUserCheckbox: this.subAssetsGridService.shouldShowWithDeviceUserCheckbox(asset),\n asset,\n showWithCascadeCheckbox: !this.smartGroupsService.isSmartGroup(asset)\n };\n\n const modalRef = this.bsModalService.show(DeleteAssetsModalComponent, { initialState });\n\n modalRef.content.closeSubject.subscribe(async (result: DeleteModalCheckboxes) => {\n if (result) {\n await this.subAssetsGridService.deleteAsset(asset, parentRef, result);\n if (result.cascade) {\n this.showCounterWarning = true;\n }\n this.refresh.emit();\n }\n });\n }\n\n ngOnChanges(changes: SimpleChanges): void {\n if (changes.parentGroup && !changes.parentGroup.firstChange) {\n this.dataGrid.reload();\n }\n }\n\n trackByName(_index, column: Column): string {\n return column.name;\n }\n\n onReload() {\n this.assetNodeService.rootNode.refresh();\n }\n\n async onDataSourceModifier(\n dataSourceModifier: DataSourceModifier\n ): Promise<ServerSideDataResult> {\n const promises = [];\n let counters;\n\n promises.push(\n this.subAssetsGridService.getData(\n dataSourceModifier.columns,\n dataSourceModifier.pagination,\n this.parentGroup,\n this.baseQuery,\n dataSourceModifier.searchText\n )\n );\n\n promises.push(this.subAssetsGridService.getTotal(this.parentGroup, this.baseQuery));\n promises.push(\n this.subAssetsGridService.getCount(\n dataSourceModifier.columns,\n dataSourceModifier.pagination,\n this.parentGroup,\n this.baseQuery,\n dataSourceModifier.searchText\n )\n );\n\n const [dataResponse, size, filteredSize] = await Promise.all(promises);\n if (!counters) {\n counters = {\n size,\n filteredSize\n };\n }\n this.onColumnsChange.emit(dataSourceModifier.columns);\n\n return {\n res: dataResponse.res,\n data: dataResponse.data,\n paging: dataResponse.paging,\n ...counters\n };\n }\n\n ngOnDestroy(): void {\n this.destroyed$.next();\n this.destroyed$.complete();\n }\n}\n","<c8y-data-grid\n [title]=\"title\"\n [loadingItemsLabel]=\"loadingItemsLabel\"\n [columns]=\"columns\"\n [pagination]=\"pagination\"\n [actionControls]=\"actionControls\"\n [selectable]=\"selectable\"\n [bulkActionControls]=\"bulkActionControls\"\n [serverSideDataCallback]=\"serverSideDataCallback\"\n [infiniteScroll]=\"getInfiniteScrollMode\"\n [showCounterWarning]=\"showCounterWarning\"\n [refresh]=\"refresh\"\n [showSearch]=\"showSearch\"\n [displayOptions]=\"displayOptions\"\n (itemsSelect)=\"itemsSelect.emit($event)\"\n c8yProductExperience\n [actionName]=\"PRODUCT_EXPERIENCE.EVENT\"\n (onReload)=\"onReload()\"\n>\n <c8y-ui-empty-state\n [icon]=\"'c8y-group-add'\"\n [title]=\"stats?.size > 0 ? (noResultsMessage | translate) : (noDataMessage | translate)\"\n [subtitle]=\"stats?.size > 0 ? (noResultsSubtitle | translate) : (emptyStateText | translate)\"\n *emptyStateContext=\"let stats\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n\n <ng-container *ngFor=\"let column of columns; trackBy: trackByName\">\n <c8y-column [name]=\"column.name\"></c8y-column>\n </ng-container>\n</c8y-data-grid>\n","import { Component, Input, Output, EventEmitter, HostListener } from '@angular/core';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport { AlertService, C8yTranslatePipe } from '@c8y/ngx-components';\nimport { InventoryService, IManagedObject } from '@c8y/client';\nimport { SubAssetsService } from '../sub-assets.service';\nimport { SubAssetsGridComponent } from '../sub-assets-grid.component';\nimport { NgClass } from '@angular/common';\n\n@Component({\n selector: 'c8y-assign-child-devices',\n templateUrl: './assign-child-devices.component.html',\n imports: [SubAssetsGridComponent, NgClass, C8yTranslatePipe]\n})\nexport class AssignChildDevicesComponent {\n @Input() currentGroupId: string;\n @Input() parentDevice: IManagedObject;\n @Output() onCancel = new EventEmitter<any>();\n @Output() onSelectedDevices = new EventEmitter<string[]>();\n @Input() refresh = new EventEmitter<any>();\n @Input() onlySelect = false; // if true, devices are only selected, not assigned\n\n selected: string[] = [];\n baseQuery: any;\n canAssignDevice = false;\n pendingStatus = false;\n\n constructor(\n private alert: AlertService,\n private subAssetsService: SubAssetsService,\n private inventoryService: InventoryService\n ) {}\n\n @HostListener('document:keydown.enter', ['$event']) onEnterKeyDown(_event: KeyboardEvent) {\n if (this.selected.length > 0) {\n this.assignDevices();\n }\n }\n\n @HostListener('document:keydown.escape', ['$event']) onEscapeKeyDown(_event: KeyboardEvent) {\n this.onCancel.emit();\n }\n\n async ngOnInit() {\n this.setNotIncludedInGroupQuery();\n this.canAssignDevice = await this.subAssetsService.canAssignDevice({\n id: this.currentGroupId\n } as IManagedObject);\n }\n\n setNotIncludedInGroupQuery() {\n const notIncludedInGroupQuery = { __not: { __bygroupid: this.currentGroupId } };\n this.baseQuery = notIncludedInGroupQuery;\n }\n\n async assignDevices() {\n if (this.canAssignDevice === false) {\n return;\n }\n if (this.onlySelect) {\n this.onSelectedDevices.emit(this.selected);\n this.alert.success(gettext('Child devices selected.'));\n this.onCancel.emit();\n return;\n }\n\n this.pendingStatus = true;\n\n try {\n await this.inventoryService.childAssetsBulkAdd(this.selected, this.currentGroupId);\n this.refresh.emit();\n this.alert.success(gettext('Child devices assigned.'));\n } catch (error) {\n this.alert.danger(gettext('Could not assign child devices.'), error);\n }\n this.pendingStatus = false;\n this.selected = [];\n this.onCancel.emit();\n }\n\n onSelected(selectedDevicesIDs: string[]) {\n this.selected = selectedDevicesIDs;\n }\n}\n","<div class=\"card-block flex-no-shrink separator-bottom col-xs-12 large-padding p-t-24 p-b-24\">\n <div class=\"row\">\n <div class=\"col-md-6 col-md-offset-3 col-lg-4 col-lg-offset-4\">\n <h4 class=\"text-center text-medium\" id=\"childDevicesDrawerTitle\">\n {{ 'Assign child devices' | translate }}\n </h4>\n </div>\n </div>\n</div>\n\n<c8y-sub-assets-grid\n [title]=\"''\"\n [emptyStateText]=\"'All child devices are already assigned' | translate\"\n [refresh]=\"refresh\"\n [actionControls]=\"[]\"\n [columnsConfigKey]=\"'assign-child-devices'\"\n [selectable]=\"true\"\n [parent-group]=\"parentDevice\"\n [baseQuery]=\"baseQuery\"\n (itemsSelect)=\"onSelected($event)\"\n class=\"d-contents\"\n></c8y-sub-assets-grid>\n\n<div class=\"text-center card-footer p-24 separator\">\n <button\n (click)=\"onCancel.emit()\"\n type=\"button\"\n class=\"btn btn-default\"\n title=\"{{ 'Cancel' | translate }}\"\n >\n <span>{{ 'Cancel' | translate }}</span>\n </button>\n <button\n (click)=\"assignDevices()\"\n type=\"button\"\n class=\"btn btn-primary\"\n [ngClass]=\"{ 'btn-pending': pendingStatus }\"\n title=\"{{ 'Assign' | translate }}\"\n [disabled]=\"selected.length === 0 || !canAssignDevice\"\n >\n <span>{{ 'Assign' | translate }}</span>\n </button>\n</div>\n","import {\n Component,\n ElementRef,\n EventEmitter,\n HostListener,\n Input,\n Output,\n ViewChild\n} from '@angular/core';\nimport {\n FormBuilder,\n FormGroup,\n Validators,\n FormsModule,\n ReactiveFormsModule\n} from '@angular/forms';\nimport { IManagedObject } from '@c8y/client';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport {\n ActionControl,\n AlertService,\n C8yStepper,\n GainsightService,\n Pagination,\n Permissions,\n Row,\n ProductExperienceDirective,\n FormGroupComponent,\n C8yTranslateDirective,\n RequiredInputPlaceholderDirective,\n C8yStepperButtons,\n C8yTranslatePipe\n} from '@c8y/ngx-components';\nimport { Subscription } from 'rxjs';\nimport { PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED } from '../shared/sub-assets.model';\nimport { SubAssetsService } from '../sub-assets.service';\nimport { AddGroupService } from './add-group.service';\nimport { NgIf, NgClass, NgTemplateOutlet, NgStyle } from '@angular/common';\nimport { CdkTrapFocus } from '@angular/cdk/a11y';\nimport { CdkStep } from '@angular/cdk/stepper';\nimport { DeviceGridComponent } from '@c8y/ngx-components/device-grid';\nimport { AssignChildDevicesComponent } from '../assign-devices/assign-child-devices.component';\n\n@Component({\n selector: 'c8y-add-group',\n templateUrl: './add-group.component.html',\n imports: [\n NgIf,\n NgClass,\n NgTemplateOutlet,\n C8yStepper,\n ProductExperienceDirective,\n CdkStep,\n CdkTrapFocus,\n FormGroupComponent,\n FormsModule,\n ReactiveFormsModule,\n C8yTranslateDirective,\n RequiredInputPlaceholderDirective,\n C8yStepperButtons,\n DeviceGridComponent,\n NgStyle,\n AssignChildDevicesComponent,\n C8yTranslatePipe\n ]\n})\nexport class AddGroupComponent {\n @Input() currentGroupId: string;\n @Input() refresh = new EventEmitter<void>();\n @Output() onDeviceQueryStringChange: EventEmitter<string> = new EventEmitter<string>();\n @Output() onCancel = new EventEmitter<void>();\n @ViewChild(C8yStepper, { static: false })\n stepper: C8yStepper;\n @ViewChild('nameRef', { static: false })\n nameInputRef: ElementRef;\n deviceQueryStringOutput: string;\n showAssignChildDevices = false;\n showChildrenForDevice: IManagedObject;\n formGroupStepOne: FormGroup;\n actionControls: ActionControl[] = [];\n pendingStatus = false;\n pagination: Pagination = { pageSize: 20, currentPage: 1 };\n selected: string[] = [];\n selectedChildDevices: string[] = [];\n subscription: Subscription;\n canCreateGroup = false;\n canAssignDevice = false;\n PRODUCT_EXPERIENCE = PRODUCT_EXPERIENCE_SUB_ASSETS_SHARED;\n\n readonly ITEMS_SELECT_LIMIT: number = 15;\n readonly btnLabels = {\n next: gettext('Next'),\n cancel: gettext('Cancel'),\n create: gettext('Create')\n };\n\n private nameInput: HTMLInputElement;\n\n constructor(\n private fb: FormBuilde