@c8y/ngx-components
Version:
Angular modules for Cumulocity IoT applications
640 lines (636 loc) • 302 kB
JavaScript
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