UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

1 lines • 94.2 kB
{"version":3,"file":"c8y-ngx-components-cockpit-config.mjs","sources":["../../cockpit-config/cockpit-config.model.ts","../../cockpit-config/cockpit-config.guard.ts","../../cockpit-config/cockpit-config.service.ts","../../cockpit-config/misc-config.component.ts","../../cockpit-config/misc-config.component.html","../../cockpit-config/feature-config.component.ts","../../cockpit-config/feature-config.component.html","../../cockpit-config/root-node-config.component.ts","../../cockpit-config/root-node-config.component.html","../../cockpit-config/home-dashboard-config.component.ts","../../cockpit-config/home-dashboard-config.component.html","../../cockpit-config/cockpit-configuration.component.ts","../../cockpit-config/cockpit-configuration.component.html","../../cockpit-config/setup/cockpit-setup-step.ts","../../cockpit-config/setup/cockpit-setup-stepper-buttons.component.ts","../../cockpit-config/setup/cockpit-setup-stepper-buttons.component.html","../../cockpit-config/setup/cockpit-setup-step1.component.ts","../../cockpit-config/setup/cockpit-setup-step1.component.html","../../cockpit-config/setup/cockpit-setup-step2.component.ts","../../cockpit-config/setup/cockpit-setup-step2.component.html","../../cockpit-config/setup/cockpit-setup-step3.component.ts","../../cockpit-config/setup/cockpit-setup-step3.component.html","../../cockpit-config/setup/cockpit-setup-step4.component.ts","../../cockpit-config/setup/cockpit-setup-step4.component.html","../../cockpit-config/cockpit-config.module.ts","../../cockpit-config/c8y-ngx-components-cockpit-config.ts"],"sourcesContent":["export const DEFAULT_HOME_DASHBOARD_NAME = 'home-cockpit1';\nexport const USER_HOME_DASHBOARD_NAME = 'home-cockpit-user';\n\nexport interface CockpitConfig {\n rootNodes: CockpitConfigRootNode[];\n features: {\n alarms: boolean;\n events: boolean;\n dataExplorer: boolean;\n groups: boolean;\n reports: boolean;\n exports: boolean;\n dataPointLibrary: boolean;\n globalSmartRules: boolean;\n smartRules: boolean;\n subassets: boolean;\n search: boolean;\n dashboardManager: boolean;\n [key: string]: boolean;\n };\n hideNavigator: boolean;\n homeDashboardName: string;\n userSpecificHomeDashboard: boolean;\n icon: {\n class: string;\n };\n appTitle?: string;\n htmlWidgetDisableSanitization?: boolean;\n htmlWidgetDefaultToAdvancedMode?: boolean;\n}\n\nexport const DEFAULT_CONFIG: CockpitConfig = {\n rootNodes: [],\n features: {\n alarms: true,\n events: true,\n dataExplorer: true,\n groups: true,\n reports: true,\n exports: true,\n dataPointLibrary: true,\n globalSmartRules: true,\n smartRules: true,\n subassets: true,\n search: true,\n dashboardManager: true\n },\n hideNavigator: false,\n homeDashboardName: DEFAULT_HOME_DASHBOARD_NAME,\n userSpecificHomeDashboard: false,\n icon: {\n class: 'c8y-cockpit'\n },\n htmlWidgetDisableSanitization: false,\n htmlWidgetDefaultToAdvancedMode: false\n};\n\nexport interface CockpitConfigRootNode {\n id: string;\n name: string;\n hideDevices?: boolean;\n}\n\nexport enum HomeDashboardType {\n /**\n * Shared by all Cockpit apps\n */\n DEFAULT,\n /**\n * Only for the current Cockpit.\n */\n APP,\n /**\n * Only for the current user.\n */\n USER\n}\n\nexport const COCKPIT_CONFIG_PATH = 'cockpit-application-configuration';\n","import { Injectable } from '@angular/core';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport {\n AppStateService,\n NavigatorNode,\n NavigatorNodeFactory,\n Permissions\n} from '@c8y/ngx-components';\nimport { EcosystemService } from '@c8y/ngx-components/ecosystem/shared';\nimport { COCKPIT_CONFIG_PATH } from './cockpit-config.model';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class CockpitConfigGuard implements NavigatorNodeFactory {\n protected readonly configNode: NavigatorNode = new NavigatorNode({\n path: `/${COCKPIT_CONFIG_PATH}`,\n parent: gettext('Configuration'),\n label: gettext('Application configuration'),\n icon: 'imac-settings',\n preventDuplicates: true\n });\n\n constructor(\n private permissions: Permissions,\n private appState: AppStateService,\n private ecosystemService: EcosystemService\n ) {}\n\n get(): NavigatorNode {\n if (this.canActivate()) {\n return this.configNode;\n }\n return;\n }\n\n canActivate(): boolean {\n return (\n this.permissions.hasRole(Permissions.ROLE_APPLICATION_MANAGEMENT_ADMIN) &&\n this.ecosystemService.isOwner(this.appState.currentApplication.value)\n );\n }\n}\n","import { Inject, Injectable, Optional } from '@angular/core';\nimport { ApplicationService, InventoryService } from '@c8y/client';\nimport {\n AppStateService,\n ExtensionFactory,\n NavigatorNode,\n NavigatorService,\n OptionsService,\n Permissions,\n SearchService,\n Tab,\n TabsService\n} from '@c8y/ngx-components';\nimport {\n AssetNavigatorConfig,\n AssetNodeService,\n ASSET_NAVIGATOR_CONFIG\n} from '@c8y/ngx-components/assets-navigator';\nimport { isUndefined, startCase } from 'lodash-es';\nimport { map, take } from 'rxjs/operators';\nimport { CockpitConfig, DEFAULT_CONFIG, DEFAULT_HOME_DASHBOARD_NAME } from './cockpit-config.model';\nimport { uniqBy } from 'lodash-es';\n\n@Injectable({\n providedIn: 'root'\n})\nexport class CockpitConfigService {\n currentConfig: CockpitConfig = DEFAULT_CONFIG;\n nodes: NavigatorNode[] = [];\n private navigationFactory: ExtensionFactory<NavigatorNode> = {\n get: () => this.nodes\n };\n\n private readonly DEFAULT_NODE_PRIORITY = 2000;\n\n get excludedFeatureKeys() {\n return Object.keys(this.currentConfig.features).filter(\n key => !this.currentConfig.features[key]\n );\n }\n\n constructor(\n private navigatorService: NavigatorService,\n private tabsService: TabsService,\n private searchService: SearchService,\n private assetNodeService: AssetNodeService,\n private inventoryService: InventoryService,\n private appState: AppStateService,\n private applicationService: ApplicationService,\n private optionsService: OptionsService,\n private permissions: Permissions,\n @Optional() @Inject(ASSET_NAVIGATOR_CONFIG) public moduleConfig: AssetNavigatorConfig\n ) {\n this.registerFilterForFeatures();\n this.init();\n }\n\n init() {\n if (this.optionsService.hideNavigator !== undefined) {\n this.currentConfig.hideNavigator = this.optionsService.hideNavigator;\n }\n this.appState.currentApplicationConfig.subscribe(config => {\n if (config) {\n this.currentConfig = { ...DEFAULT_CONFIG, ...config } as CockpitConfig;\n }\n });\n\n this.appState.currentApplicationConfig.pipe(take(1)).subscribe(() => {\n this.setRootNodes();\n });\n }\n\n /**\n * Save and apply new cockpit configuration\n * @param config - New cockpit configuration\n */\n async saveConfig(config: CockpitConfig) {\n this.currentConfig = config;\n await this.storeApplicationConfig(this.currentConfig);\n await this.updateApplication(this.currentConfig);\n }\n\n /**\n * Update current application using the provided configuration\n * @param config - Cockpit configuration\n */\n async updateApplication(config: CockpitConfig) {\n if (!config.appTitle && !config.icon) {\n return;\n }\n const application = this.appState.currentApplication.value;\n\n const updatedApp = {\n ...application,\n name: config.appTitle || application.name\n };\n\n const manifest = {\n ...updatedApp.manifest,\n name: updatedApp.name,\n icon: config.icon || { class: 'c8y-cockpit' }\n };\n\n await this.applicationService.update({\n id: updatedApp.id,\n name: updatedApp.name,\n manifest\n });\n\n this.appState.currentApplication.next(updatedApp);\n }\n\n refresh() {\n this.optionsService.hideNavigator = this.currentConfig.hideNavigator;\n this.navigatorService.refresh();\n this.searchService.refresh();\n }\n\n async setRootNodes() {\n this.addNodesToFactories();\n this.nodes.length = 0;\n for (const node of this.currentConfig.rootNodes) {\n try {\n const { data } = await this.inventoryService.detail(node.id);\n if (data) {\n this.nodes.push(\n this.assetNodeService.createAssetNode({\n mo: data,\n hideDevices: node.hideDevices,\n priority: isUndefined(this.moduleConfig?.rootNodePriority)\n ? this.DEFAULT_NODE_PRIORITY\n : this.moduleConfig.rootNodePriority\n })\n );\n }\n } catch (error) {\n // do nothing\n }\n }\n this.nodes = uniqBy(this.nodes, 'path');\n this.refresh();\n }\n\n getAppDashboardName() {\n return `${DEFAULT_HOME_DASHBOARD_NAME.substring(0, DEFAULT_HOME_DASHBOARD_NAME.length - 1)}_${\n this.appState.state.app.id\n }`;\n }\n\n private async storeApplicationConfig(config: CockpitConfig) {\n await this.appState.updateCurrentApplicationConfig<CockpitConfig>(config);\n }\n\n private addNodesToFactories() {\n const nodeInFactories = this.navigatorService.factories.find(\n fatcory => fatcory === this.navigationFactory\n );\n if (!nodeInFactories) {\n this.navigatorService.factories.push(this.navigationFactory);\n }\n }\n\n private registerFilterForFeatures() {\n this.navigatorService.items$ = this.navigatorService.items$.pipe(\n map(nodes => this.setHiddenAttrLock(nodes)),\n map(nodes => this.filterNavigatorNode(nodes))\n );\n this.tabsService.items$ = this.tabsService.items$.pipe(map(tabs => this.filterTabs(tabs)));\n this.searchService.items$ = this.searchService.items$.pipe(\n map(search => (this.currentConfig.features.search ? search : []))\n );\n }\n\n private setHiddenAttrLock(nodes) {\n nodes.forEach(node => {\n Object.keys(this.currentConfig.features).forEach(key => {\n const childNode = node.find(startCase(key).toLowerCase());\n\n if (childNode) {\n if (\n !this.permissions.hasRole(Permissions.ROLE_APPLICATION_MANAGEMENT_ADMIN) &&\n childNode.lockHiddenAttr === undefined &&\n childNode.hidden === true\n ) {\n childNode.lockHiddenAttr = childNode.hidden;\n }\n }\n });\n });\n return nodes;\n }\n\n private filterTabs(tabs: Tab[]) {\n return tabs.filter(tab => !this.excludedFeatureKeys.some(key => tab.featureId === key));\n }\n\n private filterNavigatorNode(nodes: NavigatorNode[]) {\n if (!this.currentConfig) {\n return nodes;\n }\n const disabledFeatures = this.excludedFeatureKeys;\n const filteredNodes = nodes.filter(\n node => !disabledFeatures.some(key => node.featureId === key)\n );\n\n this.showAllChildrenNodes(nodes);\n this.hideChildrenNodesThatAreDisabled(nodes, disabledFeatures);\n return filteredNodes;\n }\n\n private hideChildrenNodesThatAreDisabled(nodes: NavigatorNode[], disabledFeatures: string[]) {\n nodes.forEach(node =>\n disabledFeatures.forEach(key => {\n const childNode = node.find(key, 'featureId');\n if (childNode) {\n childNode.hidden = true;\n }\n })\n );\n }\n\n private showAllChildrenNodes(nodes: NavigatorNode[]) {\n nodes.forEach(node => {\n Object.keys(this.currentConfig.features).forEach(key => {\n const childNode = node.find(startCase(key).toLowerCase());\n\n if (childNode) {\n if (childNode.lockHiddenAttr === true) {\n return;\n }\n\n childNode.hidden = false;\n }\n });\n });\n }\n}\n","import { Component, Input } from '@angular/core';\nimport { CockpitConfig } from './cockpit-config.model';\nimport { ListGroupComponent, ListItemComponent, C8yTranslatePipe } from '@c8y/ngx-components';\nimport { FormsModule } from '@angular/forms';\n\n@Component({\n selector: 'c8y-misc-config',\n templateUrl: './misc-config.component.html',\n imports: [ListGroupComponent, ListItemComponent, FormsModule, C8yTranslatePipe]\n})\nexport class MiscConfigComponent {\n @Input()\n config: CockpitConfig;\n}\n","<c8y-list-group class=\"separator-top\">\n <c8y-li>\n <div class=\"d-flex a-i-center\">\n <p>{{ 'Always collapse navigator on start up' | translate }}</p>\n <label\n class=\"c8y-switch c8y-switch--inline m-l-auto\"\n title=\"{{ 'Collapse navigator on start up' | translate }}\"\n >\n <input data-cy=\"c8y-misc-config--collapse-button\" type=\"checkbox\" [(ngModel)]=\"config.hideNavigator\" name=\"hideNavigator\" />\n <span></span>\n </label>\n </div>\n </c8y-li>\n</c8y-list-group>\n","import { Component, input, output } from '@angular/core';\nimport { CockpitConfig } from './cockpit-config.model';\nimport {\n ListGroupComponent,\n ListItemComponent,\n ListItemIconComponent,\n C8yTranslateDirective,\n C8yTranslatePipe,\n PluginLoadedPipe\n} from '@c8y/ngx-components';\nimport { AsyncPipe } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\n\n@Component({\n selector: 'c8y-feature-config',\n templateUrl: './feature-config.component.html',\n imports: [\n ListGroupComponent,\n ListItemComponent,\n ListItemIconComponent,\n C8yTranslateDirective,\n FormsModule,\n C8yTranslatePipe,\n AsyncPipe,\n PluginLoadedPipe\n ]\n})\nexport class FeatureConfigComponent {\n config = input.required<CockpitConfig>();\n onUpdate = output<void>();\n\n updateFeatures(): void {\n this.onUpdate.emit();\n }\n}\n","<c8y-list-group>\n @if ('SearchModule' | c8yPluginLoaded | async) {\n <c8y-li data-cy=\"feature-config--feature-list\">\n <c8y-li-icon icon=\"search\"></c8y-li-icon>\n <div class=\"d-flex a-i-center\">\n <div>\n <p>{{ 'Global search' | translate }}</p>\n <p>\n <small translate>Display the global search in the main header.</small>\n </p>\n </div>\n <label\n class=\"c8y-switch c8y-switch--inline m-l-auto\"\n title=\"{{ 'Global search' | translate }}\"\n >\n <input\n name=\"search\"\n type=\"checkbox\"\n [(ngModel)]=\"config().features.search\"\n (change)=\"updateFeatures()\"\n />\n <span></span>\n </label>\n </div>\n </c8y-li>\n }\n <c8y-li data-cy=\"feature-config--feature-list\">\n <c8y-li-icon icon=\"c8y-group\"></c8y-li-icon>\n <div class=\"d-flex a-i-center\">\n <div>\n <p>{{ 'Groups' | translate }}</p>\n <p>\n <small translate>Display top level groups under the Groups navigator menu.</small>\n </p>\n </div>\n <label\n class=\"c8y-switch c8y-switch--inline m-l-auto\"\n title=\"{{ 'Groups' | translate }}\"\n >\n <input\n name=\"groups\"\n type=\"checkbox\"\n [(ngModel)]=\"config().features.groups\"\n (change)=\"updateFeatures()\"\n />\n <span></span>\n </label>\n </div>\n </c8y-li>\n @if ('CockpitAlarmsModule' | c8yPluginLoaded | async) {\n <c8y-li data-cy=\"feature-config--feature-list\">\n <c8y-li-icon icon=\"bell\"></c8y-li-icon>\n <div class=\"d-flex a-i-center\">\n <div>\n <p>{{ 'Alarms' | translate }}</p>\n <p>\n <small translate>Display a link to the global alarms list in the navigator menu.</small>\n </p>\n </div>\n <label\n class=\"c8y-switch c8y-switch--inline m-l-auto\"\n title=\"{{ 'Global Alarms view' | translate }}\"\n >\n <input\n name=\"alarms\"\n type=\"checkbox\"\n [(ngModel)]=\"config().features.alarms\"\n (change)=\"updateFeatures()\"\n />\n <span></span>\n </label>\n </div>\n </c8y-li>\n }\n @if ('eventsCockpitProviders' | c8yPluginLoaded | async) {\n <c8y-li data-cy=\"feature-config--feature-list\">\n <c8y-li-icon icon=\"online1\"></c8y-li-icon>\n <div class=\"d-flex a-i-center\">\n <div>\n <p>{{ 'Events' | translate }}</p>\n <p>\n <small translate>Display a link to the global events list in the navigator menu.</small>\n </p>\n </div>\n <label\n class=\"c8y-switch c8y-switch--inline m-l-auto\"\n title=\"{{ 'Global Events view' | translate }}\"\n >\n <input\n name=\"events\"\n type=\"checkbox\"\n [(ngModel)]=\"config().features.events\"\n (change)=\"updateFeatures()\"\n />\n <span></span>\n </label>\n </div>\n </c8y-li>\n }\n <c8y-li data-cy=\"feature-config--feature-list\">\n <c8y-li-icon icon=\"c8y-data-explorer\"></c8y-li-icon>\n <div class=\"d-flex a-i-center\">\n <div>\n <p>{{ 'Data explorer' | translate }}</p>\n <p>\n <small translate>\n Display the data explorer in the navigator menu and on the group tabs.\n </small>\n </p>\n </div>\n <label\n class=\"c8y-switch c8y-switch--inline m-l-auto\"\n title=\"{{ 'Data explorer' | translate }}\"\n >\n <input\n name=\"dataExplorer\"\n type=\"checkbox\"\n [(ngModel)]=\"config().features.dataExplorer\"\n (change)=\"updateFeatures()\"\n />\n <span></span>\n </label>\n </div>\n </c8y-li>\n @if ('ReportDashboardModule' | c8yPluginLoaded | async) {\n <c8y-li data-cy=\"feature-config--feature-list\">\n <c8y-li-icon icon=\"c8y-reports\"></c8y-li-icon>\n <div class=\"d-flex a-i-center\">\n <div>\n <p>{{ 'Reports' | translate }}</p>\n <p>\n <small translate>Display a link to the Reports list in the navigator menu.</small>\n </p>\n </div>\n <label\n class=\"c8y-switch c8y-switch--inline m-l-auto\"\n title=\"{{ 'Reports' | translate }}\"\n >\n <input\n name=\"reports\"\n type=\"checkbox\"\n [(ngModel)]=\"config().features.reports\"\n (change)=\"updateFeatures()\"\n />\n <span></span>\n </label>\n </div>\n </c8y-li>\n }\n @if ('exportsProviders' | c8yPluginLoaded | async) {\n <c8y-li data-cy=\"feature-config--feature-list\">\n <c8y-li-icon icon=\"graph-report\"></c8y-li-icon>\n <div class=\"d-flex a-i-center\">\n <div>\n <p>{{ 'Exports' | translate }}</p>\n <p>\n <small translate>\n Display a link to the Exports list under the Configuration navigator menu.\n </small>\n </p>\n </div>\n <label\n class=\"c8y-switch c8y-switch--inline m-l-auto\"\n title=\"{{ 'Exports' | translate }}\"\n >\n <input\n name=\"exports\"\n type=\"checkbox\"\n [(ngModel)]=\"config().features.exports\"\n (change)=\"updateFeatures()\"\n />\n <span></span>\n </label>\n </div>\n </c8y-li>\n }\n @if ('DatapointLibraryModule' | c8yPluginLoaded | async) {\n <c8y-li data-cy=\"feature-config--feature-list\">\n <c8y-li-icon icon=\"c8y-data-points\"></c8y-li-icon>\n <div class=\"d-flex a-i-center\">\n <div>\n <p>{{ 'Data point library' | translate }}</p>\n <p>\n <small translate>\n Display a link to the Data point library under the Configuration navigator menu.\n </small>\n </p>\n </div>\n <label\n class=\"c8y-switch c8y-switch--inline m-l-auto\"\n title=\"{{ 'Data point library' | translate }}\"\n >\n <input\n name=\"dataPointLibrary\"\n type=\"checkbox\"\n [(ngModel)]=\"config().features.dataPointLibrary\"\n (change)=\"updateFeatures()\"\n />\n <span></span>\n </label>\n </div>\n </c8y-li>\n }\n <c8y-li data-cy=\"feature-config--feature-list\">\n <c8y-li-icon icon=\"c8y-smart-rules\"></c8y-li-icon>\n <div class=\"d-flex a-i-center\">\n <div>\n <p>{{ 'Global smart rules' | translate }}</p>\n <p>\n <small translate>\n Display a link to the Global smart rules under the Configuration navigator menu.\n </small>\n </p>\n </div>\n <label\n class=\"c8y-switch c8y-switch--inline m-l-auto\"\n title=\"{{ 'Global smart rules' | translate }}\"\n >\n <input\n name=\"globalSmartRules\"\n type=\"checkbox\"\n [(ngModel)]=\"config().features.globalSmartRules\"\n (change)=\"updateFeatures()\"\n />\n <span></span>\n </label>\n </div>\n </c8y-li>\n <c8y-li data-cy=\"feature-config--feature-list\">\n <c8y-li-icon icon=\"c8y-group-open\"></c8y-li-icon>\n <div class=\"d-flex a-i-center\">\n <div>\n <p>{{ 'Subassets view' | translate }}</p>\n <p><small translate>Display the \"Subassets\" tab on groups.</small></p>\n </div>\n <label\n class=\"c8y-switch c8y-switch--inline m-l-auto\"\n title=\"{{ 'Subassets view' | translate }}\"\n >\n <input\n name=\"subassets\"\n type=\"checkbox\"\n [(ngModel)]=\"config().features.subassets\"\n (change)=\"updateFeatures()\"\n />\n <span></span>\n </label>\n </div>\n </c8y-li>\n <c8y-li data-cy=\"feature-config--feature-list\">\n <c8y-li-icon icon=\"c8y-smart-rules\"></c8y-li-icon>\n <div class=\"d-flex a-i-center\">\n <div>\n <p>{{ 'Smart rules for devices and groups' | translate }}</p>\n <p>\n <small translate>Display the smart rules tab on groups and devices.</small>\n </p>\n </div>\n <label\n class=\"c8y-switch c8y-switch--inline m-l-auto\"\n title=\"{{ 'Smart rules for devices and groups' | translate }}\"\n >\n <input\n name=\"smartRules\"\n type=\"checkbox\"\n [(ngModel)]=\"config().features.smartRules\"\n (change)=\"updateFeatures()\"\n />\n <span></span>\n </label>\n </div>\n </c8y-li>\n @if ('DashboardManagerModule' | c8yPluginLoaded | async) {\n <c8y-li data-cy=\"feature-config--feature-list\">\n <c8y-li-icon icon=\"management1\"></c8y-li-icon>\n <div class=\"d-flex a-i-center\">\n <div>\n <p>{{ 'Dashboard manager' | translate }}</p>\n <p>\n <small translate>\n Display a link to the Dashboard manager under the Configuration navigator menu.\n </small>\n </p>\n </div>\n <label\n class=\"c8y-switch c8y-switch--inline m-l-auto\"\n title=\"{{ 'Dashboard manager' | translate }}\"\n >\n <input\n name=\"dashboardManager\"\n type=\"checkbox\"\n [(ngModel)]=\"config().features.dashboardManager\"\n (change)=\"updateFeatures()\"\n />\n <span></span>\n </label>\n </div>\n </c8y-li>\n }\n</c8y-list-group>\n","import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { CockpitConfig, CockpitConfigRootNode } from './cockpit-config.model';\nimport {\n C8yTranslateDirective,\n ListGroupComponent,\n ListItemComponent,\n EmptyStateComponent,\n ListItemIconComponent,\n IconDirective,\n C8yTranslatePipe,\n GetGroupIconPipe\n} from '@c8y/ngx-components';\nimport { AsyncPipe } from '@angular/common';\nimport { FormsModule } from '@angular/forms';\nimport { TooltipDirective } from 'ngx-bootstrap/tooltip';\nimport { AssetSelectorComponent } from '@c8y/ngx-components/assets-navigator';\n\n@Component({\n selector: 'c8y-root-node-config',\n templateUrl: './root-node-config.component.html',\n imports: [\n C8yTranslateDirective,\n ListGroupComponent,\n ListItemComponent,\n EmptyStateComponent,\n ListItemIconComponent,\n FormsModule,\n TooltipDirective,\n IconDirective,\n AssetSelectorComponent,\n C8yTranslatePipe,\n AsyncPipe,\n GetGroupIconPipe\n ]\n})\nexport class RootNodeConfigComponent {\n @Input()\n config: CockpitConfig;\n\n @Input() disabled = false;\n\n @Output()\n onUpdate = new EventEmitter<void>();\n\n /**\n * Removes one of the root nodes.\n * @param node The node to remove.\n */\n removeNavigatorNode(node: CockpitConfigRootNode) {\n const index = this.config.rootNodes.indexOf(node);\n if (index > -1) {\n const newNodes = this.config.rootNodes.filter((_, i) => i !== index);\n this.config.rootNodes = newNodes;\n this.onUpdate.emit();\n }\n }\n}\n","<div class=\"col-sm-6\">\n <label\n title=\"{{ 'Current top level nodes' | translate }}\"\n translate\n >\n Current top level nodes\n </label>\n <c8y-list-group class=\"separator-top\">\n @if (config.rootNodes.length === 0) {\n <c8y-li>\n <c8y-ui-empty-state\n [icon]=\"'folder-open'\"\n [title]=\"'No top level nodes set.' | translate\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n </c8y-li>\n }\n @for (node of config.rootNodes; track node.id; let index = $index) {\n <c8y-li>\n @if (node.id | getGroupIcon | async; as icon) {\n <c8y-li-icon [icon]=\"icon\"></c8y-li-icon>\n } @else {\n <c8y-li-icon\n class=\"icon-spin loadingIndicator\"\n [icon]=\"'circle-o-notch'\"\n ></c8y-li-icon>\n }\n <div class=\"content-flex-30\">\n <div class=\"col-6\">\n <div\n class=\"text-truncate\"\n title=\"{{ node.name }}\"\n >\n {{ node.name }}\n </div>\n </div>\n <div class=\"col-4\">\n <label\n class=\"c8y-switch c8y-switch--inline d-flex\"\n title=\"{{ 'Hide devices' | translate }}\"\n >\n <input\n name=\"node.{{ index }}.hideDevices\"\n type=\"checkbox\"\n [(ngModel)]=\"node.hideDevices\"\n (change)=\"onUpdate.emit()\"\n [disabled]=\"disabled\"\n />\n <span></span>\n <small class=\"text-truncate a-s-center l-h-1\">{{ 'Hide devices' | translate }}</small>\n </label>\n </div>\n <div class=\"col-2 text-right\">\n <div class=\"d-flex fit-w\">\n <button\n class=\"btn-dot btn-dot--danger m-l-auto\"\n [attr.aria-label]=\"'Remove' | translate\"\n tooltip=\"{{ 'Remove' | translate }}\"\n type=\"button\"\n [disabled]=\"disabled\"\n [delay]=\"500\"\n (click)=\"removeNavigatorNode(node)\"\n >\n <i c8yIcon=\"minus-circle\"></i>\n </button>\n </div>\n </div>\n </div>\n </c8y-li>\n }\n </c8y-list-group>\n</div>\n\n<div\n class=\"col-sm-6 col-md-5\"\n style=\"height: calc(100vh - 430px)\"\n>\n <label\n title=\"{{ 'Select top level nodes' | translate }}\"\n translate\n >\n Select top level nodes\n </label>\n <c8y-asset-selector\n class=\"border-top d-block\"\n name=\"rootNodes\"\n [config]=\"{ groupsOnly: true, multi: true, groupsSelectable: true }\"\n [(ngModel)]=\"config.rootNodes\"\n [disabled]=\"disabled\"\n (onSelected)=\"onUpdate.emit()\"\n ></c8y-asset-selector>\n</div>\n","import { Component, Input } from '@angular/core';\nimport {\n CockpitConfig,\n DEFAULT_HOME_DASHBOARD_NAME,\n HomeDashboardType,\n USER_HOME_DASHBOARD_NAME\n} from './cockpit-config.model';\nimport { CockpitConfigService } from './cockpit-config.service';\nimport {\n ListGroupComponent,\n ListItemComponent,\n ListItemRadioComponent,\n C8yTranslateDirective\n} from '@c8y/ngx-components';\n\n@Component({\n selector: 'c8y-home-dashboard-config',\n templateUrl: './home-dashboard-config.component.html',\n imports: [ListGroupComponent, ListItemComponent, ListItemRadioComponent, C8yTranslateDirective]\n})\nexport class HomeDashboardConfigComponent {\n @Input()\n config: CockpitConfig;\n\n /**\n * The types of dashboard that can be configured.\n */\n readonly homeDashboardTypes = HomeDashboardType;\n\n /**\n * @ignore\n */\n constructor(private cockpitConfigService: CockpitConfigService) {}\n\n /**\n * @ignore\n */\n dashboardChange(selected: boolean, type: HomeDashboardType) {\n if (!selected) {\n return;\n }\n\n switch (type) {\n case this.homeDashboardTypes.DEFAULT: {\n this.config.homeDashboardName = DEFAULT_HOME_DASHBOARD_NAME;\n this.config.userSpecificHomeDashboard = false;\n break;\n }\n case this.homeDashboardTypes.APP: {\n this.config.homeDashboardName = this.cockpitConfigService.getAppDashboardName();\n this.config.userSpecificHomeDashboard = false;\n break;\n }\n case this.homeDashboardTypes.USER: {\n this.config.homeDashboardName = USER_HOME_DASHBOARD_NAME;\n this.config.userSpecificHomeDashboard = true;\n break;\n }\n }\n }\n\n /**\n * @ignore\n */\n verifySelected(type: HomeDashboardType) {\n if (type === this.homeDashboardTypes.USER) {\n return this.config.userSpecificHomeDashboard;\n }\n if (type === this.homeDashboardTypes.DEFAULT) {\n return this.config.homeDashboardName === DEFAULT_HOME_DASHBOARD_NAME;\n }\n return this.config.homeDashboardName === this.cockpitConfigService.getAppDashboardName();\n }\n}\n","<c8y-list-group>\n <c8y-li data-cy=\"home-dashboard-config--dashboard-list\">\n <c8y-li-radio\n (onSelect)=\"dashboardChange($event, homeDashboardTypes.DEFAULT)\"\n [selected]=\"verifySelected(homeDashboardTypes.DEFAULT)\"\n ></c8y-li-radio>\n <p translate>Default home dashboard</p>\n <small translate>Changes done in the home dashboard are reflected across the platform.</small>\n </c8y-li>\n <c8y-li data-cy=\"home-dashboard-config--dashboard-list\">\n <c8y-li-radio\n (onSelect)=\"dashboardChange($event, homeDashboardTypes.APP)\"\n [selected]=\"verifySelected(homeDashboardTypes.APP)\"\n ></c8y-li-radio>\n <p translate>Custom home dashboard</p>\n <small translate>\n Changes done to the home dashboard are reflected only in the current application.\n </small>\n </c8y-li>\n <c8y-li data-cy=\"home-dashboard-config--dashboard-list\">\n <c8y-li-radio\n (onSelect)=\"dashboardChange($event, homeDashboardTypes.USER)\"\n [selected]=\"verifySelected(homeDashboardTypes.USER)\"\n ></c8y-li-radio>\n <p translate>User home dashboard</p>\n <small translate>\n Changes done to the home dashboard are reflected only for the current user. NOTE: This user\n needs to have inventory write permission.\n </small>\n </c8y-li>\n</c8y-list-group>\n","import { Component, OnInit } from '@angular/core';\nimport { gettext } from '@c8y/ngx-components/gettext';\nimport {\n AlertService,\n AppStateService,\n TitleComponent,\n BreadcrumbComponent,\n BreadcrumbItemComponent,\n C8yTranslateDirective,\n ListItemComponent,\n ListItemIconComponent,\n ProductExperienceDirective,\n C8yTranslatePipe,\n PluginLoadedPipe\n} from '@c8y/ngx-components';\nimport { CockpitConfig, DEFAULT_CONFIG } from './cockpit-config.model';\nimport { CockpitConfigService } from './cockpit-config.service';\nimport { FormsModule } from '@angular/forms';\nimport { IconSelectorWrapperComponent } from '@c8y/ngx-components/icon-selector';\nimport { MiscConfigComponent } from './misc-config.component';\nimport { PopoverDirective } from 'ngx-bootstrap/popover';\nimport { FeatureConfigComponent } from './feature-config.component';\nimport { RootNodeConfigComponent } from './root-node-config.component';\nimport { NgIf, AsyncPipe } from '@angular/common';\nimport { HomeDashboardConfigComponent } from './home-dashboard-config.component';\n\n@Component({\n selector: 'c8y-cockpit-configuration',\n templateUrl: './cockpit-configuration.component.html',\n imports: [\n TitleComponent,\n BreadcrumbComponent,\n BreadcrumbItemComponent,\n FormsModule,\n C8yTranslateDirective,\n IconSelectorWrapperComponent,\n MiscConfigComponent,\n PopoverDirective,\n FeatureConfigComponent,\n RootNodeConfigComponent,\n NgIf,\n HomeDashboardConfigComponent,\n ListItemComponent,\n ListItemIconComponent,\n ProductExperienceDirective,\n C8yTranslatePipe,\n AsyncPipe,\n PluginLoadedPipe\n ]\n})\nexport class CockpitConfigurationComponent implements OnInit {\n /**\n * The currently used configuration.\n */\n config: CockpitConfig = DEFAULT_CONFIG;\n\n rootNodeDisabled = false;\n\n constructor(\n private cockpitConfigService: CockpitConfigService,\n private alertService: AlertService,\n private appState: AppStateService\n ) {}\n\n /**\n * @ignore\n */\n ngOnInit() {\n this.config = this.cockpitConfigService.currentConfig;\n }\n\n /**\n * Stores the configuration and shows a success message.\n */\n async save() {\n try {\n await this.cockpitConfigService.saveConfig(this.config);\n this.alertService.success(gettext('Cockpit configuration saved.'));\n } catch (ex) {\n this.alertService.addServerFailure(ex);\n }\n }\n\n /**\n * @ignore\n */\n iconSelectionChange(icon: string): void {\n this.config.icon = { class: icon };\n this.appState.currentApplication.next({\n ...this.appState.currentApplication.value,\n ...{ config: this.config }\n });\n }\n\n /**\n * Updates the features to directly reflect the results of the change.\n */\n updateFeatures() {\n this.cockpitConfigService.currentConfig = this.config;\n this.cockpitConfigService.refresh();\n }\n\n /**\n * Updates the root nodes to directly reflect the results of the change.\n */\n async updateRootNodes() {\n this.rootNodeDisabled = true;\n this.cockpitConfigService.currentConfig = this.config;\n await this.cockpitConfigService.setRootNodes();\n this.rootNodeDisabled = false;\n }\n}\n","<c8y-title>{{ 'Application configuration' | translate }}</c8y-title>\n<c8y-breadcrumb>\n <c8y-breadcrumb-item\n [icon]=\"'c8y-tools'\"\n [label]=\"'Configuration' | translate\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n [icon]=\"'c8y-tools'\"\n [label]=\"'Application configuration' | translate\"\n ></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n<div class=\"row\">\n <div class=\"col-lg-12 col-lg-max\">\n <form #configForm=\"ngForm\">\n <div class=\"card card--fullpage\">\n <div class=\"card-header separator\">\n <div class=\"card-title\">\n {{ config.appTitle || ('Cockpit' | translate) }} {{ 'configuration' | translate }}\n </div>\n </div>\n\n <div class=\"inner-scroll\">\n <div class=\"card-block p-t-0 p-b-0\">\n <fieldset class=\"row separator-bottom p-t-24 p-b-24\">\n <div class=\"col-xs-12 col-sm-3 col-md-2 text-left-xs text-right-sm\">\n <div\n class=\"h4 text-medium d-inline-block m-r-4\"\n translate\n >\n Title, icon, and navigator collapse\n </div>\n </div>\n <div class=\"col-xs-12 col-sm-9 col-md-8 p-l-16\">\n <div class=\"d-flex a-i-start gap-16\">\n <div class=\"form-group d-inline-block\">\n <label>{{ 'Icon' | translate }}</label>\n <c8y-icon-selector-wrapper\n [selectedIcon]=\"config?.icon?.class || 'c8y-cockpit'\"\n [iconSize]=\"24\"\n (onSelect)=\"iconSelectionChange($event)\"\n ></c8y-icon-selector-wrapper>\n </div>\n <div class=\"form-group flex-grow\">\n <label\n for=\"confAppTitle\"\n translate\n >\n Change application title\n </label>\n <input\n class=\"form-control\"\n id=\"confAppTitle\"\n placeholder=\"{{ 'e.g. Cockpit' | translate }} \"\n type=\"text\"\n maxlength=\"254\"\n [(ngModel)]=\"config.appTitle\"\n [ngModelOptions]=\"{ standalone: true }\"\n />\n </div>\n </div>\n\n <c8y-misc-config [config]=\"config\"></c8y-misc-config>\n </div>\n </fieldset>\n <fieldset class=\"row separator-bottom p-t-24 p-b-24\">\n <div class=\"col-xs-12 col-sm-3 col-md-2 text-left-xs text-right-sm\">\n <div class=\"d-inline-flex m-b-16\">\n <div\n class=\"h4 text-medium m-r-4\"\n translate\n >\n Features\n </div>\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{\n 'Define which are the enabled features in the current application.'\n | translate\n }}\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </div>\n </div>\n <div class=\"col-xs-12 col-sm-9 col-md-8\">\n <c8y-feature-config\n [config]=\"config\"\n (onUpdate)=\"updateFeatures()\"\n ></c8y-feature-config>\n </div>\n </fieldset>\n <fieldset class=\"row separator-bottom p-t-24 p-b-24\">\n <div class=\"col-xs-12 col-sm-3 col-md-2 text-left-xs text-right-sm\">\n <div class=\"d-inline-flex m-b-16\">\n <div\n class=\"h4 text-medium m-r-8\"\n translate\n >\n Top level nodes\n </div>\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{\n 'Select which nodes to display in the top level of the navigator menu. By default, only Groups is shown.'\n | translate\n }}\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </div>\n </div>\n <div class=\"col-xs-12 col-sm-10 col-md-10\">\n <div class=\"row\">\n <c8y-root-node-config\n [config]=\"config\"\n (onUpdate)=\"updateRootNodes()\"\n [disabled]=\"rootNodeDisabled\"\n ></c8y-root-node-config>\n </div>\n </div>\n </fieldset>\n\n <fieldset\n class=\"row separator-bottom p-t-24 p-b-24\"\n *ngIf=\"'CockpitDashboardModule' | c8yPluginLoaded | async\"\n >\n <div class=\"col-xs-12 col-sm-3 col-md-2 text-left-xs text-right-sm\">\n <div class=\"d-inline-flex m-b-16\">\n <div\n class=\"h4 text-medium m-r-8\"\n translate\n >\n Home dashboard\n </div>\n <button\n class=\"btn-help btn-help--sm\"\n [attr.aria-label]=\"'Help' | translate\"\n popover=\"{{\n 'The homepage of this application. By default, it is a customizable dashboard displaying the most important alarms and shortcuts to frequently used features.'\n | translate\n }}\"\n placement=\"right\"\n triggers=\"focus\"\n container=\"body\"\n type=\"button\"\n ></button>\n </div>\n </div>\n <div class=\"col-xs-12 col-sm-9 col-md-8\">\n <c8y-home-dashboard-config [config]=\"config\"></c8y-home-dashboard-config>\n </div>\n </fieldset>\n\n <fieldset\n class=\"row p-t-24 p-b-24\"\n *ngIf=\"'htmlWidgetProviders' | c8yPluginLoaded | async\"\n >\n <div class=\"col-xs-12 col-sm-3 col-md-2 text-left-xs text-right-sm\">\n <div class=\"d-inline-flex m-b-16\">\n <div\n class=\"h4 text-medium m-r-8\"\n translate\n >\n HTML widget\n </div>\n </div>\n </div>\n <div class=\"col-xs-12 col-sm-9 col-md-8\">\n <c8y-li>\n <c8y-li-icon icon=\"code1\"></c8y-li-icon>\n <div class=\"d-flex a-i-center\">\n <div class=\"p-r-16\">\n <p> {{ 'Disable sanitization' | translate }}</p>\n <p>\n <small translate>\n By default, every unsecure HTML is removed from the HTML widget. You can disable\n this behavior in this application and allow unsecure HTML to be used.\n </small>\n </p>\n </div>\n <label class=\"c8y-switch c8y-switch--inline m-l-auto\">\n <input\n [attr.aria-label]=\"'Disable sanitization' | translate\"\n name=\"htmlWidgetDisableSanitization\"\n type=\"checkbox\"\n [(ngModel)]=\"config.htmlWidgetDisableSanitization\"\n />\n <span></span>\n </label>\n </div>\n </c8y-li>\n <c8y-li class=\"\">\n <c8y-li-icon icon=\"settings\"></c8y-li-icon>\n <div class=\"d-flex a-i-center\">\n <div class=\"p-r-16\">\n <p>{{ 'Enforce advanced mode' | translate }}</p>\n <p>\n <small translate>\n If set to true, the HTML widget configuration will always be opened in the advanced mode\n (web component mode).\n </small>\n </p>\n </div>\n <label class=\"c8y-switch c8y-switch--inline m-l-auto\">\n <input\n [attr.aria-label]=\"'Enforce advanced mode' | translate\"\n name=\"htmlWidgetDefaultToAdvancedMode\"\n type=\"checkbox\"\n [(ngModel)]=\"config.htmlWidgetDefaultToAdvancedMode\"\n />\n <span></span>\n </label>\n </div>\n </c8y-li>\n </div>\n </fieldset>\n </div>\n </div>\n <div class=\"card-footer separator\">\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Save' | translate }}\"\n type=\"submit\"\n [disabled]=\"!configForm.form.valid\"\n (click)=\"save()\"\n [actionName]=\"'cockpitConfigurationSaved'\"\n [actionData]=\"{ config: config }\"\n c8yProductExperience\n >\n {{ 'Save' | translate }}\n </button>\n </div>\n </div>\n </form>\n </div>\n</div>\n","import { CdkStep } from '@angular/cdk/stepper';\nimport { AppStateService, AlertService, C8yStepper, SetupComponent } from '@c8y/ngx-components';\nimport { CockpitConfig, DEFAULT_CONFIG } from '../cockpit-config.model';\nimport { ApplicationService } from '@c8y/client';\nimport { CockpitConfigService } from '../cockpit-config.service';\n\nexport abstract class CockpitSetupStep {\n config: CockpitConfig = DEFAULT_CONFIG;\n pending = false;\n\n constructor(\n public stepper: C8yStepper,\n protected step: CdkStep,\n protected setup: SetupComponent,\n protected appState: AppStateService,\n protected alert: AlertService,\n protected applicationService: ApplicationService,\n protected cockpitConfigService: CockpitConfigService\n ) {}\n\n async next() {\n this.pending = true;\n try {\n const newConfig = { ...this.setup.data$.value, ...this.config };\n await this.appState.updateCurrentApplicationConfig(newConfig);\n this.cockpitConfigService.updateApplication(newConfig);\n this.setup.stepCompleted(this.stepper.selectedIndex);\n this.setup.data$.next(newConfig);\n this.stepper.next();\n } catch (ex) {\n this.alert.addServerFailure(ex);\n } finally {\n this.pending = false;\n }\n }\n\n back() {\n this.stepper.previous();\n }\n}\n","import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { NgIf } from '@angular/common';\nimport { C8yTranslateDirective } from '@c8y/ngx-components';\n\n@Component({\n selector: 'c8y-cockpit-setup-stepper-buttons',\n templateUrl: './cockpit-setup-stepper-buttons.component.html',\n imports: [NgIf, C8yTranslateDirective]\n})\nexport class CockpitSetupStepperButtonsComponent {\n @Input() index;\n @Output() onNext = new EventEmitter<void>();\n @Output() onBack = new EventEmitter<void>();\n}\n","<div class=\"card-footer separator d-flex gap-8 j-c-center\">\n @if (index !== 0) {\n <button\n class=\"btn btn-default\"\n type=\"button\"\n (click)=\"onBack.emit()\"\n translate\n >\n Previous\n </button>\n }\n <button\n class=\"btn btn-primary\"\n type=\"submit\"\n data-cy=\"c8y-cockpit-setup-stepper-buttons--save-continue-button\"\n (click)=\"onNext.emit()\"\n translate\n >\n Continue\n </button>\n</div>\n","import { CdkStep } from '@angular/cdk/stepper';\nimport { Component } from '@angular/core';\nimport { Observable } from 'rxjs';\nimport { filter } from 'rxjs/operators';\nimport {\n AlertService,\n AppStateService,\n C8yStepper,\n SetupComponent,\n C8yTranslateDirective,\n C8yTranslatePipe\n} from '@c8y/ngx-components';\nimport { CockpitSetupStep } from './cockpit-setup-step';\nimport { ApplicationService, IApplication } from '@c8y/client';\nimport { CockpitConfigService } from '../cockpit-config.service';\nimport { FormsModule } from '@angular/forms';\nimport { IconSelectorWrapperComponent } from '@c8y/ngx-components/icon-selector';\nimport { MiscConfigComponent } from '../misc-config.component';\nimport { CockpitSetupStepperButtonsComponent } from './cockpit-setup-stepper-buttons.component';\nimport { AsyncPipe } from '@angular/common';\n\n@Component({\n selector: 'c8y-cockpit-setup-step1',\n templateUrl: './cockpit-setup-step1.component.html',\n host: { class: 'd-contents' },\n imports: [\n FormsModule,\n C8yTranslateDirective,\n IconSelectorWrapperComponent,\n MiscConfigComponent,\n CockpitSetupStepperButtonsComponent,\n C8yTranslatePipe,\n AsyncPipe\n ]\n})\nexport class CockpitSetupStep1Component extends CockpitSetupStep {\n app$: Observable<IApplication>;\n\n constructor(\n public stepper: C8yStepper,\n protected step: CdkStep,\n protected setup: SetupComponent,\n protected appState: AppStateService,\n protected alert: AlertService,\n protected appService: ApplicationService,\n protected cockpitConfigService: CockpitConfigService\n ) {\n super(stepper, step, setup, appState, alert, appService, cockpitConfigService);\n this.app$ = this.appState.currentApplication.pipe(filter(app => !!app));\n }\n\n iconSelectionChange(icon): void {\n this.config.icon = { class: icon };\n }\n}\n","<form\n class=\"d-contents\"\n name=\"form\"\n #stepForm=\"ngForm\"\n>\n <div class=\"container-fluid flex-no-shrink fit-w\">\n <div class=\"row separator-bottom\">\n <div class=\"col-md-8 col-md-offset-2 col-lg-6 col-lg-offset-3 p-t-24 p-l-16 p-r-16\">\n <h3\n data-cy=\"c8y-cockpit-setup-step1--step1-header-title\"\n class=\"text-medium l-h-base\"\n translate\n >\n Title, icon, and navigator collapse\n </h3>\n <p\n class=\"lead text-normal\"\n translate\n >\n Change the icon, the title, and set the initial navigator state.\n </p>\n </div>\n </div>\n </div>\n <div class=\"inner-scroll flex-grow\">\n <div class=\"container-fluid fit-w\">\n <div class=\"row\">\n <div class=\"col-md-8 col-md-offset-2 col-lg-6 col-lg-offset-3\">\n <div class=\"d-flex a-i-start gap-16 p-t-16\">\n <div class=\"form-group d-inline-block\">\n <label>{{ 'Icon' | translate }}</label>\n <c8y-icon-selecto