UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

702 lines 118 kB
import { Injectable } from '@angular/core'; import { Router } from '@angular/router'; import { InventoryService, QueriesUtil } from '@c8y/client'; import { AlertService, DynamicComponentService, getActivatedRoute, gettext, GroupService, ModalService, NavigatorNode, NavigatorService, NEW_DASHBOARD_ROUTER_STATE_PROP, OptionsService, Permissions, Status, TabsService, ViewContext } from '@c8y/ngx-components'; import { TranslateService } from '@ngx-translate/core'; import { assign, cloneDeep, forEach, get, has, keyBy, keys, pick, reduce, set, some } from 'lodash-es'; import { combineLatest, from, of, Subject } from 'rxjs'; import { catchError, filter, map, mergeMap, tap, throwIfEmpty, toArray } from 'rxjs/operators'; import { ALL_GLOBAL_ROLES_SELECTED, ContextDashboardType, PRODUCT_EXPERIENCE, STYLING_CLASS_PREFIXES } from './context-dashboard.model'; import * as i0 from "@angular/core"; import * as i1 from "@c8y/client"; import * as i2 from "@c8y/ngx-components"; import * as i3 from "@ngx-translate/core"; import * as i4 from "@angular/router"; export class ContextDashboardService { get formDisabled() { return this._formDisabled; } set formDisabled(value) { this._formDisabled = value; this.formDisabledSubject.next(value); } constructor(inventory, tabs, modal, translateService, router, navigator, permissions, alert, dynamicComponent, groupService, optionsService) { this.inventory = inventory; this.tabs = tabs; this.modal = modal; this.translateService = translateService; this.router = router; this.navigator = navigator; this.permissions = permissions; this.alert = alert; this.dynamicComponent = dynamicComponent; this.groupService = groupService; this.optionsService = optionsService; this.REPORT_PARTIAL_NAME = 'report_'; this.VERSION_HISTORY_SIZE_LIMIT = 10; this.INVENTORY_ROLES = [ Permissions.ROLE_INVENTORY_ADMIN, Permissions.ROLE_MANAGED_OBJECT_ADMIN ]; this.cache = new Map(); this.DEFAULT_PAGESIZE = 1000; this.FRAGMENT_NAME = 'c8y_Dashboard'; this.DASHBOARD_ROUTE_PATH = 'dashboard'; this.INDEX_SPLIT = '!'; this.CACHE_TIMEOUT = 500; this._formDisabled = true; this.formDisabledSubject = new Subject(); this.HIDE_TYPE_DASHBOARD_FOR_ASSETS = 'hideTypeDashboardForAssets'; this.formDisabled$ = this.formDisabledSubject.asObservable(); this.queriesUtil = new QueriesUtil(); } async create(dashboardCfg, context, name = '') { let dashboard = {}; assign(dashboard, this.adjustDashboardFor24Columns({ c8y_Dashboard: dashboardCfg }), this.updateDashboardHistory(dashboard, dashboardCfg)); const [dashboardType, dashboardFragments] = this.getDashboardFragments({ c8y_Dashboard: dashboardCfg }, context, name, false); dashboard = { ...dashboard, ...dashboardFragments }; if (this.shouldSetGlobal(dashboard, context)) { assign(dashboard, { c8y_Global: {} }); } dashboard.name = dashboard.c8y_Dashboard.name; const { data } = dashboardType === ContextDashboardType.Group || dashboardType === ContextDashboardType.Device || (context && dashboardType === ContextDashboardType.Named) ? await this.inventory.childAdditionsCreate(dashboard, context?.contextData.id || '') : await this.inventory.create(dashboard); return data; } async detail(dashboardMO) { let { data } = await this.inventory.detail(dashboardMO); data = this.adjustDashboardFor24Columns(data); this.cache.set(dashboardMO.id, data); return data; } async update(dashboard, context) { const dashboardCfg = dashboard.c8y_Dashboard; dashboard.name = dashboard.c8y_Dashboard.name; assign(dashboard, this.adjustDashboardFor24Columns({ c8y_Dashboard: dashboardCfg }), this.updateDashboardHistory(dashboard, dashboardCfg)); const keepFragments = this.clean(pick(dashboard, [this.FRAGMENT_NAME, 'id', 'name'])); keepFragments.c8y_DashboardHistory = dashboard.c8y_DashboardHistory; await this.serializeWidgetConfigs(keepFragments); const [, dashboardTypeFragments] = this.getDashboardFragments(dashboard, context, '', true); keepFragments.c8y_Global = this.shouldSetGlobal({ ...dashboard, ...dashboardTypeFragments }); const { data } = await this.inventory.update({ ...keepFragments, ...dashboardTypeFragments }); this.cache.set(dashboard.id, data); return data; } async delete(dashboard, withConfirmation = true) { try { if (withConfirmation) { let msg = gettext(`You are about to delete the dashboard "{{ dashboardName }}". Do you want to proceed?`); if (this.isDeviceType(dashboard)) { msg = gettext(`You are about to delete the dashboard "{{ dashboardName }}" from all devices of the type "{{ deviceType }}". Do you want to proceed?`); } await this.modal.confirm(gettext('Delete dashboard'), this.translateService.instant(msg, { dashboardName: dashboard.c8y_Dashboard.name, deviceType: dashboard.c8y_Dashboard.deviceTypeValue }), Status.DANGER, { ok: gettext('Delete'), cancel: gettext('Cancel') }); } await this.inventory.delete(dashboard); const tabToRemove = Array.from(this.tabs.state).find(tab => { if (typeof tab.path === 'string') { return tab.path.endsWith(`${this.DASHBOARD_ROUTE_PATH}/${dashboard.id}`); } }); this.tabs.remove(tabToRemove); queueMicrotask(() => { this.tabs.refresh(); }); } catch (ex) { // intended empty } } updateDashboardHistory(dashboard, dashboardCfg) { if (!dashboard.c8y_DashboardHistory) { dashboard.c8y_DashboardHistory = []; } if (!dashboardCfg.historyDescription) { dashboardCfg.historyDescription = { changeType: 'create' }; } dashboardCfg.created = new Date().toISOString(); dashboard.c8y_DashboardHistory = cloneDeep([dashboardCfg, ...dashboard.c8y_DashboardHistory]); if (dashboard.c8y_DashboardHistory.length > this.VERSION_HISTORY_SIZE_LIMIT) { dashboard.c8y_DashboardHistory = [ ...dashboard.c8y_DashboardHistory.slice(0, this.VERSION_HISTORY_SIZE_LIMIT) ]; } return dashboard; } activateDashboards(route, types) { const { dashboardId } = route.params; if (dashboardId) { return this.getDashboard$(dashboardId, types, route.parent.data.contextData).pipe(tap(dashboard => { route.data = { dashboard }; }), map(() => true), catchError(() => { return of(false); })); } this.dashboardTabs$ = this.getTabs$(route.data.contextData, types, route?.parent?.data); return this.dashboardTabs$; } getNamedDashboardOrCreate(name, defaultWidgets, context) { const children = this.mapWidgets(defaultWidgets); return this.getDashboard$(name, [ContextDashboardType.Named]).pipe(throwIfEmpty(), catchError(() => { if (!this.hasPermissionsToCopyDashboard()) { this.alert.warning(gettext('You are viewing a read-only dashboard because you don’t have the necessary permissions to modify it.')); return of(this.getTemporaryDashboard({ name, children, widgetClasses: { 'dashboard-theme-light': true, 'panel-title-regular': true } })); } else return from(this.create({ children, widgetClasses: { 'dashboard-theme-light': true, 'panel-title-regular': true } }, context, name)); })); } updateNavigatorItem(mo) { this.navigator.state.forEach(node => { if (node.path === `reports/${mo.id}`) { this.navigator.remove(node); } }); if (mo.c8y_IsNavigatorNode) { const nodeToAdd = new NavigatorNode({ label: mo.name, path: `reports/${mo.id}`, icon: mo.icon, priority: mo.priority }); this.navigator.add(nodeToAdd); } } async navigateToDashboard(dashboardMO, isNewDashboard = false) { if (/dashboard/.test(this.router.url)) { this.router.navigate(['..', dashboardMO.id], { relativeTo: getActivatedRoute(this.router), ...(isNewDashboard && { state: { [NEW_DASHBOARD_ROUTER_STATE_PROP]: true } }) }); } else if (/^\/(device|group)\/[0-9]+$/.test(this.router.url)) { // in case the add dashboard button is the only tab on that route this.router.navigate(['.', this.DASHBOARD_ROUTE_PATH, dashboardMO.id], { relativeTo: getActivatedRoute(this.router), ...(isNewDashboard && { state: { [NEW_DASHBOARD_ROUTER_STATE_PROP]: true } }) }); } else { this.router.navigate(['..', this.DASHBOARD_ROUTE_PATH, dashboardMO.id], { relativeTo: getActivatedRoute(this.router), ...(isNewDashboard && { state: { [NEW_DASHBOARD_ROUTER_STATE_PROP]: true } }) }); } } /** * Checks if user is able to edit dashboard according to his roles and dashboard ownership. * * @param mo - Dashboard managed object. * @returns True if user is able to edit dashboard, false if he cannot. */ async canEditDashboard(mo) { return await this.permissions.canEdit(this.INVENTORY_ROLES, mo); } /** * Checks if user has permissions to copy dashboard according to his roles. * * @returns True if user has permissions to copy dashboard, false if he cannot. */ hasPermissionsToCopyDashboard() { return this.permissions.hasAnyRole([ Permissions.ROLE_INVENTORY_ADMIN, Permissions.ROLE_INVENTORY_CREATE, Permissions.ROLE_MANAGED_OBJECT_ADMIN, Permissions.ROLE_MANAGED_OBJECT_CREATE ]); } isNamed(dashboard) { return some(keys(dashboard), prop => new RegExp(`^${this.FRAGMENT_NAME}${this.INDEX_SPLIT}${ContextDashboardType.Named}${this.INDEX_SPLIT}`).test(prop)); } isReport(dashboard) { return some(keys(dashboard), prop => new RegExp(`^${this.FRAGMENT_NAME}${this.INDEX_SPLIT}${ContextDashboardType.Named}${this.INDEX_SPLIT}${this.REPORT_PARTIAL_NAME}`).test(prop)); } isDeviceType(dashboard) { return some(keys(dashboard), prop => { const matchingProp = new RegExp(`^${this.FRAGMENT_NAME}${this.INDEX_SPLIT}${ContextDashboardType.Type}${this.INDEX_SPLIT}`).test(prop); if (!matchingProp) { return false; } else { // there might be matching key, but its value can be {} or null return !!dashboard[prop]; } }); } isDeviceDashboard(dashboard) { return some(keys(dashboard), prop => new RegExp(`^${this.FRAGMENT_NAME}${this.INDEX_SPLIT}${ContextDashboardType.Device}${this.INDEX_SPLIT}`).test(prop)); } isGroupDashboard(dashboard) { return some(keys(dashboard), prop => new RegExp(`^${this.FRAGMENT_NAME}${this.INDEX_SPLIT}${ContextDashboardType.Group}${this.INDEX_SPLIT}`).test(prop)); } getFilteredDashboardStyles(styleList) { return styleList.filter(c => STYLING_CLASS_PREFIXES.some(classPrefix => c.startsWith(classPrefix))); } getStyling(styleList, styleName, defaultValue) { const styling = styleList.find(style => style && new RegExp(`-${styleName}$`, 'i').test(style.class)); return styling ? styling.class : defaultValue; } mapWidgets(widgets) { return keyBy(widgets.map(widget => { widget.id = String(Math.random()).substr(2); return widget; }), 'id'); } getDashboard$(dashboardIdOrName, dashboardType, mo) { const cache = this.cache.get(dashboardIdOrName); const dashboards = mo ? this.getContextDashboards(mo, dashboardType) : this.getNamedDashboard(dashboardIdOrName); const cacheRefresh = this.getContextDashboards$(dashboards).pipe(tap(dashboard => this.cacheDashboard(dashboard)), filter(dashboard => dashboard.id === dashboardIdOrName || has(dashboard, `${this.FRAGMENT_NAME}${this.INDEX_SPLIT}${ContextDashboardType.Named}${this.INDEX_SPLIT}${dashboardIdOrName}`))); return cache ? of(cache) : cacheRefresh; } async pasteDashboard(newContext) { if (this.copyClipboard) { try { const dashboardToPaste = this.createContextDashboardCopy(this.copyClipboard.dashboard, newContext.contextData, this.copyClipboard.context.contextData); const dashboard = await this.create(this.clean(dashboardToPaste), newContext); // linking childAdditions for e.g. to grant access to the images uploaded by the image widget for users with only inventory roles. const { data: childAdditions } = await this.inventory.childAdditionsList(this.copyClipboard.dashboardId, { pageSize: 2000 }); if (childAdditions.length) { await this.inventory.childAdditionsBulkAdd(childAdditions, dashboard.id); } this.copyClipboard = undefined; this.navigateToDashboard(dashboard); } catch { this.alert.warning(gettext('Insufficient permissions for this action.')); } } } /** * Creates fragment that associates dashboards with device/asset. It consists of three elements: * - FRAGMENT_NAME - static string * - dashboard type (e.g. 'group', 'device') * - fragment value ( id of device/asset if it is not typed dashboard; deviceTypeValue property of dashboard if it is type dashboard) * Example fragment for device dashboard: 'c8y_Dashboard!device!773200' * Example fragment for group dashboard: 'c8y_Dashboard!group!84129208' * Example fragment for typed device dashboard: 'c8y_Dashboard!type!c8y_lwm2m_connector_device' * * @param contextDashboardType Type of dashboard * @param value Fragment value * @returns Fragment for dashboard */ createFragmentKey(contextDashboardType, value) { return `${this.FRAGMENT_NAME}${this.INDEX_SPLIT}${contextDashboardType}${this.INDEX_SPLIT}${value}`; } /** * Indicates if dashboard can be set to type dashboard. * First, it checks if deviceTypeValue exists and if user has permission to set dashboard type. * Then, case from sensor app is checked- dashboard created with sensor app has deviceType set to true but * type fragment is missing- we do not support this combination. * @param mo Dashboard managed object * @param context {ContextData} Current context * @returns True if dashboard can be set to type dashboard, false if it is forbidden. */ shouldAllowToSetDashboardType(mo, context) { // disallow if dashboard managed object or context is missing or context is not device/asset/group if (!mo || !context?.contextData || (context.context !== ViewContext.Device && context.context !== ViewContext.Group)) { return 'disallow'; } // if context is asset/group and type dashboard feature is hidden for assets/groups or asset/group has no typ, return disallow const typeDashboardHiddenForAssets = this.optionsService.get(this.HIDE_TYPE_DASHBOARD_FOR_ASSETS, true); if (context.context === ViewContext.Group && (typeDashboardHiddenForAssets || !context.contextData.type)) { return 'disallow'; } // if user has no permission to change dashboard, return disallow if (!this.permissions.hasAnyRole(this.INVENTORY_ROLES)) { return 'disallow'; } // case from sensor app is checked- dashboard created with sensor app has deviceType set to true but // type fragment is missing- we do not support this combination. const typeFragment = this.createFragmentKey(ContextDashboardType.Type, context?.contextData?.type); if (mo?.c8y_Dashboard && mo?.c8y_Dashboard.deviceType && context?.contextData?.type && !mo[typeFragment]) { return 'disallow'; } // if view context is Device and contextData of this device has no type yet but type dashboard can be set when type is filled, // return allow_if_type_filled if (!context?.contextData?.type && context.context === ViewContext.Device && this.permissions.hasAnyRole(this.INVENTORY_ROLES)) { return 'allow_if_type_filled'; } return 'allow'; } createReport(reportCfg) { const report = {}; Object.assign(report, reportCfg); Object.assign(report, { c8y_Report: {} }); return this.inventory.create(report); } addReportNavigatorNode(report) { const node = new NavigatorNode({ label: report.name, path: `reports/${report.id}`, icon: report.icon, priority: report.priority }); this.navigator.add(node); } getContextForGS(mo) { if (this.groupService.isDevice(mo)) { return PRODUCT_EXPERIENCE.DASHBOARD.CONTEXT.DEVICE; } else if (this.groupService.isAsset(mo)) { return PRODUCT_EXPERIENCE.DASHBOARD.CONTEXT.ASSET; } else if (this.groupService.isGroup(mo)) { return PRODUCT_EXPERIENCE.DASHBOARD.CONTEXT.GROUP; } else { return null; } } async getContextDashboards(mo, dashboardType) { const filterCriteria = dashboardType.map(t => ({ // it's necessary to wrap fragment in quotes because dashboard type can contain spaces __has: `'${this.createDashboardFragment(mo, t)}'` })); // the has query above does not work for device type dashboards where the type contains a dot const typeFilterCriteria = dashboardType.includes(ContextDashboardType.Type) && mo.type ? { __and: [ { 'c8y_Dashboard.deviceType': { __eq: true } }, { 'c8y_Dashboard.deviceTypeValue': { __eq: mo.type } } ] } : undefined; const finalFilterCriteria = typeFilterCriteria ? [...filterCriteria, typeFilterCriteria] : filterCriteria; const query = this.queriesUtil.buildQuery({ __filter: { __or: finalFilterCriteria } }); const now = Date.now(); const cacheHasValidResponse = this.contextDashboardsCache && this.contextDashboardsCache.query === query && now - this.contextDashboardsCache.timestamp < this.CACHE_TIMEOUT; if (cacheHasValidResponse) { return this.contextDashboardsCache.result; } else { this.contextDashboardsCache = null; } this.contextDashboardsCache = { query, result: this.inventory.list({ query, pageSize: this.DEFAULT_PAGESIZE }), timestamp: now }; return this.contextDashboardsCache.result; } /** * Creates a tuple describing the dashboard type and its fragments. For assets like devices and groups, it's possible * to have two fragments: one indicating this particular device/asset with its ID, and the second indicating * the device/asset type (if the dashboard is meant to be applied to all assets of this type). * * @param dashboardMO - Dashboard managed object. * @param context - Context data of asset. * @param name - Name of the dashboard. * @param isEdit - True if existing dashboard is updated, false when it's creation of new dashboard. * @returns Tuple of dashboard type and object containing dashboard fragments. */ getDashboardFragments(dashboardMO, context, name, isEdit) { let dashboardType; const id = context?.contextData?.id || ''; const fragments = {}; if (name) { // a named dashboard should not receive any other fragments dashboardType = ContextDashboardType.Named; const namedFragmentKey = this.createFragmentKey(ContextDashboardType.Named, name); fragments[namedFragmentKey] = {}; } else if (context?.context === ViewContext.Device || context?.context === ViewContext.Group) { // get base type for device or group const defaultType = context.context === ViewContext.Device ? ContextDashboardType.Device : ContextDashboardType.Group; dashboardType = dashboardMO.c8y_Dashboard.deviceType ? ContextDashboardType.Type : defaultType; // clear fragments from other asset if current asset is not origin of this dashboard this.clearRedundantFragment(dashboardMO, defaultType, fragments); // add base fragment for particular asset const deviceOrGroupFragmentKey = this.createFragmentKey(defaultType, id); fragments[deviceOrGroupFragmentKey] = {}; // add or clear type fragment if (dashboardMO.c8y_Dashboard.deviceType || isEdit) { const typeFragmentKey = this.createFragmentKey(ContextDashboardType.Type, dashboardMO.c8y_Dashboard.deviceTypeValue); fragments[typeFragmentKey] = dashboardMO.c8y_Dashboard.deviceType ? {} : null; } } return [dashboardType, fragments]; } /** * Clears fragments that originates from other managed object. * E.g. typed dashboard is created for device A of type c8y_MQTTDevice and id 1, so it gets fragments object * ```ts * { * c8y_Dashboard!device!1: {}, * c8y_Dashboard!type!c8y_MQTTDevice: {} * } *``` * then, on device B of type c8y_MQTTDevice and id 2, which also has access to this dashboard, deviceType is set to * false, so dashboard is not typed dashboard anymore and now belongs to device B, therefore fragments should look like * ```ts * { * c8y_Dashboard!device!1: null, // this value is cleared because dashboard is doesn't belong to device A anymore * c8y_Dashboard!device!2: {}, // assign dashboard to device B * c8y_Dashboard!type!c8y_MQTTDevice: null // this value is cleared in getDashboardFragments method as it's not typed dashboard anymore * } * ``` * * @param dashboardMO - Dashboard managed object. * @param type - Context dashboard type. * @param fragments - Fragments object. */ clearRedundantFragment(dashboardMO, type, fragments) { Object.keys(dashboardMO) .filter(key => key.startsWith(this.createFragmentKey(type, ''))) .forEach(key => (fragments[key] = null)); } adjustDashboardFor24Columns(dashboards) { if (Array.isArray(dashboards)) { return dashboards.map(dashboard => this.adjustDashboardFor24Columns(dashboard)); } // if `columns` attribute exists, dashboard was already adjusted. if (dashboards.c8y_Dashboard.columns) { return dashboards; } dashboards.c8y_Dashboard.columns = 24; if (!dashboards.c8y_Dashboard.children) { return dashboards; } // Newly created NamedContextDashboards are still created with 12 columns for backwards compatibility. // Default widgets might be already configured for 24 columns. // If a widget is already configured for more than 12 columns, we should not adjust it. const alreadyHasWidgetsConfiguredForMoreThan12Columns = Object.values(dashboards.c8y_Dashboard.children).some(widget => widget._x + widget._width > 12); if (alreadyHasWidgetsConfiguredForMoreThan12Columns) { return dashboards; } // we need to multiply both _width and _x attributes with 2 to migrate from 12 to 24 columns. Object.values(dashboards.c8y_Dashboard.children).forEach(widget => { if (widget._width) { widget._width = widget._width * 2; } if (widget._x) { widget._x = widget._x * 2; } }); return dashboards; } async serializeWidgetConfigs(dashboard) { const children = cloneDeep(dashboard.c8y_Dashboard.children); if (!children) { return; } const configs = Object.values(children); const details = configs.map(({ componentId, config }) => ({ componentId, config })); const results = await this.dynamicComponent.serializeConfigs(details); results.forEach((result, index) => { Object.entries(result).forEach(([key, value]) => { set(details[index].config, key, value); }); }); dashboard.c8y_Dashboard.children = children; } createContextDashboardCopy(dash, newContext, oldContext) { const children = reduce(dash.children, (_children, child) => { const { id } = child; const cfg = child.config; const propertiesToCopy = { device: device => this.replaceContextInObj(device, newContext, oldContext), datapoints: dataPoints => this.replaceContextInDataPoints(dataPoints, newContext, oldContext), dataPoints: dataPoints => this.replaceContextInDataPoints(dataPoints, newContext, oldContext), datapointsGauge: dataPoints => this.replaceContextInDataPoints(dataPoints, newContext, oldContext), datapointsLabels: dataPoints => this.replaceContextInDataPoints(dataPoints, newContext, oldContext) }; if (cfg) { this.copyProperties(cfg, propertiesToCopy); if (cfg.options) { this.copyProperties(cfg.options, propertiesToCopy); } } _children[id] = cloneDeep(child); return _children; }, {}); dash.children = children; const isTypeDashboard = dash.deviceType && !!dash.deviceTypeValue; if (isTypeDashboard) { dash.deviceTypeValue = newContext.type; } return dash; } copyProperties(obj, propertiesToCopy) { forEach(propertiesToCopy, (copyFn, property) => { if (obj[property]) { obj[property] = copyFn(obj[property]); } }); } replaceContextInDataPoints(dataPoints, newContext, oldContext) { dataPoints.forEach(dp => { dp.__target = this.replaceContextInObj(dp.__target, newContext, oldContext); }); return dataPoints; } replaceContextInObj(obj, newContext, oldContext) { if (obj && obj.id === oldContext.id) { Object.assign(obj, pick(newContext, ['id', 'name'])); } return obj; } getTabs$(mo, dashboardType, context) { const dashboards = this.getContextDashboards(mo, dashboardType); return this.getContextDashboards$(dashboards).pipe(mergeMap(dashboard => this.verifyDashboardAvailability$(dashboard)), // Due to MTM-62321 named context dashboards included fragments for device and groups. // therefore some device-info dashboards (from DM app) might occur in cockpit on device level. // We need to filter those out.. filter(([dashboard]) => { // list all identifiers of the dashboard (name, device, group, type) const dashboardIdentifiers = Object.keys(dashboard) .filter(key => key.startsWith(`${this.FRAGMENT_NAME}${this.INDEX_SPLIT}`)) .map(key => key.split(this.INDEX_SPLIT)[1]); // if dashboard is not named, it's safe to include it if (!dashboardIdentifiers.includes(ContextDashboardType.Named)) { return true; } // if dashboard is named, but also has a context identifier, we skip it if (dashboardIdentifiers.includes(ContextDashboardType.Device) || dashboardIdentifiers.includes(ContextDashboardType.Group)) { return false; } // all others are good.. return true; }), mergeMap(([dashboard]) => this.removeDashboardMoProperty(dashboard)), tap(dashboard => this.cacheDashboard(dashboard)), map(dashboard => this.createDashboardTab(dashboard, context)), toArray()); } verifyDashboardAvailability$(dashboard) { const globalRolesIds = dashboard?.c8y_Dashboard?.globalRolesIds; const canEdit = from(this.permissions.canEdit(this.INVENTORY_ROLES, dashboard, { skipRequestCheck: true })); const hasAnyGlobalRole = !globalRolesIds || globalRolesIds === ALL_GLOBAL_ROLES_SELECTED ? of(true) : of(this.permissions.hasAnyGlobalRole(globalRolesIds)); return combineLatest([of(dashboard), canEdit, hasAnyGlobalRole]).pipe(filter(([, canEdit, hasAnyGlobalRole]) => canEdit || hasAnyGlobalRole)); } getContextDashboards$(request) { return from(request).pipe(mergeMap(response => this.adjustDashboardFor24Columns(response.data))); } /** * Cleans already corrupted dashboards from dashboardMo property. * Added to fix dashboards on the cloud instance (eu-latest). * @deprecated This is going to be removed after 1007.7.0. */ async removeDashboardMoProperty(dashboard) { const dashboardCopy = cloneDeep(dashboard); const children = get(dashboardCopy, 'c8y_Dashboard.children'); let updateDashboard = false; forEach(children, child => { if (get(child, 'componentTransformConfigWithContext')) { delete child.componentTransformConfigWithContext; updateDashboard = true; } if (get(child, 'config.dashboardMo')) { delete child.config.dashboardMo; updateDashboard = true; } }); if (updateDashboard) { await this.update(dashboardCopy); } return dashboardCopy; } cacheDashboard(dashboard) { this.cache.set(dashboard.id, dashboard); } createDashboardTab(dashboard, context) { const { c8y_Dashboard: _dashboard, id } = dashboard; return { icon: _dashboard.icon, path: `${this.DASHBOARD_ROUTE_PATH}/${id}`, label: _dashboard.name, priority: _dashboard.priority, hide: this.isReport(dashboard), badge: _dashboard.deviceType && this.shouldAllowToSetDashboardType(dashboard, context) ? gettext('Dashboard template') : null, tooltipText: _dashboard.description || gettext('Dashboard template') }; } clean(dashboard) { const jsonString = JSON.stringify(dashboard, (key, value) => { if (key === '$$hashKey' || key === 'klasses') { return undefined; } return value; }); return JSON.parse(jsonString); } getNamedDashboard(name) { return this.inventory.list({ fragmentType: `${this.FRAGMENT_NAME}${this.INDEX_SPLIT}${ContextDashboardType.Named}${this.INDEX_SPLIT}${name}`, pageSize: 1 }); } createDashboardFragment(mo, type) { let value; if (mo.c8y_Report) { value = `${this.REPORT_PARTIAL_NAME}${mo.id}`; } else { value = type === ContextDashboardType.Type ? mo.type : mo.id; } return `${this.FRAGMENT_NAME}${this.INDEX_SPLIT}${type}${this.INDEX_SPLIT}${value}`; } shouldSetGlobal(dashboard, context) { if ((!context && this.isNamed(dashboard) && !this.isReport(dashboard)) || this.isDeviceType(dashboard)) { return {}; } return null; } getTemporaryDashboard(dashboardCfg) { return { c8y_Dashboard: { ...dashboardCfg, name: dashboardCfg.name || 'Temporary Dashboard', isTransient: true, description: gettext('This is a temporary, non-editable dashboard displayed due to insufficient permissions.') }, [`c8y_Dashboard!name!${dashboardCfg.name}`]: {} }; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContextDashboardService, deps: [{ token: i1.InventoryService }, { token: i2.TabsService }, { token: i2.ModalService }, { token: i3.TranslateService }, { token: i4.Router }, { token: i2.NavigatorService }, { token: i2.Permissions }, { token: i2.AlertService }, { token: i2.DynamicComponentService }, { token: i2.GroupService }, { token: i2.OptionsService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContextDashboardService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ContextDashboardService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1.InventoryService }, { type: i2.TabsService }, { type: i2.ModalService }, { type: i3.TranslateService }, { type: i4.Router }, { type: i2.NavigatorService }, { type: i2.Permissions }, { type: i2.AlertService }, { type: i2.DynamicComponentService }, { type: i2.GroupService }, { type: i2.OptionsService }] }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY29udGV4dC1kYXNoYm9hcmQuc2VydmljZS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uLy4uL2NvbnRleHQtZGFzaGJvYXJkL2NvbnRleHQtZGFzaGJvYXJkLnNlcnZpY2UudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFVBQVUsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUMzQyxPQUFPLEVBQTBCLE1BQU0sRUFBRSxNQUFNLGlCQUFpQixDQUFDO0FBQ2pFLE9BQU8sRUFBa0IsZ0JBQWdCLEVBQWUsV0FBVyxFQUFFLE1BQU0sYUFBYSxDQUFDO0FBQ3pGLE9BQU8sRUFDTCxZQUFZLEVBR1osdUJBQXVCLEVBQ3ZCLGlCQUFpQixFQUNqQixPQUFPLEVBQ1AsWUFBWSxFQUNaLFlBQVksRUFDWixhQUFhLEVBQ2IsZ0JBQWdCLEVBQ2hCLCtCQUErQixFQUMvQixjQUFjLEVBQ2QsV0FBVyxFQUNYLE1BQU0sRUFFTixXQUFXLEVBQ1gsV0FBVyxFQUVaLE1BQU0scUJBQXFCLENBQUM7QUFDN0IsT0FBTyxFQUFFLGdCQUFnQixFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDdkQsT0FBTyxFQUNMLE1BQU0sRUFDTixTQUFTLEVBQ1QsT0FBTyxFQUNQLEdBQUcsRUFDSCxHQUFHLEVBQ0gsS0FBSyxFQUNMLElBQUksRUFDSixJQUFJLEVBQ0osTUFBTSxFQUNOLEdBQUcsRUFDSCxJQUFJLEVBQ0wsTUFBTSxXQUFXLENBQUM7QUFDbkIsT0FBTyxFQUFFLGFBQWEsRUFBRSxJQUFJLEVBQWMsRUFBRSxFQUFFLE9BQU8sRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUNwRSxPQUFPLEVBQUUsVUFBVSxFQUFFLE1BQU0sRUFBRSxHQUFHLEVBQUUsUUFBUSxFQUFFLEdBQUcsRUFBRSxZQUFZLEVBQUUsT0FBTyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDL0YsT0FBTyxFQUNMLHlCQUF5QixFQUl6QixvQkFBb0IsRUFHcEIsa0JBQWtCLEVBQ2xCLHNC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