@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
1 lines • 382 kB
Source Map (JSON)
{"version":3,"file":"c8y-ngx-components-ecosystem.mjs","sources":["../../ecosystem/activity-log/activity-log.component.ts","../../ecosystem/activity-log/activity-log.component.html","../../ecosystem/application-properties/subscription-modal/subscription-modal.component.ts","../../ecosystem/application-properties/subscription-modal/subscription-modal.component.html","../../ecosystem/application-plugins/appState.pipe.ts","../../ecosystem/application-plugins/apps-to-update-remotes-select.component.ts","../../ecosystem/application-plugins/apps-to-update-remotes-select.component.html","../../ecosystem/application-plugins/plugin-list-item.component.ts","../../ecosystem/application-plugins/plugin-list-item.component.html","../../ecosystem/application-plugins/plugin-list.component.ts","../../ecosystem/application-plugins/plugin-list.component.html","../../ecosystem/application-properties/update-application-modal/update-application-modal.component.ts","../../ecosystem/application-properties/update-application-modal/update-application-modal.component.html","../../ecosystem/application-properties/application-properties.component.ts","../../ecosystem/application-properties/application-properties.component.html","../../ecosystem/application-properties/application-properties.guard.ts","../../ecosystem/applications/add-external-applicaiton/add-external-application.component.ts","../../ecosystem/applications/add-external-applicaiton/add-external-application.component.html","../../ecosystem/applications/add-web-application/add-web-application.component.ts","../../ecosystem/applications/application-list/application-list.component.ts","../../ecosystem/applications/application-list/application-list.component.html","../../ecosystem/applications/install-from-package/install-from-package.component.ts","../../ecosystem/applications/install-from-package/install-from-package.component.html","../../ecosystem/ecosystem-navigation.factory.ts","../../ecosystem/ecosystem-tabs.factory.ts","../../ecosystem/application-plugins/only-latest-filter/only-latest-filter.component.ts","../../ecosystem/application-plugins/only-latest-filter/only-latest-filter.component.html","../../ecosystem/application-plugins/install-plugin.component.ts","../../ecosystem/application-plugins/install-plugin.component.html","../../ecosystem/application-plugins/label-cell-renderer.component.ts","../../ecosystem/application-plugins/label-cell-renderer.component.html","../../ecosystem/application-plugins/orphaned-status-cell-renderer.component.ts","../../ecosystem/application-plugins/orphaned-status-cell-renderer.component.html","../../ecosystem/application-plugins/update-plugin-of-app/update-plugin-of-app.component.ts","../../ecosystem/application-plugins/update-plugin-of-app/update-plugin-of-app.component.html","../../ecosystem/application-plugins/application-plugins.component.ts","../../ecosystem/application-plugins/application-plugins.component.html","../../ecosystem/application-plugins/application-plugins.guard.ts","../../ecosystem/application-plugins/application-plugins.module.ts","../../ecosystem/features/feature-list.component.ts","../../ecosystem/features/feature-list.component.html","../../ecosystem/microservices/add-microservice.component.ts","../../ecosystem/microservices/microservice-list.component.ts","../../ecosystem/microservices/microservice-list.component.html","../../ecosystem/packages/add-package.component.ts","../../ecosystem/packages/deploy-application/deploy-application.component.ts","../../ecosystem/packages/deploy-application/deploy-application.component.html","../../ecosystem/packages/package-details/package-details.component.ts","../../ecosystem/packages/package-details/package-details.component.html","../../ecosystem/packages/package-list/packages-list.component.ts","../../ecosystem/packages/package-list/packages-list.component.html","../../ecosystem/packages/package-versions.guard.ts","../../ecosystem/packages/package-versions/package-contents/contents-apps/contents-apps.component.ts","../../ecosystem/packages/package-versions/package-contents/contents-apps/contents-apps.component.html","../../ecosystem/packages/package-versions/package-contents/contents-plugins/contents-plugins.component.ts","../../ecosystem/packages/package-versions/package-contents/contents-plugins/contents-plugins.component.html","../../ecosystem/packages/package-versions/package-contents/packages-contents.component.ts","../../ecosystem/packages/package-versions/package-contents/packages-contents.component.html","../../ecosystem/packages/package-versions/package-versions-list/package-versions-list.component.ts","../../ecosystem/packages/package-versions/package-versions-list/package-versions-list.component.html","../../ecosystem/packages/package-versions/packages-versions.component.ts","../../ecosystem/packages/package-versions/packages-versions.component.html","../../ecosystem/packages/package.guard.ts","../../ecosystem/ecosystem.module.ts","../../ecosystem/c8y-ngx-components-ecosystem.ts"],"sourcesContent":["import { Component, Input, OnInit } from '@angular/core';\nimport { IApplication, IApplicationBinary } from '@c8y/client';\nimport { AlertService } from '@c8y/ngx-components';\nimport { BehaviorSubject } from 'rxjs';\nimport { EcosystemService } from '@c8y/ngx-components/ecosystem/shared';\n\n@Component({\n selector: 'c8y-activity-log',\n templateUrl: './activity-log.component.html'\n})\nexport class ActivityLogComponent implements OnInit {\n @Input() application: IApplication;\n @Input() hasAdminPermissions = false;\n archives: IApplicationBinary[] = [];\n toActivateVersionId: string;\n last: IApplicationBinary;\n isLoading: boolean;\n canReactivate = false;\n\n constructor(private ecosystemService: EcosystemService, private alertService: AlertService) {}\n\n get uploadProgress(): BehaviorSubject<number> {\n return this.ecosystemService.progress;\n }\n\n async ngOnInit() {\n this.canReactivate = this.showReactivate();\n this.refresh();\n }\n\n isActive(archive: IApplicationBinary): boolean {\n return this.application.activeVersionId === archive.id;\n }\n\n toActivate(archive: IApplicationBinary): boolean {\n return this.toActivateVersionId === archive.id;\n }\n\n checkIfLast(archive: IApplicationBinary): boolean {\n return archive.id === this.last.id;\n }\n\n showReactivate(): boolean {\n return this.ecosystemService.isApplication(this.application);\n }\n\n async setActive(archive): Promise<void> {\n const id = archive.id || archive;\n this.toActivateVersionId = id;\n this.isLoading = true;\n try {\n this.application = (\n await this.ecosystemService.setAppActiveVersion(this.application, id)\n ).data;\n } catch (ex) {\n this.alertService.addServerFailure(ex);\n }\n this.isLoading = false;\n this.refresh();\n }\n\n async deleteArchive(archive: IApplicationBinary): Promise<void> {\n await this.ecosystemService.deleteArchive(archive, this.application);\n this.refresh();\n }\n\n async downloadArchive(archive: IApplicationBinary): Promise<void> {\n await this.ecosystemService.downloadArchive(this.application, archive);\n }\n\n async reactivateArchive(): Promise<void> {\n await this.ecosystemService.reactivateArchive(this.application);\n }\n\n async onRefresh() {\n await this.refresh();\n }\n\n private async refresh(): Promise<void> {\n this.isLoading = true;\n this.archives = await this.ecosystemService.listArchives(this.application.id);\n if (this.application.manifest?.package === 'blueprint') {\n // filter out entries without description because using them as active may break application's\n // manifest (changing isPackage property of deployed app to 'true')\n this.archives = this.archives.filter((archive: IApplicationBinary) => !!archive.description);\n }\n this.archives.sort((a, b) => {\n return (new Date(b.created) as any) - (new Date(a.created) as any);\n });\n this.last = this.archives[this.archives.length - 1];\n this.isLoading = false;\n }\n}\n","<div class=\"inner-scroll bg-level-1 flex-grow inner-scroll--md overflow-visible-sm overflow-visible-xs\">\n <div class=\"card-block overflow-visible\">\n <c8y-list-group>\n <c8y-li-timeline *ngFor=\"let archive of archives\" [ngClass]=\"{ active: isActive(archive) }\">\n {{ archive.created | date: 'd MMM YYYY' }}\n {{ archive.created | date: 'shortTime' }}\n <c8y-li>\n <c8y-li-icon\n [icon]=\"checkIfLast(archive) ? 'flag-checkered' : 'file-zip-o'\"\n ></c8y-li-icon>\n <c8y-li-body>\n <div class=\"d-flex a-i-start\">\n <div style=\"min-width: 0; flex: 1\">\n <span class=\"text-truncate-wrap\" title=\" {{ archive.description || archive.name }}\">\n {{ archive.description || archive.name }}\n </span>\n <small *ngIf=\"archive.description\" class=\"text-muted\">{{\n archive.description\n }}</small>\n </div>\n <i\n *ngIf=\"isLoading && toActivate(archive)\"\n [c8yIcon]=\"'circle-o-notch'\"\n class=\"icon-spin\"\n title=\"{{ 'Activating' | translate }}\"\n ></i>\n\n <span *ngIf=\"isActive(archive)\" class=\"label label-primary m-l-auto m-t-4\">{{\n 'Active' | translate\n }}</span>\n </div>\n </c8y-li-body>\n <c8y-li-action\n (click)=\"setActive(archive)\"\n *ngIf=\"hasAdminPermissions && !isLoading && !isActive(archive)\"\n icon=\"check-square-o\"\n >\n {{ 'Set as active`archive`' | translate }}\n </c8y-li-action>\n <c8y-li-action (click)=\"downloadArchive(archive)\" icon=\"download\">\n {{ 'Download`archive`' | translate }}\n </c8y-li-action>\n <c8y-li-action\n (click)=\"deleteArchive(archive)\"\n *ngIf=\"\n hasAdminPermissions &&\n archives.length > 1 &&\n !checkIfLast(archive) &&\n !isActive(archive)\n \"\n icon=\"delete\"\n >\n {{ 'Delete`archive`' | translate }}\n </c8y-li-action>\n <c8y-li-action\n (click)=\"reactivateArchive()\"\n *ngIf=\"hasAdminPermissions && canReactivate && isActive(archive)\"\n icon=\"undo\"\n >\n {{ 'Reactivate archive' | translate }}\n </c8y-li-action>\n </c8y-li>\n </c8y-li-timeline>\n </c8y-list-group>\n </div>\n</div>\n<div class=\"card-footer\" *ngIf=\"!isLoading && hasAdminPermissions\">\n <c8y-form-group class=\"m-auto\">\n <c8y-upload-archive [(application)]=\"application\" (refresh)=\"onRefresh()\"></c8y-upload-archive>\n </c8y-form-group>\n</div>\n","import { Component, OnInit } from '@angular/core';\nimport { ApplicationService, IApplication, IApplicationManagedObject } from '@c8y/client';\nimport {\n AlertService,\n ContextRouteService,\n gettext,\n ModalService,\n Status,\n TabsService\n} from '@c8y/ngx-components';\nimport { isEmpty } from 'lodash';\nimport { BsModalRef } from 'ngx-bootstrap/modal';\nimport { EcosystemService } from '@c8y/ngx-components/ecosystem/shared';\n\n@Component({\n selector: 'c8y-subscription-modal',\n templateUrl: './subscription-modal.component.html'\n})\nexport class SubscriptionModalComponent implements OnInit {\n readonly RETRY_TIMEOUT = 3000;\n application: IApplication;\n message: string;\n isLoading = false;\n result: Promise<void> = new Promise(resolve => {\n this._resolve = resolve;\n });\n isSubscribed: boolean;\n retryCounter = 0;\n\n private readonly TABS: string[] = ['Logs', 'Status'];\n private _resolve: (value: void | PromiseLike<void>) => void;\n\n constructor(\n private bsModalRef: BsModalRef,\n private ecosystemService: EcosystemService,\n private tabsService: TabsService,\n private modal: ModalService,\n private applicationService: ApplicationService,\n private alertService: AlertService,\n private contextRouteService: ContextRouteService\n ) {}\n\n ngOnInit() {\n if (this.isSubscribed) {\n this.unsubscribe();\n } else {\n this.subscribe();\n }\n }\n\n async subscribe(): Promise<void> {\n this.retryCounter = 0;\n this.isLoading = true;\n this.message = gettext('Subscribing…');\n await this.ecosystemService.subscribeApp(this.application);\n this.getStatusDetails('subscribe');\n }\n\n async unsubscribe(): Promise<void> {\n this.retryCounter = 0;\n this.isLoading = true;\n this.message = gettext('Unsubscribing…');\n await this.ecosystemService.unsubscribeApp(this.application);\n this.getStatusDetails('unsubscribe');\n }\n\n private async getStatusDetails(action: 'subscribe' | 'unsubscribe') {\n this.contextRouteService.refreshContext();\n const actionSuccessful =\n action === 'subscribe' ? await this.onSubscribe() : this.onUnsubscribe();\n if (actionSuccessful) {\n return this.hideSubscriptionModal();\n }\n if (this.retryCounter === 4) {\n this.showWarningModal(action);\n return this.hideSubscriptionModal();\n }\n this.retryCounter += 1;\n setTimeout(async () => {\n this.getStatusDetails(action);\n }, this.RETRY_TIMEOUT);\n }\n\n private async onSubscribe(): Promise<boolean> {\n try {\n if (!this.application.activeVersionId) {\n return true;\n }\n const res: IApplicationManagedObject = (\n await this.applicationService.getStatusDetails(this.application)\n ).data[0];\n return this.shouldShowMSSpecificTabs(res);\n } catch (er) {\n this.alertService.addServerFailure(er);\n }\n }\n\n // Checks if the UI should show tabs with logs and status\n private shouldShowMSSpecificTabs(mo: IApplicationManagedObject): boolean {\n return !isEmpty(mo.c8y_Status?.instances) && !!mo.c8y_SupportedLogs;\n }\n\n private onUnsubscribe(): boolean {\n return !this.tabsService.areAvailable(this.TABS);\n }\n\n private hideSubscriptionModal(): void {\n this._resolve();\n this.bsModalRef.hide();\n this.isLoading = false;\n }\n\n private showWarningModal(action: string): void {\n const title = gettext('Warning');\n const body =\n action === 'subscribe'\n ? gettext('Something went wrong, please refresh the page or resubscribe the application.')\n : gettext(\n 'Something went wrong, please refresh the page or retry to unsubscribe from the application.'\n );\n this.modal.acknowledge(title, body, Status.WARNING, gettext('Close'));\n }\n}\n","<div class=\"viewport-modal\">\n <div class=\"modal-header dialog-header\">\n <i c8yIcon=\"c8y-atom\"></i>\n <h4 id=\"modal-title\">{{ message | translate }}</h4>\n </div>\n <div class=\"modal-body\" id=\"modal-body\" *ngIf=\"isLoading\">\n <div class=\"p-16 text-center\">\n <c8y-loading></c8y-loading>\n </div>\n </div>\n</div>\n","import { Pipe, PipeTransform } from '@angular/core';\nimport { IApplication } from '@c8y/client';\nimport { ApplicationState, EcosystemService } from '@c8y/ngx-components/ecosystem/shared';\n@Pipe({\n name: 'appState',\n pure: true\n})\nexport class AppStatePipe implements PipeTransform {\n constructor(private ecosystemService: EcosystemService) {}\n\n transform(\n app: IApplication,\n arg: keyof ApplicationState\n ): ApplicationState[keyof ApplicationState] {\n const appState = this.ecosystemService.getAppState(app);\n return appState[arg];\n }\n}\n","import { Component, Input, OnDestroy, OnInit } from '@angular/core';\nimport { WizardConfig, WizardModalService, gettext } from '@c8y/ngx-components';\nimport { EcosystemService } from '@c8y/ngx-components/ecosystem/shared';\nimport { ApplicationType } from '@c8y/client';\nimport { ModalOptions } from 'ngx-bootstrap/modal';\nimport { BsModalRef } from 'ngx-bootstrap/modal';\nimport { IApplication } from '@c8y/client';\nimport { UpdateType } from './apps-to-update-remotes-select.model';\nimport { Subject, BehaviorSubject, combineLatest, Observable, of } from 'rxjs';\nimport { map, takeUntil } from 'rxjs/operators';\n\n@Component({\n templateUrl: './apps-to-update-remotes-select.component.html',\n selector: 'c8y-apps-to-update-remotes-select'\n})\nexport class AppsToUpdateRemotesSelectComponent implements OnInit, OnDestroy {\n @Input() apps: IApplication[];\n @Input() updateType: UpdateType;\n @Input() pluginName: string;\n @Input() appsDisabled: Set<IApplication['id']>;\n private destroy$ = new Subject<void>();\n filterTerm$: BehaviorSubject<string> = new BehaviorSubject('');\n filteredApps$: Observable<IApplication[]> = new BehaviorSubject([]);\n textConfig: {\n header: string;\n applyButton: string;\n };\n appsToUpdateRemotes: IApplication[] = [];\n result: Promise<IApplication[]> = new Promise((resolve, reject) => {\n this._update = resolve;\n this._cancel = reject;\n });\n private _update: (apps: IApplication[]) => void;\n private _cancel: (reason?: any) => void;\n\n constructor(\n private bsModalRef: BsModalRef,\n private wizardModalService: WizardModalService,\n private ecosystemService: EcosystemService\n ) {}\n\n ngOnInit(): void {\n this.filteredApps$ = combineLatest([of(this.apps), this.filterTerm$]).pipe(\n map(([apps, filterTerm]) =>\n filterTerm.trim().length === 0\n ? apps\n : apps.filter((application: IApplication) =>\n this.ecosystemService.filterContainString(application.name, filterTerm)\n )\n )\n );\n this.textConfig =\n this.updateType === 'install'\n ? {\n header: gettext('Select applications to install the plugin to'),\n applyButton: gettext('Install')\n }\n : {\n header: gettext('Select applications to uninstall the plugin from'),\n applyButton: gettext('Uninstall')\n };\n }\n\n cancel() {\n this.bsModalRef.hide();\n this._cancel();\n }\n\n setSelectedApps(selected: boolean, app: IApplication) {\n selected\n ? this.appsToUpdateRemotes.push(app)\n : (this.appsToUpdateRemotes = this.appsToUpdateRemotes.filter(\n application => app.key !== application.key\n ));\n }\n\n ngOnDestroy(): void {\n this.destroy$.next();\n this.destroy$.complete();\n }\n\n async duplicateApp() {\n const wizardConfig: WizardConfig = {\n headerText: gettext('Duplicate application'),\n headerIcon: 'c8y-copy'\n };\n\n const initialState: Record<string, unknown> = {\n wizardConfig,\n componentInitialState: {\n noBackButton: true\n },\n id: 'duplicateApplication'\n };\n\n const modalOptions: ModalOptions = { initialState };\n\n const modalRef = this.wizardModalService.show(modalOptions);\n modalRef.content.onClose.pipe(takeUntil(this.destroy$)).subscribe(async () => {\n this.apps = await this.getOwnedHostedApps();\n this.ngOnInit();\n });\n }\n\n async apply() {\n this._update(this.appsToUpdateRemotes);\n this.bsModalRef.hide();\n }\n\n private async getOwnedHostedApps(): Promise<IApplication[]> {\n return (await this.ecosystemService.getWebApplications()).filter(\n app => this.ecosystemService.isOwner(app) && app.type !== ApplicationType.EXTERNAL\n );\n }\n}\n","<div class=\"viewport-modal\">\n <div class=\"modal-header dialog-header\">\n <i [c8yIcon]=\"'c8y-modules'\"></i>\n <div class=\"modal-title h4\" id=\"modal-title\" translate>Custom applications</div>\n </div>\n <div class=\"inner-scroll\" id=\"modal-body\">\n <div class=\"p-16 text-center separator-bottom sticky-top bg-component\">\n <p class=\"text-medium\">\n {{ textConfig.header | translate }}\n </p>\n <c8y-filter (onSearch)=\"filterTerm$.next($event)\"></c8y-filter>\n </div>\n <c8y-list-group *ngIf=\"apps.length; else emptyList\">\n <c8y-li\n [ngClass]=\"{ disabled: updateType === 'install' && appsDisabled.has(app.id) }\"\n *ngFor=\"let app of filteredApps$ | async\"\n data-cy=\"apps-to-update-remotes-select--applications-list\"\n >\n <c8y-li-checkbox (onSelect)=\"setSelectedApps($event, app)\" data-cy=\"apps-to-update-remotes-select--app-checkbox\"></c8y-li-checkbox>\n <c8y-li-icon class=\"p-l-0 icon-32\">\n <c8y-app-icon\n class=\"list-group-icon\"\n [app]=\"app\"\n [contextPath]=\"app.contextPath\"\n [name]=\"app.name\"\n ></c8y-app-icon>\n </c8y-li-icon>\n <div class=\"d-flex\">\n <div class=\"p-r-8\">\n <p class=\"text-medium\" [innerText]=\"app | humanizeAppName | async\"></p>\n <p class=\"small text-muted\">{{ app.description }}</p>\n </div>\n <span class=\"label m-l-auto a-s-start\" [ngClass]=\"app | appState: 'class'\">\n {{ app | appState: 'label' | translate }}\n </span>\n </div>\n </c8y-li>\n </c8y-list-group>\n </div>\n <div class=\"modal-footer\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Cancel' | translate }}\"\n type=\"button\"\n (click)=\"cancel()\"\n >\n {{ 'Cancel' | translate }}\n </button>\n <button\n class=\"btn btn-primary\"\n title=\"{{ textConfig.applyButton | translate }}\"\n [disabled]=\"appsToUpdateRemotes.length === 0\"\n (click)=\"apply()\"\n >\n {{ textConfig.applyButton | translate }}\n </button>\n </div>\n</div>\n<ng-template #emptyList>\n <c8y-ui-empty-state\n [icon]=\"'c8y-modules'\"\n [title]=\"'No custom applications available.' | translate\"\n *ngIf=\"updateType !== 'install'\"\n [horizontal]=\"true\"\n ></c8y-ui-empty-state>\n <ng-container *ngIf=\"updateType === 'install'\">\n <c8y-ui-empty-state\n [icon]=\"'c8y-modules'\"\n [title]=\"'No custom applications available.' | translate\"\n [subtitle]=\"'Create a custom application by duplicating an existing one.' | translate\"\n [horizontal]=\"true\"\n >\n <button\n class=\"btn btn-sm btn-default m-t-8\"\n title=\"{{ 'Duplicate' | translate }}\"\n (click)=\"duplicateApp()\"\n >\n {{ 'Duplicate' | translate }}\n </button>\n </c8y-ui-empty-state>\n </ng-container>\n</ng-template>\n","import { Component, EventEmitter, Input, OnInit, Output } from '@angular/core';\nimport { ApplicationPlugin, PackageType, PluginsService } from '@c8y/ngx-components';\n\n@Component({\n selector: 'c8y-plugin-list-item',\n templateUrl: './plugin-list-item.component.html'\n})\nexport class PluginListItemComponent implements OnInit {\n @Input() plugin: ApplicationPlugin;\n @Input() selectable: boolean;\n @Input() hideSource = false;\n @Output() isItemSelected: EventEmitter<boolean> = new EventEmitter();\n\n packageType: PackageType = PackageType.UNKNOWN;\n\n readonly PACKAGE_TYPE = PackageType;\n\n constructor(private pluginService: PluginsService) {}\n\n ngOnInit() {\n this.packageType = this.pluginService.getPackageType(this.plugin.originApp);\n }\n\n onChange(event) {\n this.plugin.selected = !this.plugin.selected;\n this.isItemSelected.next(event);\n }\n}\n","<c8y-li-checkbox\n class=\"p-r-16 p-l-0\"\n (change)=\"onChange($event.target.checked)\"\n *ngIf=\"selectable\"\n [disabled]=\"plugin.installed\"\n [selected]=\"plugin.selected\"\n></c8y-li-checkbox>\n<c8y-li-icon class=\"p-l-0 text-center\">\n <i class=\"c8y-plugin-icon\">\n <span>{{ plugin.name?.substr(0, 2) }}</span>\n </i>\n</c8y-li-icon>\n<div class=\"p-relative\">\n <div [ngClass]=\"{ 'p-r-8': selectable }\">\n <p>\n <span class=\"text-medium\">{{ plugin.name }}</span>\n <em class=\"text-muted small m-l-8\">{{ plugin.version }}</em>\n <span *ngIf=\"plugin.installed\">\n <i\n class=\"text-success\"\n [c8yIcon]=\"'check-circle'\"\n ></i>\n <em\n class=\"text-muted small\"\n translate\n >\n Installed`plugins`\n </em>\n </span>\n </p>\n <p class=\"small l-h-tight\">{{ plugin.description }}</p>\n </div>\n\n <span\n class=\"tag tag--info a-s-start m-t-8\"\n *ngIf=\"selectable && !hideSource\"\n >\n {{ plugin.contextPath }}\n </span>\n\n <span\n class=\"tag a-s-start m-t-8 m-l-4\"\n [ngClass]=\"{\n 'tag--default': packageType === PACKAGE_TYPE.COMMUNITY,\n 'tag--primary': packageType === PACKAGE_TYPE.OFFICIAL\n }\"\n >\n {{ plugin.originApp?.label || plugin.originApp?.manifest?.label | translatePackageLabel }}\n </span>\n</div>\n","import { Component, EventEmitter, Input, Output } from '@angular/core';\nimport { ApplicationRemotePlugins, ApplicationType, IApplication } from '@c8y/client';\nimport {\n AlertService,\n ApplicationPlugin,\n GainsightService,\n PluginsService,\n gettext,\n HumanizeAppNamePipe\n} from '@c8y/ngx-components';\nimport {\n EcosystemService,\n PRODUCT_EXPERIENCE_ECOSYSTEM\n} from '@c8y/ngx-components/ecosystem/shared';\nimport { TranslateService } from '@ngx-translate/core';\nimport { BsModalService } from 'ngx-bootstrap/modal';\nimport { BehaviorSubject, firstValueFrom, Observable } from 'rxjs';\nimport { AppsToUpdateRemotesSelectComponent } from './apps-to-update-remotes-select.component';\nimport { UpdateType } from './apps-to-update-remotes-select.model';\nimport { pick } from 'lodash-es';\n\n@Component({\n selector: 'c8y-plugin-list',\n templateUrl: './plugin-list.component.html'\n})\nexport class PluginListComponent {\n CURRENT_LOCATION = location.href;\n\n @Input() plugins$: Observable<ApplicationPlugin[]>;\n @Input() emptyListText = '';\n @Input() selectable: boolean;\n @Input() hideSource = false;\n /**\n * Shows the install button for each plugin separately. Currently used in package-details view.\n */\n @Input() installable = false;\n @Input() package: IApplication;\n @Output() selectedItems: EventEmitter<ApplicationPlugin[]> = new EventEmitter();\n remotePlugins$: BehaviorSubject<ApplicationRemotePlugins> = new BehaviorSubject({});\n selectedPlugins: { [key: string]: ApplicationPlugin } = {};\n updatingPluginId: Record<UpdateType, string> = { install: '', uninstall: '' };\n appsDisabled: Set<IApplication['id']> = new Set<IApplication['id']>();\n\n constructor(\n private ecosystemService: EcosystemService,\n private bsModalService: BsModalService,\n private pluginsService: PluginsService,\n private alertService: AlertService,\n private translateService: TranslateService,\n private gainsightService: GainsightService,\n private humanizeAppNamePipe: HumanizeAppNamePipe\n ) {}\n\n updateSelectedItems(selected: boolean, plugin: ApplicationPlugin) {\n this.selectedPlugins[plugin.id] = selected ? plugin : undefined;\n const onlyInstalledPlugins = Object.values(this.selectedPlugins).filter(Boolean);\n this.selectedItems.emit(onlyInstalledPlugins);\n }\n\n async installPlugin(plugin: ApplicationPlugin) {\n await this.updateAppRemotes(plugin, 'install');\n }\n\n async uninstallPlugin(plugin: ApplicationPlugin) {\n await this.updateAppRemotes(plugin, 'uninstall');\n }\n\n private async updateAppRemotes(plugin: ApplicationPlugin, updateType: UpdateType) {\n this.updatingPluginId[updateType] = plugin?.id;\n let initialState: Pick<\n AppsToUpdateRemotesSelectComponent,\n 'apps' | 'updateType' | 'pluginName' | 'appsDisabled'\n >;\n try {\n const apps = await this.getAppsForUpdate(plugin, updateType);\n initialState = {\n apps,\n updateType,\n pluginName: plugin.name,\n appsDisabled: this.appsDisabled\n };\n } catch (e) {\n this.alertService.addServerFailure(e);\n this.updatingPluginId[updateType] = '';\n return;\n }\n\n let selectedApps: IApplication[];\n try {\n selectedApps = await this.selectApps(initialState);\n if (!selectedApps) {\n this.updatingPluginId[updateType] = '';\n return;\n }\n } catch {\n // unreached\n }\n\n if (updateType === 'install') {\n const isArchived = await this.ecosystemService.verifyArchived([plugin]);\n if (!isArchived) {\n this.updatingPluginId[updateType] = '';\n return;\n }\n\n const licensesVerifiedByUser = await this.ecosystemService.verifyLicenses([plugin]);\n if (!licensesVerifiedByUser) {\n this.updatingPluginId[updateType] = '';\n return;\n }\n }\n\n for (const app of selectedApps) {\n try {\n if (updateType === 'install') {\n const versionIsCompatible = await this.ecosystemService.verifyPluginVersionsCompatibility(\n [plugin],\n app\n );\n\n if (!versionIsCompatible) {\n continue;\n }\n }\n\n await this.handleRemotesUpdate(app, plugin, updateType);\n const humanizedAppName = await firstValueFrom(this.humanizeAppNamePipe.transform(app));\n const successText =\n updateType === 'install'\n ? this.translateService.instant(\n gettext('Plugin installed to application \"{{ appName }}\".'),\n {\n appName: humanizedAppName\n }\n )\n : this.translateService.instant(\n gettext('Plugin uninstalled from application \"{{ appName }}\".'),\n { appName: humanizedAppName }\n );\n this.alertService.success(successText);\n this.onUpdateEventHandleGS(plugin, app, updateType);\n } catch (error) {\n this.onUpdateEventHandleGS(plugin, app, updateType, error);\n }\n }\n this.updatingPluginId[updateType] = '';\n }\n\n private onUpdateEventHandleGS(\n plugin: ApplicationPlugin,\n app: IApplication,\n updateType: UpdateType,\n error?: unknown\n ) {\n const pluginCustomEventInfo = pick(plugin, [\n 'name',\n 'contextPath',\n 'module',\n 'version',\n 'type',\n 'id'\n ] satisfies (keyof ApplicationPlugin)[]);\n\n const gsEventResult =\n updateType === 'install'\n ? PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.RESULTS.PLUGIN_INSTALLED\n : PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.RESULTS.PLUGIN_REMOVED;\n\n const eventData = {\n component: PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.COMPONENTS.PLUGIN_LIST,\n result: error || gsEventResult,\n url: this.CURRENT_LOCATION,\n ...pluginCustomEventInfo,\n targetApplicationName: app.name,\n targetApplicationContextPath: app.contextPath,\n ...(error && { error })\n };\n\n this.gainsightService.triggerEvent(\n PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.EVENTS.PACKAGE_PLUGINS,\n eventData\n );\n }\n\n private async getAppsForUpdate(plugin: ApplicationPlugin, updateType: UpdateType) {\n let apps = (await this.ecosystemService.getWebApplications()).filter(\n app => this.ecosystemService.isOwner(app) && app.type !== ApplicationType.EXTERNAL\n );\n\n if (updateType === 'install') {\n this.appsDisabled.clear();\n for (const app of apps) {\n if (this.isPluginInstalledInApp(plugin, app)) {\n this.appsDisabled.add(app.id);\n }\n }\n }\n\n if (updateType === 'uninstall') {\n const installedApps: IApplication[] = [];\n for (const app of apps) {\n if (this.isPluginInstalledInApp(plugin, app)) {\n installedApps.push(app);\n }\n }\n apps = installedApps;\n }\n return apps;\n }\n\n private isPluginInstalledInApp(plugin: ApplicationPlugin, app: IApplication): boolean {\n const appRemotes = this.pluginsService.getMFRemotes(app) || {};\n\n for (const [remoteName, modules] of Object.entries(appRemotes)) {\n const pluginFromThisPackageIsInstalled =\n this.getPluginContextPathWithoutVersion(remoteName) === plugin.contextPath;\n const specificPluginModuleIsInstalled = modules.some(module => module === plugin.module);\n if (pluginFromThisPackageIsInstalled && specificPluginModuleIsInstalled) {\n return true;\n }\n }\n return false;\n }\n\n private getPluginContextPathWithoutVersion(remote: string) {\n return remote.split('@')[0];\n }\n\n private async handleRemotesUpdate(\n application: IApplication,\n plugin: ApplicationPlugin,\n updateType: UpdateType\n ) {\n try {\n // When remotes object is not set in the configuration object of an application.\n // Fallback to setInitialRemotes is triggered.\n const { remotes, excludedRemotes } = await (updateType === 'install'\n ? this.pluginsService.addRemotes(application, plugin)\n : this.pluginsService.removeRemotes(application, this.getAllPluginsToRemove(plugin)));\n if (!application.config) {\n application.config = {};\n }\n application.config.remotes = remotes;\n application.config.excludedRemotes = excludedRemotes;\n const actualRemotes = this.pluginsService.getMFRemotes(application);\n return this.emitRemotes(actualRemotes);\n } catch (er) {\n if (er) {\n this.alertService.addServerFailure(er);\n }\n throw er;\n }\n }\n\n private getAllPluginsToRemove(plugin: ApplicationPlugin): ApplicationPlugin[] {\n return this.package.applicationVersions.map(av => ({\n id: `${plugin.contextPath}@${av.version}/${plugin.module}`,\n idLatest: `${plugin.contextPath}/${plugin.module}`,\n module: plugin.module,\n path: plugin.path\n }));\n }\n\n private emitRemotes(remotes: ApplicationRemotePlugins): ApplicationRemotePlugins {\n this.remotePlugins$.next(remotes);\n return { ...this.remotePlugins$.value };\n }\n\n private async selectApps(\n initialState: Pick<\n AppsToUpdateRemotesSelectComponent,\n 'apps' | 'updateType' | 'pluginName' | 'appsDisabled'\n >\n ): Promise<IApplication[]> {\n try {\n return await (\n this.bsModalService.show(AppsToUpdateRemotesSelectComponent, {\n class: 'modal-sm',\n ariaDescribedby: 'modal-body',\n ariaLabelledBy: 'modal-title',\n initialState,\n ignoreBackdropClick: true,\n keyboard: false\n }).content as AppsToUpdateRemotesSelectComponent\n ).result;\n } catch (er) {\n return;\n }\n }\n}\n","<c8y-list-group class=\"bg-inherit\">\n <ng-container *ngIf=\"(plugins$ | async)?.length !== 0; else emptyList\">\n <ng-container *ngFor=\"let plugin of plugins$ | async\">\n <c8y-li [ngClass]=\"{ disabled: plugin.installed }\" class=\"bg-inherit\">\n <c8y-plugin-list-item\n (isItemSelected)=\"updateSelectedItems($event, plugin)\"\n [plugin]=\"plugin\"\n [selectable]=\"selectable\"\n [hideSource]=\"hideSource\"\n class=\"d-flex\"\n ></c8y-plugin-list-item>\n <div class=\"p-l-40 m-t-4\">\n <button\n *ngIf=\"installable\"\n (click)=\"uninstallPlugin(plugin)\"\n [ngClass]=\"{ 'btn-pending': plugin.id === updatingPluginId.uninstall }\"\n [disabled]=\"updatingPluginId.uninstall && plugin.id !== updatingPluginId.uninstall\"\n class=\"btn btn-danger btn-sm m-l-4\"\n title=\"{{ 'Uninstall plugin' | translate }}\"\n data-cy=\"plugin-list--uninstall-plugin-button\"\n translate\n >\n Uninstall plugin\n </button>\n <button\n *ngIf=\"installable\"\n (click)=\"installPlugin(plugin)\"\n [ngClass]=\"{ 'btn-pending': plugin.id === updatingPluginId.install }\"\n [disabled]=\"updatingPluginId.install && plugin.id !== updatingPluginId.install\"\n class=\"btn btn-default btn-sm m-l-8\"\n title=\"{{ 'Install plugin' | translate }}\"\n data-cy=\"plugin-list--install-plugin-button\"\n translate\n >\n Install plugin\n </button>\n </div>\n </c8y-li>\n </ng-container>\n </ng-container>\n</c8y-list-group>\n<ng-template #emptyList>\n <div class=\"c8y-empty-state text-left\" *ngIf=\"emptyListText\">\n <h1 c8yIcon=\"plugin\"></h1>\n <p>\n {{ emptyListText | translate }}\n </p>\n </div>\n</ng-template>\n","import { Component, OnInit, ViewChild } from '@angular/core';\nimport { IApplication, InventoryService } from '@c8y/client';\nimport { ApplicationPlugin, PluginsService } from '@c8y/ngx-components';\nimport { EcosystemService } from '@c8y/ngx-components/ecosystem/shared';\nimport { PluginsExportScopes } from '@c8y/ngx-components';\nimport { BehaviorSubject } from 'rxjs';\n\n@Component({\n selector: 'c8y-update-application-modal',\n templateUrl: './update-application-modal.component.html'\n})\nexport class UpdateApplicationModalComponent implements OnInit {\n @ViewChild('modal', { static: false }) private modal;\n isUpdateOngoing = false;\n updateFailure = false;\n sourcePackage: IApplication;\n application: IApplication;\n result: Promise<void> = new Promise((resolve, reject) => {\n this._resolve = resolve;\n this._reject = reject;\n });\n orphanedPlugins$: BehaviorSubject<ApplicationPlugin[]> = new BehaviorSubject([]);\n newPlugins$: BehaviorSubject<ApplicationPlugin[]> = new BehaviorSubject([]);\n\n private _resolve: (value: void | PromiseLike<void>) => void;\n private _reject: (value: void | PromiseLike<void>) => void;\n\n constructor(\n private inventoryService: InventoryService,\n private ecosystemService: EcosystemService,\n private pluginsService: PluginsService\n ) {}\n\n ngOnInit(): void {\n const hasDelta = this.getRemoteDelta();\n if (!hasDelta) {\n // start update directly if no remote delta detected\n this.updateApplication();\n }\n }\n\n /**\n * Checks if there is a delta between the current application and the source package remotes.\n * @returns {boolean} true if there is a delta between the current application and the source package remotes.\n */\n getRemoteDelta() {\n const currentRemotes = this.pluginsService.getMFExports(this.application, []);\n const possibleNewRemotes = this.pluginsService.getMFExports(this.sourcePackage, []);\n const installedRemotes = this.pluginsService.getMFRemotes(this.application);\n const installedRemotesIds = PluginsService.convertInstalledRemotesToIds(installedRemotes);\n\n const allRemotesToRemove = this.getAllRemotesToRemove(\n currentRemotes,\n possibleNewRemotes,\n installedRemotesIds\n );\n\n const allRemotesToAdd = this.getAllRemotesToAdd(possibleNewRemotes, currentRemotes);\n\n this.orphanedPlugins$.next(allRemotesToRemove);\n this.newPlugins$.next(allRemotesToAdd);\n\n return allRemotesToRemove.length !== 0 || allRemotesToAdd.length !== 0;\n }\n\n close() {\n this._reject();\n this.modal._dismiss();\n }\n\n done() {\n if (!this.updateFailure) {\n this._resolve();\n this.modal._dismiss();\n return;\n }\n this._reject();\n this.modal._dismiss();\n }\n\n async updateApplication() {\n try {\n this.isUpdateOngoing = true;\n const toInstallPlugins = this.newPlugins$.value.filter(({ selected }) => selected === true);\n const toRemovePlugins = this.orphanedPlugins$.value;\n this.orphanedPlugins$.next([]);\n this.newPlugins$.next([]);\n if (toRemovePlugins.length > 0) {\n const remotes = await this.pluginsService.removeRemotes(this.application, toRemovePlugins);\n this.application.config.remotes = remotes;\n }\n if (toInstallPlugins.length > 0) {\n const remotes = await this.pluginsService.addRemotes(this.application, toInstallPlugins);\n this.application.config.remotes = remotes;\n }\n const binaryMoId = this.sourcePackage.activeVersionId;\n await this.inventoryService.detail(binaryMoId); // only trying if we can access it\n await this.ecosystemService.uploadBinaryFromOtherPackage(\n this.sourcePackage,\n this.application,\n this.sourcePackage.activeVersionId\n );\n } catch (e) {\n if (e.res?.status === 404) {\n try {\n this.updateFailure = !(await this.ecosystemService.fallbackToClone(\n this.application,\n this.sourcePackage\n ));\n } catch (ex) {\n this.updateFailure = true;\n this.ecosystemService.alertError(e);\n }\n } else {\n this.updateFailure = true;\n this.ecosystemService.alertError(e);\n }\n } finally {\n this.isUpdateOngoing = false;\n }\n }\n\n private getAllRemotesToAdd(\n possibleNewRemotes: ApplicationPlugin[],\n currentRemotes: ApplicationPlugin[]\n ) {\n return possibleNewRemotes\n .filter(possibleNewRemote => {\n const isUnchanged = !!currentRemotes.some(\n currentRemote =>\n PluginsService.createPluginId(\n this.application.contextPath,\n possibleNewRemote.module,\n '',\n true\n ) === currentRemote.idLatest\n );\n\n const isSelf =\n possibleNewRemote.scope === PluginsExportScopes.SELF ||\n possibleNewRemote.scope === PluginsExportScopes.SELF_OPTIONAL;\n\n return !isUnchanged && isSelf;\n })\n .map(newRemote => {\n newRemote.contextPath = this.application.contextPath;\n newRemote.id = `${newRemote.contextPath}/${newRemote.module}`;\n newRemote.selected = newRemote.scope === PluginsExportScopes.SELF;\n return newRemote;\n });\n }\n\n private getAllRemotesToRemove(\n currentRemotes: ApplicationPlugin[],\n possibleNewRemotes: ApplicationPlugin[],\n installedRemotesIds: string[]\n ) {\n return currentRemotes\n .filter(currentRemote => {\n const isUnchanged = !!possibleNewRemotes.some(\n newRemote =>\n PluginsService.createPluginId(\n this.application.contextPath,\n newRemote.module,\n '',\n true\n ) === currentRemote.idLatest\n );\n const isInstalled = installedRemotesIds\n ? !!installedRemotesIds.some(\n currentInstalled =>\n currentRemote.id === currentInstalled || currentRemote.idLatest === currentInstalled\n )\n : false;\n\n const isSelf =\n currentRemote.scope === PluginsExportScopes.SELF ||\n currentRemote.scope === PluginsExportScopes.SELF_OPTIONAL;\n\n return !isUnchanged && isInstalled && isSelf;\n })\n .map(toRemoveRemote => {\n toRemoveRemote.contextPath = this.application.contextPath;\n toRemoveRemote.id = `${toRemoveRemote.contextPath}/${toRemoveRemote.module}`;\n toRemoveRemote.selected = true;\n return toRemoveRemote;\n });\n }\n}\n","<c8y-modal\n [title]=\"'Update application' | translate\"\n [headerClasses]=\"'dialog-header'\"\n [customFooter]=\"true\"\n #modal\n>\n <ng-container c8y-modal-title>\n <span class=\"dlt-c8y-icon-installing-updates\"></span>\n </ng-container>\n\n <ng-container\n *ngIf=\"\n (orphanedPlugins$ | async).length > 0 || (newPlugins$ | async).length > 0;\n else updateProgress\n \"\n >\n <p\n class=\"text-center text-break-word p-24 text-14\"\n translate\n >\n Updating this blueprint will change the default plugins. Review the plugin changes before\n proceeding.\n </p>\n <c8y-list-group *ngIf=\"(orphanedPlugins$ | async).length > 0\">\n <c8y-li [collapsed]=\"true\">\n <c8y-li-icon>\n <span class=\"badge badge-danger\">{{ (orphanedPlugins$ | async).length }}</span>\n </c8y-li-icon>\n <c8y-li-body>\n <div translate>Orphaned plugins</div>\n </c8y-li-body>\n <c8y-li-footer translate>\n Some plugins are not contained in the new version of this blueprint and will therefore get\n removed.\n </c8y-li-footer>\n <c8y-li-collapse>\n <c8y-plugin-list\n class=\"m-t-16\"\n [emptyListText]=\"'No plugins available' | translate\"\n [plugins$]=\"orphanedPlugins$\"\n [selectable]=\"false\"\n [hideSource]=\"true\"\n ></c8y-plugin-list>\n </c8y-li-collapse>\n </c8y-li>\n </c8y-list-group>\n\n <c8y-list-group *ngIf=\"(newPlugins$ | async).length > 0\">\n <c8y-li [collapsed]=\"false\">\n <c8y-li-icon>\n <span class=\"badge badge-success\">{{ (newPlugins$ | async).length }}</span>\n </c8y-li-icon>\n <c8y-li-body>\n <div translate>New plugins added</div>\n </c8y-li-body>\n <c8y-li-footer translate>\n This blueprint will add new plugins. Please choose which you want to install.\n </c8y-li-footer>\n <c8y-li-collapse>\n <c8y-plugin-list\n class=\"m-t-16\"\n [emptyListText]=\"'No plugins available' | translate\"\n [plugins$]=\"newPlugins$\"\n [selectable]=\"true\"\n [hideSource]=\"false\"\n ></c8y-plugin-list>\n </c8y-li-collapse>\n </c8y-li>\n </c8y-list-group>\n </ng-container>\n\n <ng-container c8y-modal-footer-custom>\n <div\n class=\"modal-footer\"\n *ngIf=\"\n (orphanedPlugins$ | async).length > 0 || (newPlugins$ | async).length > 0;\n else updateProgressButtons\n \"\n >\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Close' | translate }}\"\n (click)=\"close()\"\n [disabled]=\"isUpdateOngoing\"\n >\n {{ 'Close' | translate }}\n </button>\n <button\n class=\"btn btn-primary\"\n title=\"{{ 'Continue' | translate }}\"\n (click)=\"updateApplication()\"\n *ngIf=\"(orphanedPlugins$ | async).length > 0 || (newPlugins$ | async).length > 0\"\n [disabled]=\"isUpdateOngoing\"\n >\n {{ 'Continue' | translate }}\n </button>\n </div>\n </ng-container>\n\n <ng-template #updateProgressButtons>\n <ng-container c8y-modal-footer-custom>\n <div class=\"modal-footer\">\n <button\n class=\"btn btn-default\"\n title=\"{{ 'Close' | translate }}\"\n (click)=\"done()\"\n [disabled]=\"isUpdateOngoing\"\n >\n {{ 'Close' | translate }}\n </button>\n </div>\n </ng-container>\n </ng-template>\n\n <ng-template #updateProgress>\n <c8y-loading\n class=\"text-center d-block p-t-56 p-b-56 m-t-4 m-b-4\"\n style=\"min-height: 180px\"\n layout=\"application\"\n *ngIf=\"isUpdateOngoing\"\n [message]=\"'Updating…' | translate\"\n ></c8y-loading>\n\n <c8y-operation-result\n type=\"success\"\n *ngIf=\"!isUpdateOngoing && !updateFailure\"\n text=\"{{ 'Update completed' | translate }}\"\n [size]=\"120\"\n [vertical]=\"true\"\n ></c8y-operation-result>\n <c8y-operation-result\n type=\"error\"\n *ngIf=\"!isUpdateOngoing && updateFailure\"\n text=\"{{ 'Failed to update application.' | translate }}\"\n [size]=\"120\"\n [vertical]=\"true\"\n ></c8y-operation-result>\n </ng-template>\n</c8y-modal>\n","import { Component, OnInit } from '@angular/core';\nimport { FormBuilder, FormGroup, Validators } from '@angular/forms';\nimport { ActivatedRoute, Router } from '@angular/router';\nimport {\n ApplicationService,\n ApplicationType,\n BillingMode,\n IApplication,\n IManagedObject,\n InventoryService,\n Isolation\n} from '@c8y/client';\nimport {\n AlertService,\n GainsightService,\n ModalService,\n Permissions,\n PropertiesListItem,\n ViewContext,\n gettext\n} from '@c8y/ngx-components';\nimport {\n ApplicationState,\n EcosystemService,\n PRODUCT_EXPERIENCE_ECOSYSTEM,\n packageProperties\n} from '@c8y/ngx-components/ecosystem/shared';\nimport { TranslateService } from '@ngx-translate/core';\nimport { BsModalService } from 'ngx-bootstrap/modal';\nimport { SubscriptionModalComponent } from './subscription-modal/subscription-modal.component';\nimport { UpdateApplicationModalComponent } from './update-application-modal/update-application-modal.component';\n\nconst MICROSERVICES_BASE_PATH = '/ecosystem/microservice/microservices';\nconst APPLICATIONS_BASE_PATH = '/ecosystem/application/applications';\n\n@Component({\n selector: 'c8y-application-properties',\n templateUrl: './application-properties.component.html'\n})\nexport class ApplicationPropertiesComponent implements OnInit {\n CURRENT_LOCATION = location.href;\n formGroup: FormGroup;\n application: IApplication;\n binaryMo: IManagedObject;\n singleTenant = false;\n subscription = false;\n canOpenInBrowser: boolean;\n disableOpenInBrowser: boolean;\n canDelete: boolean;\n isOwner: boolean;\n isSubscribed: boolean;\n isPackage: boolean;\n isMicroservice: boolean;\n isFeature: boolean;\n isExternal: boolean;\n isUnpacked: boolean;\n iconMap = {\n HOSTED: 'cloud',\n EXTERNAL: 'external-link-square',\n MICROSERVICE: 'microchip'\n };\n appState: ApplicationState;\n sourcePackage: IApplication;\n isLoading = true;\n isActivityLogSupported: boolean;\n isCustomMicroservice: boolean;\n hasAdminPermissions = false;\n noDescriptionLabel = gettext('No description available.');\n\n breadcrumbConfig: { icon: string; labe