UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

486 lines 85.9 kB
import { Component, Input, ViewChild } from '@angular/core'; import { ActivatedRoute } from '@angular/router'; import { AlertService, ApplicationPluginStatus, DataGridComponent, GainsightService, gettext, PluginsService, PluginsExportScopes } from '@c8y/ngx-components'; import { EcosystemService, PRODUCT_EXPERIENCE_ECOSYSTEM } from '@c8y/ngx-components/ecosystem/shared'; import { pick } from 'lodash-es'; import { BsModalService } from 'ngx-bootstrap/modal'; import { BehaviorSubject, combineLatest, firstValueFrom, Subject } from 'rxjs'; import { map, shareReplay } from 'rxjs/operators'; import { InstallPluginComponent } from './install-plugin.component'; import { LabelCellRendererComponent } from './label-cell-renderer.component'; import { OrphanedStatusCellRendererComponent } from './orphaned-status-cell-renderer.component'; import { UpdatePluginOfAppComponent } from './update-plugin-of-app/update-plugin-of-app.component'; import * as i0 from "@angular/core"; import * as i1 from "@angular/router"; import * as i2 from "@c8y/ngx-components/ecosystem/shared"; import * as i3 from "ngx-bootstrap/modal"; import * as i4 from "@c8y/ngx-components"; import * as i5 from "@angular/common"; export class ApplicationPluginsComponent { constructor(activatedRoute, ecosystemService, bsModalService, pluginsService, alertService, gainsightService) { this.activatedRoute = activatedRoute; this.ecosystemService = ecosystemService; this.bsModalService = bsModalService; this.pluginsService = pluginsService; this.alertService = alertService; this.gainsightService = gainsightService; this.PRODUCT_EXPERIENCE = PRODUCT_EXPERIENCE_ECOSYSTEM; this.CURRENT_LOCATION = location.href; this.remotePlugins$ = new BehaviorSubject({}); this.allAvailablePlugins$ = new BehaviorSubject([]); this.selfPlugins$ = new BehaviorSubject([]); this.installedPlugins$ = combineLatest([ this.remotePlugins$.pipe(map(remotes => PluginsService.convertInstalledRemotesToIds(remotes))), this.allAvailablePlugins$ ]).pipe(map(([remotePlugins, allPlugins]) => this.getInstalledPlugins(allPlugins, remotePlugins)), shareReplay(1)); this.orphanedPlugins$ = this.installedPlugins$.pipe(map(plugins => plugins.filter(p => p.status === ApplicationPluginStatus.ORPHANED))); this.isStandard$ = combineLatest([this.installedPlugins$, this.selfPlugins$]).pipe(map(([installedPlugins, selfPlugins]) => { const manifestRemotes = this.app?.manifest?.remotes || {}; // ensure that every installed plugin is a self plugin or a plugin from the manifest const allInstalledPluginsAreSelf = installedPlugins.every(p => selfPlugins.some(selfPlugin => selfPlugin.id === p.id) || (Array.isArray(manifestRemotes[p.contextPath]) && manifestRemotes[p.contextPath].includes(p.module))); // ensure that every self plugin is installed const allSelfPluginsAreInstalled = selfPlugins.every(selfPlugin => installedPlugins.some(p => p.id === selfPlugin.id)); const configRemotes = this.app?.config?.remotes || {}; // ensure that every remote from the manifest is in the config // if no config exists we are also all good const everyRemoteFromManifestIsInConfig = !this.app?.config?.remotes || Object.keys(manifestRemotes).every(contextPath => Array.isArray(configRemotes[contextPath]) && Array.isArray(manifestRemotes[contextPath]) && manifestRemotes[contextPath].every(module => configRemotes[contextPath].includes(module))); return (allInstalledPluginsAreSelf && allSelfPluginsAreInstalled && everyRemoteFromManifestIsInConfig); })); this.title = gettext('Installed plugins'); this.loadMoreItemsLabel = gettext('Load more packages'); this.loadingItemsLabel = gettext('Loading packages…'); this.actionControls = this.getActionControls(); this.bulkActionControls = this.getBulkActionControls(); this.headerActionControls = []; this.noResultsMessage = gettext('No plugins to display.'); this.noDataMessage = gettext('No plugins installed.'); this.noResultsSubtitle = gettext('Refine your search terms or check your spelling.'); this.noDataSubtitle = gettext("This application doesn't have any plugin. Click below to install."); this.pagination = { pageSize: 10, currentPage: 1 }; this.displayOptions = { bordered: false, striped: true, filter: true, gridHeader: true, hover: true }; this.columns = [ { name: 'name', header: gettext('Plugin name'), path: 'name', filterable: true }, { name: 'Version', header: gettext('Version'), path: 'version', filterable: false }, { name: 'Tag', header: gettext('Tag`noun`'), path: 'installedViaTag', filterable: false, cellRendererComponent: LabelCellRendererComponent }, { name: 'description', header: gettext('Description'), path: 'description', filterable: false, cellCSSClassName: 'small' }, { name: 'contextPath', header: gettext('Source'), path: 'contextPath', filterable: false, cellRendererComponent: LabelCellRendererComponent }, { name: 'scope', header: gettext('Scope'), path: 'scope', filterable: false, visible: false, cellRendererComponent: LabelCellRendererComponent }, { name: 'status', header: gettext('Status'), path: 'status', filterable: false, cellRendererComponent: OrphanedStatusCellRendererComponent } ]; this.destroy$ = new Subject(); } ngOnInit() { this.addInstallButtonToHeaderActionControls(); this.loadData(); } async loadData() { this.isLoading = true; await this.getApplicationMO(); await this.getApplicationMFRemotes(this.app); await this.getAllApplicationsMFExports(this.app); await this.getAllSelfMFExports(this.app); this.isLoading = false; } async resetToDefault() { this.isLoading = true; await this.pluginsService.resetRemotes(this.app); await this.loadData(); this.alertService.success(gettext('The application was reset to its default plugins.')); } async installPlugins() { let currentPlugin = null; try { this.gainsightService.triggerEvent(PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.EVENTS.INSTALLED_PLUGINS, { component: PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.COMPONENTS.APPLICATION_PLUGINS, action: PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.ACTIONS.INSTALL_PLUGINS_INITIATED, url: this.CURRENT_LOCATION, targetApplicationName: this.app.name, targetApplicationContextPath: this.app.contextPath }); const pluginsToAdd = await this.bsModalService.show(InstallPluginComponent, { class: 'modal-md', ariaDescribedby: 'modal-body', ariaLabelledBy: 'modal-title', initialState: this.getInstallModalInitState(), ignoreBackdropClick: true }).content.result; const isArchived = await this.ecosystemService.verifyArchived(pluginsToAdd); if (!isArchived) { return; } const licensesVerifiedByUser = await this.ecosystemService.verifyLicenses(pluginsToAdd); if (!licensesVerifiedByUser) { return; } const verifyVersionCompatibility = await this.ecosystemService.verifyPluginVersionsCompatibility(pluginsToAdd, this.app); if (!verifyVersionCompatibility) { return; } this.isLoading = true; await this.handleRemotesInstallation(pluginsToAdd); this.alertService.success(gettext('Plugins installed.')); pluginsToAdd.forEach(plugin => { currentPlugin = plugin; this.triggerPluginEvent(plugin, PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.RESULTS.PLUGIN_INSTALLED); }); } catch (ex) { if (ex) { this.alertService.addServerFailure(ex); this.triggerPluginEvent(currentPlugin, PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.RESULTS.SERVER_FAILURE); } } finally { this.isLoading = false; } } async removePlugins(plugins) { let currentPlugin = null; try { this.isLoading = true; const installedPlugins = await firstValueFrom(this.installedPlugins$); const pluginsToRemove = installedPlugins.filter(p => plugins.includes(p.id)); const updatedRemotes = await this.pluginsService.removeRemotes(this.app, plugins.map(id => installedPlugins.find(p => p.id === id))); this.emitRemotes(updatedRemotes); this.isLoading = false; this.dataGrid.cancel(); this.alertService.success(gettext('Plugins removed.')); pluginsToRemove.forEach(plugin => { currentPlugin = plugin; this.triggerPluginEvent(plugin, PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.RESULTS.PLUGIN_REMOVED); }); } catch (ex) { if (ex) { this.alertService.addServerFailure(ex); this.triggerPluginEvent(currentPlugin, PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.RESULTS.SERVER_FAILURE); } } finally { this.isLoading = false; } } async cleanupOrphanedPlugins(plugins) { const pluginIds = plugins.map(p => p.id); await this.removePlugins(pluginIds); } getActionControls() { return [ { type: 'customUpdate', text: gettext('Update'), icon: 'caret-square-o-up', showIf: plugin => { return (plugin.status === ApplicationPluginStatus.OUTDATED || plugin.status === ApplicationPluginStatus.REVOKED); }, callback: plugin => this.updatePlugin(this.app, plugin) }, { type: 'customDowngrade', text: gettext('Downgrade'), icon: 'caret-square-o-down', showIf: (plugin) => { if (plugin.scope === PluginsExportScopes.SELF || plugin.scope === PluginsExportScopes.SELF_OPTIONAL) { return false; } return plugin.status === ApplicationPluginStatus.LATEST; }, callback: plugin => this.updatePlugin(this.app, plugin, true) } ]; } getBulkActionControls() { return [ { type: 'customDelete', text: gettext('Remove'), icon: 'trash', callback: plugins => this.removePlugins(plugins) } ]; } async updatePlugin(app, plugin, downgrade = false) { try { await this.bsModalService.show(UpdatePluginOfAppComponent, { class: 'modal-sm', ariaDescribedby: 'modal-body', ariaLabelledBy: 'modal-title', initialState: { app, plugin, downgrade }, ignoreBackdropClick: true }).content.result; this.refresh(); } catch (er) { return; } } refresh() { this.loadData(); } ngOnDestroy() { this.destroy$.next(); this.destroy$.complete(); } addInstallButtonToHeaderActionControls() { if (this.appId) { this.headerActionControls = [ { text: gettext('Install plugins'), callback: () => { this.installPlugins(); }, icon: 'plus-circle', type: 'custom' } ]; } } async handleRemotesInstallation(plugins) { const updatedRemotes = await this.pluginsService.addRemotes(this.app, plugins); return this.emitRemotes(updatedRemotes); } emitRemotes(pluginsConfig) { const { remotes, excludedRemotes } = pluginsConfig; // needed for first time adding/removing a plugin if (!this.app.config) { this.app.config = {}; } this.app.config.remotes = remotes; this.app.config.excludedRemotes = excludedRemotes; const actualRemotes = this.pluginsService.getMFRemotes(this.app); this.remotePlugins$.next(actualRemotes); return { ...this.remotePlugins$.value }; } async getApplicationMO() { let id = this.appId; if (!id) { const { id: routeId } = this.activatedRoute.snapshot.parent.data.contextData; id = routeId; } try { this.app = await this.ecosystemService.getApplication(id); } catch (er) { if (er) { this.alertService.addServerFailure(er); } } return this.app; } async getApplicationMFExports(app) { const exports = this.pluginsService.getMFExports(app, [], true); return exports; } async getApplicationMFRemotes(app) { const appConfigRemotes = this.pluginsService.getMFRemotes(app); this.remotePlugins$.next(appConfigRemotes || {}); } async getAllApplicationsMFExports(app) { const exportedByCurrentApp = await this.getApplicationMFExports(app); const allAppsMFExports = await this.pluginsService.getAllMFExports(true); this.allAvailablePlugins$.next([...allAppsMFExports, ...exportedByCurrentApp]); } async getAllSelfMFExports(app) { const exportedByCurrentApp = await this.getApplicationMFExports(app); this.selfPlugins$.next(exportedByCurrentApp.filter(({ scope }) => scope === 'self')); } getInstallModalInitState() { return { plugins$: combineLatest([this.allAvailablePlugins$, this.installedPlugins$]).pipe(map(([allPlugins, installedPlugins]) => { // to not mutate the original array and objects contained in it const allPluginsAsNewObjects = allPlugins.map(p => ({ ...p })); for (const plugin of installedPlugins) { let installedPlugin = allPluginsAsNewObjects.find(p => p.id === plugin.id); if (!installedPlugin && plugin.installedViaTag) { installedPlugin = allPluginsAsNewObjects.find(p => p.contextPath === plugin.contextPath && p.module === plugin.module && p.tags?.includes(plugin.installedViaTag)); } if (installedPlugin) { installedPlugin.installed = true; continue; } } return allPluginsAsNewObjects.map(p => ({ ...p, installed: !!p.installed })); }), shareReplay(1)) }; } getOrphanedPlugins(orphanedPluginIds, allPlugins) { const orphanedPlugins = orphanedPluginIds.map(p => this.extractDetails(p)); const orphanedPluginsUpdated = orphanedPlugins.map(p => { const pluginWithMatchingTag = allPlugins.find(tmp => tmp.contextPath === p.contextPath && tmp.module === p.module && tmp.tags?.includes(p.version || 'latest')); if (pluginWithMatchingTag) { return { ...pluginWithMatchingTag, id: p.id, status: ApplicationPluginStatus.AUTO, installedViaTag: p.version || 'latest' }; } const pluginInDifferentVersion = allPlugins.find(tmp => tmp.contextPath === p.contextPath && tmp.module === p.module); if (pluginInDifferentVersion) { return { ...pluginInDifferentVersion, version: p.version, id: p.id, status: ApplicationPluginStatus.OUTDATED }; } return p; }); return orphanedPluginsUpdated; } splitOrphanedPluginsIntoOrphanedAndRevokedPlugins(allPlugins, orphanedPlugins) { const revokedPlugins = new Array(); const actuallyOrphanedPlugins = new Array(); for (const plugin of orphanedPlugins) { const foundFamiliarPlugin = allPlugins.find(plugin1 => plugin.contextPath === plugin1.contextPath && plugin.module === plugin1.module); if (foundFamiliarPlugin) { revokedPlugins.push(Object.assign({}, foundFamiliarPlugin, plugin, { status: ApplicationPluginStatus.REVOKED })); } else { actuallyOrphanedPlugins.push(plugin); } } return { revokedPlugins, actuallyOrphanedPlugins }; } getInstalledPlugins(allPlugins, remotePlugins) { const availablePlugins = allPlugins .filter(plugin => remotePlugins.includes(plugin.id)) .map(plugin => Object.assign(plugin, { status: plugin.tags?.includes('latest') ? ApplicationPluginStatus.LATEST : ApplicationPluginStatus.OUTDATED })); const orphanedPluginIds = remotePlugins.filter(r => !availablePlugins.find(plugin => plugin.id === r)); const orphanedPlugins = this.getOrphanedPlugins(orphanedPluginIds, allPlugins); const notActuallyOrphanedPlugins = orphanedPlugins.filter(p => p.status === ApplicationPluginStatus.AUTO); const orphanedOrRevokedPlugins = orphanedPlugins.filter(p => p.status !== ApplicationPluginStatus.AUTO); const { actuallyOrphanedPlugins, revokedPlugins } = this.splitOrphanedPluginsIntoOrphanedAndRevokedPlugins(allPlugins, orphanedOrRevokedPlugins); return [ ...availablePlugins, ...notActuallyOrphanedPlugins, ...revokedPlugins, ...actuallyOrphanedPlugins ]; } extractDetails(pluginId) { const contextPath = this.getStringMatchingRegex(pluginId, /^[^@]*(@|\/)/); const version = this.getStringMatchingRegex(pluginId, /@.*\//); const module = this.getStringMatchingRegex(pluginId, /\/.*$/); const unavailable = gettext('unavailable`plugin`'); return { id: pluginId, idLatest: `${contextPath}/${module}`, path: '', module, name: module, status: ApplicationPluginStatus.ORPHANED, contextPath: contextPath, description: unavailable, version: version }; } getStringMatchingRegex(str, regex) { const matches = str.match(regex); const value = matches ? matches[0] : ''; return value.replace(/(@|\/)/g, ''); } triggerPluginEvent(plugin, result) { const pluginInfo = pick(plugin, [ 'name', 'contextPath', 'module', 'version', 'type', 'id' ]); this.gainsightService.triggerEvent(PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.EVENTS.INSTALLED_PLUGINS, { component: PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.COMPONENTS.APPLICATION_PLUGINS, result, url: this.CURRENT_LOCATION, ...pluginInfo, targetApplicationName: this.app.name, targetApplicationContextPath: this.app.contextPath }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ApplicationPluginsComponent, deps: [{ token: i1.ActivatedRoute }, { token: i2.EcosystemService }, { token: i3.BsModalService }, { token: i4.PluginsService }, { token: i4.AlertService }, { token: i4.GainsightService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "18.2.13", type: ApplicationPluginsComponent, selector: "c8y-app-plugins", inputs: { appId: "appId" }, viewQueries: [{ propertyName: "dataGrid", first: true, predicate: DataGridComponent, descendants: true }], ngImport: i0, template: "<c8y-title>{{ app | humanizeAppName | async }}</c8y-title>\n\n<c8y-breadcrumb>\n <c8y-breadcrumb-item\n [icon]=\"'c8y-atom'\"\n [label]=\"'Ecosystem' | translate\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n [icon]=\"'c8y-modules'\"\n [label]=\"'Applications' | translate\"\n [path]=\"'ecosystem/application/applications'\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item [label]=\"app | humanizeAppName | async\"></c8y-breadcrumb-item>\n <c8y-breadcrumb-item [label]=\"'Plugins' | translate\"></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<c8y-action-bar-item\n [placement]=\"'right'\"\n *ngIf=\"!(isStandard$ | async)\"\n>\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Reset to default plugins' | translate }}\"\n [ngClass]=\"{ 'btn-pending': isLoading }\"\n (click)=\"resetToDefault()\"\n >\n <i c8yIcon=\"undo\"></i>\n {{ 'Reset to default' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Install plugins' | translate }}\"\n (click)=\"installPlugins()\"\n [ngClass]=\"{ 'btn-pending': isLoading }\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Install plugins' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<ng-container *ngIf=\"orphanedPlugins$ | async as orphanedPlugins\">\n <c8y-action-bar-item\n *ngIf=\"orphanedPlugins?.length\"\n [placement]=\"'right'\"\n >\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Clean up orphaned plugins' | translate }}\"\n (click)=\"cleanupOrphanedPlugins(orphanedPlugins)\"\n [ngClass]=\"{ 'btn-pending': isLoading }\"\n >\n <i c8yIcon=\"erase\"></i>\n {{ 'Clean up orphaned plugins' | translate }}\n </button>\n </c8y-action-bar-item>\n</ng-container>\n\n<div class=\"content-fullpage d-flex d-col border-top\">\n <c8y-data-grid\n class=\"d-contents\"\n [title]=\"title\"\n [loadMoreItemsLabel]=\"loadMoreItemsLabel\"\n [loadingItemsLabel]=\"loadingItemsLabel\"\n [displayOptions]=\"displayOptions\"\n [columns]=\"columns\"\n [rows]=\"installedPlugins$ | async\"\n [pagination]=\"pagination\"\n [selectable]=\"true\"\n [actionControls]=\"actionControls\"\n [bulkActionControls]=\"bulkActionControls\"\n [headerActionControls]=\"headerActionControls\"\n (onReload)=\"refresh()\"\n >\n <c8y-ui-empty-state\n [icon]=\"stats?.size > 0 ? 'search' : 'plugin'\"\n [title]=\"stats?.size > 0 ? (noResultsMessage | translate) : (noDataMessage | translate)\"\n [subtitle]=\"stats?.size > 0 ? (noResultsSubtitle | translate) : (noDataSubtitle | translate)\"\n *emptyStateContext=\"let stats\"\n [horizontal]=\"stats?.size > 0\"\n >\n <p *ngIf=\"stats?.size === 0\">\n <button\n class=\"btn btn-primary btn-sm\"\n title=\"{{ 'Install plugins' | translate }}\"\n (click)=\"installPlugins()\"\n [ngClass]=\"{ 'btn-pending': isLoading }\"\n translate\n >\n Install plugins\n </button>\n </p>\n </c8y-ui-empty-state>\n </c8y-data-grid>\n</div>\n", dependencies: [{ kind: "component", type: i4.ActionBarItemComponent, selector: "c8y-action-bar-item", inputs: ["placement", "priority", "itemClass", "injector", "groupId", "inGroupPriority"] }, { kind: "component", type: i4.BreadcrumbComponent, selector: "c8y-breadcrumb" }, { kind: "component", type: i4.BreadcrumbItemComponent, selector: "c8y-breadcrumb-item", inputs: ["icon", "translate", "label", "path", "injector"] }, { kind: "component", type: i4.EmptyStateComponent, selector: "c8y-ui-empty-state", inputs: ["icon", "title", "subtitle", "horizontal"] }, { kind: "directive", type: i4.EmptyStateContextDirective, selector: "[emptyStateContext]" }, { kind: "directive", type: i4.IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: i4.C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "directive", type: i5.NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "directive", type: i5.NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: i4.DataGridComponent, selector: "c8y-data-grid", inputs: ["title", "loadMoreItemsLabel", "loadingItemsLabel", "showSearch", "refresh", "columns", "rows", "pagination", "infiniteScroll", "serverSideDataCallback", "selectable", "singleSelection", "selectionPrimaryKey", "displayOptions", "actionControls", "bulkActionControls", "headerActionControls", "searchText", "configureColumnsEnabled", "showCounterWarning", "activeClassName", "expandableRows"], outputs: ["rowMouseOver", "rowMouseLeave", "rowClick", "onConfigChange", "onBeforeFilter", "onBeforeSearch", "onFilter", "itemsSelect", "onReload", "onAddCustomColumn", "onRemoveCustomColumn", "onColumnFilterReset", "onSort", "onPageSizeChange", "onColumnReordered", "onColumnVisibilityChange"] }, { kind: "component", type: i4.TitleComponent, selector: "c8y-title", inputs: ["pageTitleUpdate"] }, { kind: "pipe", type: i4.C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: i5.AsyncPipe, name: "async" }, { kind: "pipe", type: i4.HumanizeAppNamePipe, name: "humanizeAppName" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: ApplicationPluginsComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-app-plugins', template: "<c8y-title>{{ app | humanizeAppName | async }}</c8y-title>\n\n<c8y-breadcrumb>\n <c8y-breadcrumb-item\n [icon]=\"'c8y-atom'\"\n [label]=\"'Ecosystem' | translate\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item\n [icon]=\"'c8y-modules'\"\n [label]=\"'Applications' | translate\"\n [path]=\"'ecosystem/application/applications'\"\n ></c8y-breadcrumb-item>\n <c8y-breadcrumb-item [label]=\"app | humanizeAppName | async\"></c8y-breadcrumb-item>\n <c8y-breadcrumb-item [label]=\"'Plugins' | translate\"></c8y-breadcrumb-item>\n</c8y-breadcrumb>\n\n<c8y-action-bar-item\n [placement]=\"'right'\"\n *ngIf=\"!(isStandard$ | async)\"\n>\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Reset to default plugins' | translate }}\"\n [ngClass]=\"{ 'btn-pending': isLoading }\"\n (click)=\"resetToDefault()\"\n >\n <i c8yIcon=\"undo\"></i>\n {{ 'Reset to default' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<c8y-action-bar-item [placement]=\"'right'\">\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Install plugins' | translate }}\"\n (click)=\"installPlugins()\"\n [ngClass]=\"{ 'btn-pending': isLoading }\"\n >\n <i c8yIcon=\"plus-circle\"></i>\n {{ 'Install plugins' | translate }}\n </button>\n</c8y-action-bar-item>\n\n<ng-container *ngIf=\"orphanedPlugins$ | async as orphanedPlugins\">\n <c8y-action-bar-item\n *ngIf=\"orphanedPlugins?.length\"\n [placement]=\"'right'\"\n >\n <button\n class=\"btn btn-link\"\n title=\"{{ 'Clean up orphaned plugins' | translate }}\"\n (click)=\"cleanupOrphanedPlugins(orphanedPlugins)\"\n [ngClass]=\"{ 'btn-pending': isLoading }\"\n >\n <i c8yIcon=\"erase\"></i>\n {{ 'Clean up orphaned plugins' | translate }}\n </button>\n </c8y-action-bar-item>\n</ng-container>\n\n<div class=\"content-fullpage d-flex d-col border-top\">\n <c8y-data-grid\n class=\"d-contents\"\n [title]=\"title\"\n [loadMoreItemsLabel]=\"loadMoreItemsLabel\"\n [loadingItemsLabel]=\"loadingItemsLabel\"\n [displayOptions]=\"displayOptions\"\n [columns]=\"columns\"\n [rows]=\"installedPlugins$ | async\"\n [pagination]=\"pagination\"\n [selectable]=\"true\"\n [actionControls]=\"actionControls\"\n [bulkActionControls]=\"bulkActionControls\"\n [headerActionControls]=\"headerActionControls\"\n (onReload)=\"refresh()\"\n >\n <c8y-ui-empty-state\n [icon]=\"stats?.size > 0 ? 'search' : 'plugin'\"\n [title]=\"stats?.size > 0 ? (noResultsMessage | translate) : (noDataMessage | translate)\"\n [subtitle]=\"stats?.size > 0 ? (noResultsSubtitle | translate) : (noDataSubtitle | translate)\"\n *emptyStateContext=\"let stats\"\n [horizontal]=\"stats?.size > 0\"\n >\n <p *ngIf=\"stats?.size === 0\">\n <button\n class=\"btn btn-primary btn-sm\"\n title=\"{{ 'Install plugins' | translate }}\"\n (click)=\"installPlugins()\"\n [ngClass]=\"{ 'btn-pending': isLoading }\"\n translate\n >\n Install plugins\n </button>\n </p>\n </c8y-ui-empty-state>\n </c8y-data-grid>\n</div>\n" }] }], ctorParameters: () => [{ type: i1.ActivatedRoute }, { type: i2.EcosystemService }, { type: i3.BsModalService }, { type: i4.PluginsService }, { type: i4.AlertService }, { type: i4.GainsightService }], propDecorators: { appId: [{ type: Input }], dataGrid: [{ type: ViewChild, args: [DataGridComponent, { static: false }] }] } }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiYXBwbGljYXRpb24tcGx1Z2lucy5jb21wb25lbnQuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9lY29zeXN0ZW0vYXBwbGljYXRpb24tcGx1Z2lucy9hcHBsaWNhdGlvbi1wbHVnaW5zLmNvbXBvbmVudC50cyIsIi4uLy4uLy4uLy4uL2Vjb3N5c3RlbS9hcHBsaWNhdGlvbi1wbHVnaW5zL2FwcGxpY2F0aW9uLXBsdWdpbnMuY29tcG9uZW50Lmh0bWwiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUEsT0FBTyxFQUFFLFNBQVMsRUFBRSxLQUFLLEVBQXFCLFNBQVMsRUFBRSxNQUFNLGVBQWUsQ0FBQztBQUMvRSxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0saUJBQWlCLENBQUM7QUFFakQsT0FBTyxFQUVMLFlBQVksRUFFWix1QkFBdUIsRUFHdkIsaUJBQWlCLEVBRWpCLGdCQUFnQixFQUNoQixPQUFPLEVBSVAsY0FBYyxFQUNkLG1CQUFtQixFQUNwQixNQUFNLHFCQUFxQixDQUFDO0FBQzdCLE9BQU8sRUFDTCxnQkFBZ0IsRUFDaEIsNEJBQTRCLEVBQzdCLE1BQU0sc0NBQXNDLENBQUM7QUFDOUMsT0FBTyxFQUFFLElBQUksRUFBRSxNQUFNLFdBQVcsQ0FBQztBQUNqQyxPQUFPLEVBQUUsY0FBYyxFQUFFLE1BQU0scUJBQXFCLENBQUM7QUFDckQsT0FBTyxFQUFFLGVBQWUsRUFBRSxhQUFhLEVBQUUsY0FBYyxFQUFjLE9BQU8sRUFBRSxNQUFNLE1BQU0sQ0FBQztBQUMzRixPQUFPLEVBQUUsR0FBRyxFQUFFLFdBQVcsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBQ2xELE9BQU8sRUFBRSxzQkFBc0IsRUFBRSxNQUFNLDRCQUE0QixDQUFDO0FBQ3BFLE9BQU8sRUFBRSwwQkFBMEIsRUFBRSxNQUFNLGlDQUFpQyxDQUFDO0FBQzdFLE9BQU8sRUFBRSxtQ0FBbUMsRUFBRSxNQUFNLDJDQUEyQyxDQUFDO0FBQ2hHLE9BQU8sRUFBRSww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