UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

640 lines (636 loc) 302 kB
import * as i0 from '@angular/core'; import { Input, Component, ViewChild, Injectable, EventEmitter, Output, inject, NgModule } from '@angular/core'; import * as i2 from '@c8y/ngx-components'; import { ListGroupComponent, ListItemTimelineComponent, ListItemComponent, ListItemIconComponent, ListItemBodyComponent, IconDirective, ListItemActionComponent, FormGroupComponent, C8yTranslatePipe, DatePipe, Status, LoadingComponent, PluginsService, PluginsExportScopes, ModalComponent, C8yTranslateDirective, ListItemFooterComponent, ListItemCollapseComponent, OperationResultComponent, Permissions, ViewContext, TitleComponent, BreadcrumbComponent, BreadcrumbItemComponent, AppIconComponent, TextareaAutoresizeDirective, PropertiesListComponent, RequiredInputPlaceholderDirective, MessagesComponent, MessageDirective, HumanizeAppNamePipe, WizardHeaderComponent, WizardBodyComponent, WizardFooterComponent, IfAllowedDirective, ActionBarItemComponent, ListDisplaySwitchComponent, HelpComponent, EmptyStateComponent, TypeaheadComponent, ForOfDirective, HighlightComponent, C8yStepper, PackageType, MarkdownToHtmlPipe, CoreModule, FormsModule as FormsModule$1, hookTab, hookRoute, hookWizard } from '@c8y/ngx-components'; import * as i1 from '@c8y/ngx-components/ecosystem/shared'; import { UploadArchiveComponent, packageProperties, PRODUCT_EXPERIENCE_ECOSYSTEM, AddApplicationComponent, defaultPackageAvailabilities, EcosystemWizards, ListFiltersComponent, ApplicationCardComponent, ApplicationPropertiesFormComponent, ERROR_TYPE, APP_STATE, PackageVersionSelectComponent, PACKAGE_TYPE_LABELS, TranslatePackageLabelPipe, defaultPackageTypes, defaultPackageContents, ArchivedFilterComponent, PackageChangelogComponent, SharedEcosystemModule } from '@c8y/ngx-components/ecosystem/shared'; import { NgFor, NgClass, NgIf, AsyncPipe, NgSwitch, NgSwitchCase, NgStyle } from '@angular/common'; import * as i3 from '@angular/forms'; import { Validators, FormsModule, ReactiveFormsModule } from '@angular/forms'; import * as i1$2 from '@angular/router'; import { Router, RouterModule } from '@angular/router'; import * as i4 from '@c8y/client'; import { Isolation, BillingMode, ApplicationType } from '@c8y/client'; import { gettext } from '@c8y/ngx-components/gettext'; import * as i6 from '@ngx-translate/core'; import * as i1$1 from 'ngx-bootstrap/modal'; import { isEmpty } from 'lodash'; import { BehaviorSubject, Subject, combineLatest, of, from } from 'rxjs'; import { PluginListComponent, ApplicationPluginsModule } from '@c8y/ngx-components/ecosystem/application-plugins'; import { TooltipDirective, TooltipModule } from 'ngx-bootstrap/tooltip'; import { IconSelectorWrapperComponent, IconSelectorModule } from '@c8y/ngx-components/icon-selector'; import { tap, switchMap, shareReplay, takeUntil, map } from 'rxjs/operators'; import { A11yModule } from '@angular/cdk/a11y'; import { ArchivedConfirmModule } from '@c8y/ngx-components/ecosystem/archived-confirm'; import { LicenseConfirmModule } from '@c8y/ngx-components/ecosystem/license-confirm'; import { BsDropdownDirective, BsDropdownToggleDirective, BsDropdownMenuDirective, BsDropdownModule } from 'ngx-bootstrap/dropdown'; import { PopoverModule } from 'ngx-bootstrap/popover'; import { pick } from 'lodash-es'; class ActivityLogComponent { constructor(ecosystemService, alertService) { this.ecosystemService = ecosystemService; this.alertService = alertService; this.hasAdminPermissions = false; this.archives = []; this.canReactivate = false; } get uploadProgress() { return this.ecosystemService.progress; } async ngOnInit() { this.canReactivate = this.showReactivate(); this.refresh(); } isActive(archive) { return this.application.activeVersionId === archive.id; } toActivate(archive) { return this.toActivateVersionId === archive.id; } checkIfLast(archive) { return archive.id === this.last.id; } showReactivate() { return this.ecosystemService.isApplication(this.application); } async setActive(archive) { const id = archive.id || archive; this.toActivateVersionId = id; this.isLoading = true; try { this.application = (await this.ecosystemService.setAppActiveVersion(this.application, id)).data; } catch (ex) { this.alertService.addServerFailure(ex); } this.isLoading = false; this.refresh(); } async deleteArchive(archive) { await this.ecosystemService.deleteArchive(archive, this.application); this.refresh(); } async downloadArchive(archive) { await this.ecosystemService.downloadArchive(this.application, archive); } async reactivateArchive() { await this.ecosystemService.reactivateArchive(this.application); } async onRefresh() { await this.refresh(); } async refresh() { this.isLoading = true; this.archives = await this.ecosystemService.listArchives(this.application.id); if (this.application.manifest?.package === 'blueprint') { // filter out entries without description because using them as active may break application's // manifest (changing isPackage property of deployed app to 'true') this.archives = this.archives.filter((archive) => !!archive.description); } this.archives.sort((a, b) => { return new Date(b.created) - new Date(a.created); }); this.last = this.archives[this.archives.length - 1]; this.isLoading = false; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ActivityLogComponent, deps: [{ token: i1.EcosystemService }, { token: i2.AlertService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: ActivityLogComponent, isStandalone: true, selector: "c8y-activity-log", inputs: { application: "application", hasAdminPermissions: "hasAdminPermissions" }, ngImport: i0, template: "<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 | c8yDate: 'd MMM YYYY' }}\n {{ archive.created | c8yDate: '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", dependencies: [{ kind: "component", type: ListGroupComponent, selector: "c8y-list-group" }, { kind: "directive", type: NgFor, selector: "[ngFor][ngForOf]", inputs: ["ngForOf", "ngForTrackBy", "ngForTemplate"] }, { kind: "component", type: ListItemTimelineComponent, selector: "c8y-list-item-timeline, c8y-li-timeline" }, { kind: "directive", type: NgClass, selector: "[ngClass]", inputs: ["class", "ngClass"] }, { kind: "component", type: ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: ListItemIconComponent, selector: "c8y-list-item-icon, c8y-li-icon", inputs: ["icon", "status"] }, { kind: "component", type: ListItemBodyComponent, selector: "c8y-list-item-body, c8y-li-body", inputs: ["body"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "component", type: ListItemActionComponent, selector: "c8y-list-item-action, c8y-li-action", inputs: ["label", "icon", "disabled"], outputs: ["click"] }, { kind: "component", type: FormGroupComponent, selector: "c8y-form-group", inputs: ["hasError", "hasWarning", "hasSuccess", "novalidation", "status"] }, { kind: "component", type: UploadArchiveComponent, selector: "c8y-upload-archive", inputs: ["application", "uploadNewVersion", "preUploadCallback"], outputs: ["applicationChange", "refresh"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: DatePipe, name: "c8yDate" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: ActivityLogComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-activity-log', imports: [ ListGroupComponent, NgFor, ListItemTimelineComponent, NgClass, ListItemComponent, ListItemIconComponent, ListItemBodyComponent, NgIf, IconDirective, ListItemActionComponent, FormGroupComponent, UploadArchiveComponent, C8yTranslatePipe, DatePipe ], template: "<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 | c8yDate: 'd MMM YYYY' }}\n {{ archive.created | c8yDate: '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" }] }], ctorParameters: () => [{ type: i1.EcosystemService }, { type: i2.AlertService }], propDecorators: { application: [{ type: Input }], hasAdminPermissions: [{ type: Input }] } }); class SubscriptionModalComponent { constructor(bsModalRef, ecosystemService, tabsService, modal, applicationService, alertService, contextRouteService) { this.bsModalRef = bsModalRef; this.ecosystemService = ecosystemService; this.tabsService = tabsService; this.modal = modal; this.applicationService = applicationService; this.alertService = alertService; this.contextRouteService = contextRouteService; this.RETRY_TIMEOUT = 3000; this.isLoading = false; this.result = new Promise(resolve => { this._resolve = resolve; }); this.retryCounter = 0; this.TABS = ['Logs', 'Status']; } ngOnInit() { if (this.isSubscribed) { this.unsubscribe(); } else { this.subscribe(); } } async subscribe() { this.retryCounter = 0; this.isLoading = true; this.message = gettext('Subscribing…'); await this.ecosystemService.subscribeApp(this.application); this.getStatusDetails('subscribe'); } async unsubscribe() { this.retryCounter = 0; this.isLoading = true; this.message = gettext('Unsubscribing…'); await this.ecosystemService.unsubscribeApp(this.application); this.getStatusDetails('unsubscribe'); } async getStatusDetails(action) { this.contextRouteService.refreshContext(); const actionSuccessful = action === 'subscribe' ? await this.onSubscribe() : this.onUnsubscribe(); if (actionSuccessful) { return this.hideSubscriptionModal(); } if (this.retryCounter === 4) { this.showWarningModal(action); return this.hideSubscriptionModal(); } this.retryCounter += 1; setTimeout(async () => { this.getStatusDetails(action); }, this.RETRY_TIMEOUT); } async onSubscribe() { try { if (!this.application.activeVersionId) { return true; } const res = (await this.applicationService.getStatusDetails(this.application)).data[0]; return this.shouldShowMSSpecificTabs(res); } catch (er) { this.alertService.addServerFailure(er); } } // Checks if the UI should show tabs with logs and status shouldShowMSSpecificTabs(mo) { return !isEmpty(mo.c8y_Status?.instances) && !!mo.c8y_SupportedLogs; } onUnsubscribe() { return !this.tabsService.areAvailable(this.TABS); } hideSubscriptionModal() { this._resolve(); this.bsModalRef.hide(); this.isLoading = false; } showWarningModal(action) { const title = gettext('Warning'); const body = action === 'subscribe' ? gettext('Something went wrong, please refresh the page or resubscribe the application.') : gettext('Something went wrong, please refresh the page or retry to unsubscribe from the application.'); this.modal.acknowledge(title, body, Status.WARNING, gettext('Close')); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SubscriptionModalComponent, deps: [{ token: i1$1.BsModalRef }, { token: i1.EcosystemService }, { token: i2.TabsService }, { token: i2.ModalService }, { token: i4.ApplicationService }, { token: i2.AlertService }, { token: i2.ContextRouteService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: SubscriptionModalComponent, isStandalone: true, selector: "c8y-subscription-modal", ngImport: i0, template: "<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", dependencies: [{ kind: "directive", type: IconDirective, selector: "[c8yIcon]", inputs: ["c8yIcon"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: SubscriptionModalComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-subscription-modal', imports: [IconDirective, NgIf, LoadingComponent, C8yTranslatePipe], template: "<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" }] }], ctorParameters: () => [{ type: i1$1.BsModalRef }, { type: i1.EcosystemService }, { type: i2.TabsService }, { type: i2.ModalService }, { type: i4.ApplicationService }, { type: i2.AlertService }, { type: i2.ContextRouteService }] }); class UpdateApplicationModalComponent { constructor(inventoryService, ecosystemService, pluginsService) { this.inventoryService = inventoryService; this.ecosystemService = ecosystemService; this.pluginsService = pluginsService; this.isUpdateOngoing = false; this.updateFailure = false; this.result = new Promise((resolve, reject) => { this._resolve = resolve; this._reject = reject; }); this.orphanedPlugins$ = new BehaviorSubject([]); this.newPlugins$ = new BehaviorSubject([]); } ngOnInit() { const hasDelta = this.getRemoteDelta(); if (!hasDelta) { // start update directly if no remote delta detected this.updateApplication(); } } /** * Checks if there is a delta between the current application and the source package remotes. * @returns {boolean} true if there is a delta between the current application and the source package remotes. */ getRemoteDelta() { const currentRemotes = this.pluginsService.getMFExports(this.application, []); const possibleNewRemotes = this.pluginsService.getMFExports(this.sourcePackage, []); const installedRemotes = this.pluginsService.getMFRemotes(this.application); const installedRemotesIds = PluginsService.convertInstalledRemotesToIds(installedRemotes); const allRemotesToRemove = this.getAllRemotesToRemove(currentRemotes, possibleNewRemotes, installedRemotesIds); const allRemotesToAdd = this.getAllRemotesToAdd(possibleNewRemotes, currentRemotes); this.orphanedPlugins$.next(allRemotesToRemove); this.newPlugins$.next(allRemotesToAdd); return allRemotesToRemove.length !== 0 || allRemotesToAdd.length !== 0; } close() { this._reject(); this.modal._dismiss(); } done() { if (!this.updateFailure) { this._resolve(); this.modal._dismiss(); return; } this._reject(); this.modal._dismiss(); } async updateApplication() { try { this.isUpdateOngoing = true; const toInstallPlugins = this.newPlugins$.value.filter(({ selected }) => selected === true); const toRemovePlugins = this.orphanedPlugins$.value; this.orphanedPlugins$.next([]); this.newPlugins$.next([]); if (toRemovePlugins.length > 0) { const remotes = await this.pluginsService.removeRemotes(this.application, toRemovePlugins); this.application.config.remotes = remotes; } if (toInstallPlugins.length > 0) { const remotes = await this.pluginsService.addRemotes(this.application, toInstallPlugins); this.application.config.remotes = remotes; } const binaryMoId = this.sourcePackage.activeVersionId; await this.inventoryService.detail(binaryMoId); // only trying if we can access it await this.ecosystemService.uploadBinaryFromOtherPackage(this.sourcePackage, this.application, this.sourcePackage.activeVersionId); } catch (e) { if (e.res?.status === 404) { try { this.updateFailure = !(await this.ecosystemService.fallbackToClone(this.application, this.sourcePackage)); } catch (ex) { this.updateFailure = true; this.ecosystemService.alertError(e); } } else { this.updateFailure = true; this.ecosystemService.alertError(e); } } finally { this.isUpdateOngoing = false; } } getAllRemotesToAdd(possibleNewRemotes, currentRemotes) { return possibleNewRemotes .filter(possibleNewRemote => { const isUnchanged = !!currentRemotes.some(currentRemote => PluginsService.createPluginId(this.application.contextPath, possibleNewRemote.module, '', true) === currentRemote.idLatest); const isSelf = possibleNewRemote.scope === PluginsExportScopes.SELF || possibleNewRemote.scope === PluginsExportScopes.SELF_OPTIONAL; return !isUnchanged && isSelf; }) .map(newRemote => { newRemote.contextPath = this.application.contextPath; newRemote.id = `${newRemote.contextPath}/${newRemote.module}`; newRemote.selected = newRemote.scope === PluginsExportScopes.SELF; return newRemote; }); } getAllRemotesToRemove(currentRemotes, possibleNewRemotes, installedRemotesIds) { return currentRemotes .filter(currentRemote => { const isUnchanged = !!possibleNewRemotes.some(newRemote => PluginsService.createPluginId(this.application.contextPath, newRemote.module, '', true) === currentRemote.idLatest); const isInstalled = installedRemotesIds ? !!installedRemotesIds.some(currentInstalled => currentRemote.id === currentInstalled || currentRemote.idLatest === currentInstalled) : false; const isSelf = currentRemote.scope === PluginsExportScopes.SELF || currentRemote.scope === PluginsExportScopes.SELF_OPTIONAL; return !isUnchanged && isInstalled && isSelf; }) .map(toRemoveRemote => { toRemoveRemote.contextPath = this.application.contextPath; toRemoveRemote.id = `${toRemoveRemote.contextPath}/${toRemoveRemote.module}`; toRemoveRemote.selected = true; return toRemoveRemote; }); } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: UpdateApplicationModalComponent, deps: [{ token: i4.InventoryService }, { token: i1.EcosystemService }, { token: i2.PluginsService }], target: i0.ɵɵFactoryTarget.Component }); } static { this.ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "14.0.0", version: "20.3.15", type: UpdateApplicationModalComponent, isStandalone: true, selector: "c8y-update-application-modal", viewQueries: [{ propertyName: "modal", first: true, predicate: ["modal"], descendants: true }], ngImport: i0, template: "<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\u2026' | 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", dependencies: [{ kind: "component", type: ModalComponent, selector: "c8y-modal", inputs: ["disabled", "close", "dismiss", "title", "body", "customFooter", "headerClasses", "labels"], outputs: ["onDismiss", "onClose"] }, { kind: "directive", type: NgIf, selector: "[ngIf]", inputs: ["ngIf", "ngIfThen", "ngIfElse"] }, { kind: "directive", type: C8yTranslateDirective, selector: "[translate],[ngx-translate]" }, { kind: "component", type: ListGroupComponent, selector: "c8y-list-group" }, { kind: "component", type: ListItemComponent, selector: "c8y-list-item, c8y-li", inputs: ["active", "highlighted", "emptyActions", "dense", "collapsed", "selectable"], outputs: ["collapsedChange"] }, { kind: "component", type: ListItemIconComponent, selector: "c8y-list-item-icon, c8y-li-icon", inputs: ["icon", "status"] }, { kind: "component", type: ListItemBodyComponent, selector: "c8y-list-item-body, c8y-li-body", inputs: ["body"] }, { kind: "component", type: ListItemFooterComponent, selector: "c8y-list-item-footer, c8y-li-footer", inputs: ["footer"] }, { kind: "component", type: ListItemCollapseComponent, selector: "c8y-list-item-collapse, c8y-li-collapse", inputs: ["collapseWay"] }, { kind: "component", type: PluginListComponent, selector: "c8y-plugin-list", inputs: ["plugins$", "emptyListText", "selectable", "hideSource", "installable", "package", "selectedPlugin"], outputs: ["selectedItems", "showOverview"] }, { kind: "component", type: LoadingComponent, selector: "c8y-loading", inputs: ["layout", "progress", "message"] }, { kind: "component", type: OperationResultComponent, selector: "c8y-operation-result", inputs: ["text", "vertical", "size", "type"] }, { kind: "pipe", type: C8yTranslatePipe, name: "translate" }, { kind: "pipe", type: AsyncPipe, name: "async" }] }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.15", ngImport: i0, type: UpdateApplicationModalComponent, decorators: [{ type: Component, args: [{ selector: 'c8y-update-application-modal', imports: [ ModalComponent, NgIf, C8yTranslateDirective, ListGroupComponent, ListItemComponent, ListItemIconComponent, ListItemBodyComponent, ListItemFooterComponent, ListItemCollapseComponent, PluginListComponent, LoadingComponent, OperationResultComponent, C8yTranslatePipe, AsyncPipe ], template: "<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\u2026' | 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" }] }], ctorParameters: () => [{ type: i4.InventoryService }, { type: i1.EcosystemService }, { type: i2.PluginsService }], propDecorators: { modal: [{ type: ViewChild, args: ['modal', { static: false }] }] } }); const MICROSERVICES_BASE_PATH = '/ecosystem/microservice/microservices'; const APPLICATIONS_BASE_PATH = '/ecosystem/application/applications'; class ApplicationPropertiesComponent { constructor(activatedRoute, ecosystemService, router, formBuilder, applicationService, alertService, inventoryService, permissions, modalService, translate, bsModalService, gainsightService) { this.activatedRoute = activatedRoute; this.ecosystemService = ecosystemService; this.router = router; this.formBuilder = formBuilder; this.applicationService = applicationService; this.alertService = alertService; this.inventoryService = inventoryService; this.permissions = permissions; this.modalService = modalService; this.translate = translate; this.bsModalService = bsModalService; this.gainsightService = gainsightService; this.CURRENT_LOCATION = location.href; this.singleTenant = false; this.subscription = false; this.iconMap = { HOSTED: 'cloud', EXTERNAL: 'external-link-square', MICROSERVICE: 'microchip' }; this.isLoading = true; this.hasAdminPermissions = false; this.noDescriptionLabel = gettext('No description available.'); this.packageProperties = [...packageProperties]; this.isUpdateAvailable = false; } async ngOnInit() { this.hasAdminPermissions = this.permissions.hasRole(Permissions.ROLE_APPLICATION_MANAGEMENT_ADMIN); await this.refresh(); } async refresh() { await this.load(); this.isUnpacked = this.ecosystemService.isUnpacked(this.application); this.isPackage = this.ecosystemService.isPackage(this.application); this.isFeature = this.ecosystemService.isFeature(this.application); this.isExternal = this.ecosystemService.isExternal(this.application); this.isMicroservice = this.ecosystemService.isMicroservice(this.application); this.appState = this.ecosystemService.getAppState(this.application); if (this.isUnpacked) { await this.resolveSourcePackageDetails(); } this.setBreadcrumbConfig(); if (this.isCustomMicroservice) { this.loadBinaryMo(); } } async load() { this.isLoading = true; this.initForm(); await this.loadApplication(); this.isLoading = false; } onApplication(app) { if (app.manifest) { this.singleTenant = app.manifest.isolation === Isolation.PER_TENANT; this.subscription = app.manifest.billingMode === BillingMode.SUBSCRIPTION; } } cancel() { if (this.application.type === ApplicationType.MICROSERVICE) { this.router.navigateByUrl(MICROSERVICES_BASE_PATH); } else { this.router.navigateByUrl(APPLICATIONS_BASE_PATH); } } openApp(app) { this.ecosystemService.openApp(app); } getPackage(entityOrId) { return this.applicationService.detail(entityOrId); } async delete() { try { await this.ecosystemService.deleteApp(this.application); if (this.application.type === ApplicationType.MICROSERVICE) { this.router.navigateByUrl(MICROSERVICES_BASE_PATH); } else { this.router.navigateByUrl(APPLICATIONS_BASE_PATH); } } catch (ex) { if (ex) { this.alertService.addServerFailure(ex); } } } async subscribe() { const initialState = { application: this.application, isSubscribed: false }; await this.confirmSubscriptionChange(initialState); this.loadApplication(); } async unsubscribe() { const initialState = { application: this.application, isSubscribed: true }; await this.confirmSubscriptionChange(initialState); this.loadApplication(); } async loadApplication() { const { id } = this.activatedRoute.snapshot.parent.data.contextData; this.application = await this.ecosystemService.getApplication(id); if (this.application.type === ApplicationType.MICROSERVICE) { this.formGroup.get('name').disable(); } const updatedApplication = { ...this.application, description: this.getDescription(this.application), icon: this.application.config?.icon?.class || this.application.manifest?.icon?.class }; this.formGroup.patchValue(updatedApplication); this.canOpenInBrowser = this.ecosystemService.canOpenAppInBrowser(this.application); this.disableOpenInBrowser = this.canOpenInBrowser && (await this.ecosystemService.isOverwrittenByCustomApp(this.application)); this.canDelete = await this.ecosystemService.canDeleteApp(this.application); this.isOwner = this.ecosystemService.isOwner(this.application); this.isActivityLogSupported = this.isActivityLogSupportedByApp(this.application); this.isCustomMicroservice = this.ecosystemService.isCustomMicroservice(this.application); this.isSubscribed = await this.ecosystemService.checkIfSubscribed(this.application); this.onApplication(this.application); } async save(app) { const icon = this.formGroup.controls['icon'].value; const name = this.formGroup.controls['name'].value; if (icon) { app = { ...app, config: { ...this.application.config, icon: { class: icon }, appTitle: name } }; } else if (this.application.config) { app = { ...app, config: { ...this.application.config, appTitle: name } }; } app.id = this.application.id; try { const { data } = await this.ecosystemService.updateApp(app); this.formGroup.reset(); this.loadApplication(); if (data) { this.alertService.success(gettext('Application saved.')); } } catch (ex) { // do nothing } } onNewArchive() { this.loadBinaryMo(); } async updateToLatestVersion() { this.gainsightService.triggerEvent(PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.EVENTS.APPLICATION_PROPERTIES, { component: PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.COMPONENTS.APPLICATION_PROPERTIES, action: PRODUCT_EXPERIENCE_ECOSYSTEM.APPLICATIONS.ACTIONS.UPDATE_AVAILABLE, url: this.CURRENT_LOCATION }); try { const translatedBody = this.translate.instant(gettext(`You're using the version {{ currentVersion }} of the {{ packageName }} package, the latest version available is {{ latestVersion }}, do you want to update? You can always revert to a previous version in the Activity log panel.`), { currentVersion: this.application.manifest?.version, latestVersion: this.blueprintApplicationVersion, packageName: this.sourcePackage.name }); await this.modalService.confirm(gettext('Update application'), translatedBody, 'warning', { ok: gettext('Update'), cancel: gettext('Cancel') }); } catch { // modal canceled return; } try { await this.bsModalService.show(UpdateApplicationModalComponent, { ariaDescribedby: 'modal-body', ariaLabelledBy: 'modal-title', class: 'modal-sm', ignoreBackdropClick: true, initialState: { sourcePackage: this.sourcePackage, application: this.application } }).content.result; } catch { // modal canceled return; } await this.refresh(); this.alertService.success(gettext('Application updated.')); } getDescription(application) { if (!application) { return; } return application.description || application.manifest?.description; } async confirmSubscriptionChange(initialState) { await this.bsModalService.show(SubscriptionModalComponent, { class: 'modal-sm', ariaDescribedby: 'modal-body', ariaLabelledBy: 'modal-title', initialState, ignoreBackdropClick: true }).content.result; } async resolveSourcePackageDetails() { try { this.sourcePackage = (await this.getPackage(this.application.manifest?.source)).data; if (this.sourcePackage) { this.packageProperties.push({ label: gettext('Source package'), value: this.sourcePackage.name, type: 'link', action: () => this.router.navigateByUrl(ViewContext.Extension.replace(':id', this.sourcePackage.id.toString())) }); } } catch { this.alertService.warning(gettext('Unable to resolve source package.')); return; } await this.extractVersionInformation(this.application); } initForm() { this.formGroup = this.formBuilder.group({ id: [{ value: '' }], name: [Validators.required, Validators.maxLength(120)], key: [Validators.required, Validators.maxLength(120)], contextPath: [Validators.required, Validators.maxLength(120)], description: ['', Validators.maxLength(200)], username: [Validators.required], password: [Validators.required], externalUrl: [Validators.required], icon: [undefined, [Validators.minLength(1)]] }); if (!this.hasAdminPermissions) { this.formGroup.disable(); } } isActivityLogSupportedByApp(app) { return (this.ecosystemService.isOwner(app) && app.type !== ApplicationType.MICROSERVICE && app.type !== ApplicationType.EXTERNAL); } setBreadcrumbConfig() { this.breadcrumbConfig = { icon: this.isMicroservice ? 'microchip' : this.isFeature ? 'tab' : 'c8y-modules', label: this.isMicroservice ? gettext('Microservices') : this.isFeature ? gettext('Features') : gettext('Applications'), path: this.isMicroservice ? '/ecosystem/microservice/microservices' : this.isFeature ? 'ecosystem/application/features' : 'ecosystem/application/applications' }; } async loadBinaryMo() { this.binaryMo = (await this.inventoryService.detail(this.application.activeVersionId)).data; } async extractVersionInformation(application) { if (!application.manifest.isPackage && !application.manifest.source) { return; } const blueprintApplicationId = application.manifest?.source; const currentVersion = application.manifest?.version; try { const { data: blueprintApplicationVersions } = await this.applicationService.listVersions(blueprintApplicationId); const blueprintLatestVersion = this.ecosystemService.getApplicationVersionObjectByTag(blueprintApplicationVersions, 'latest'); this.blueprintApplicationVersion = blueprintLatestVersion