UNPKG

@c8y/ngx-components

Version:

Angular modules for Cumulocity IoT applications

855 lines 128 kB
import { EventEmitter, Injectable } from '@angular/core'; import { ApplicationAvailability, ApplicationService, ApplicationType, InventoryService, TenantService } from '@c8y/client'; import { AlertService, AppStateService, gettext, HumanizeAppNamePipe, ModalService, PackageType, PluginsService, Status, WizardModalService, ZipService } from '@c8y/ngx-components'; import { TranslateService } from '@ngx-translate/core'; import { saveAs } from 'file-saver'; import { cloneDeep, get, groupBy, kebabCase, pick, uniqBy, omit, isUndefined } from 'lodash-es'; import { BehaviorSubject, defer } from 'rxjs'; import { debounceTime, take, map, filter, shareReplay } from 'rxjs/operators'; import { gt, coerce, satisfies } from 'semver'; import { EcosystemError } from './ecosystem-error'; import { APP_STATE, ERROR_MESSAGES } from './ecosystem.constants'; import { EcosystemWizards, ERROR_TYPE } from './ecosystem.model'; import * as i0 from "@angular/core"; import * as i1 from "@c8y/ngx-components"; import * as i2 from "@ngx-translate/core"; import * as i3 from "@c8y/client"; const CUMULOCITY_JSON = 'cumulocity.json'; const MICROSERVICE_NAME_MAX_LENGTH = 23; export class EcosystemService { constructor(modal, alertService, humanizeAppName, translateService, applicationService, appStateService, zipService, tenantService, inventoryService, wizardModalService, pluginService) { this.modal = modal; this.alertService = alertService; this.humanizeAppName = humanizeAppName; this.translateService = translateService; this.applicationService = applicationService; this.appStateService = appStateService; this.zipService = zipService; this.tenantService = tenantService; this.inventoryService = inventoryService; this.wizardModalService = wizardModalService; this.pluginService = pluginService; this.appDeleted = new EventEmitter(); this.progress = new BehaviorSubject(null); this.appsGroupedByContextPath$ = defer(() => this.getWebApplications()).pipe(map(webApps => groupBy(webApps, 'contextPath')), shareReplay({ bufferSize: 1, refCount: true })); } getUniqueAppConfig(srcApp, existingApps) { let app = { name: srcApp.name, key: srcApp.key, contextPath: srcApp.contextPath }; for (let retryNo = 0; retryNo < 9;) { if (this.checkIfAppNameKeyPathExists(existingApps, app, retryNo)) { retryNo++; app = { name: [srcApp.name, retryNo].join('-'), key: [srcApp.key, retryNo].join('-'), contextPath: [srcApp.contextPath, retryNo].join('-') }; } else { return app; } } return app; } /** * Verify versions compatibility for blueprints. If a blueprint version * is not compatible, a warning is shown. * * @param blueprint The blueprint to install. * @returns true if the installation can continue or false if it should be aborted. */ async verifyBlueprintVersionsCompatibility(blueprint) { const api = await this.getPlatformVersion(); if (blueprint.versioningMatrix) { try { const pluginApiVersion = blueprint.versioningMatrix[blueprint.version].api; if (!satisfies(api, pluginApiVersion)) { return await this.showModal(blueprint, false, api); } } catch { return await this.showModal(blueprint, false, api); } } return true; } /** * Verify versions compatibility for plugins. In case a version does not exist in the * versioningMatrix we don't do anything due to backward compatibility. If a plugin version * is not compatible, a warning is shown. * * @param pluginsToInstall The list of plugins to install. * @returns true if the installation can continue or false if it should be aborted. */ async verifyPluginVersionsCompatibility(pluginsToInstall, app) { const api = await this.getPlatformVersion(); const sdk = app.manifest?.webSdkVersion; for (const plugin of pluginsToInstall) { if (plugin.versioningMatrix) { try { const pluginSdkVersion = plugin.versioningMatrix[plugin.version].sdk; const pluginApiVersion = plugin.versioningMatrix[plugin.version].api; if (!satisfies(sdk, pluginSdkVersion) || !satisfies(api, pluginApiVersion)) { return await this.showModal(plugin, true, api, sdk); } } catch { return await this.showModal(plugin, false, api, sdk); } } } return true; } /** * Community plugins need to verify the license agreement. If a package is a community * package, the license is shown. * * @param pluginsToInstall The list of plugins to install. * @returns true if the installation can continue. */ async verifyLicenses(pluginsToInstall) { let _resolve; const result = new Promise(resolve => { _resolve = resolve; }); pluginsToInstall = pluginsToInstall.filter(plugin => plugin.type !== PackageType.CUSTOM && plugin.type !== PackageType.OFFICIAL); if (pluginsToInstall.length === 0) { return Promise.resolve(true); } const initialState = { id: EcosystemWizards.LICENSE_CONFIRM, componentInitialState: { pluginsToInstall } }; const modalOptions = { initialState }; const wizard = this.wizardModalService.show(modalOptions); wizard.content.onClose.subscribe(confirmed => { _resolve(confirmed); }); return result; } /** * If the plugin is archived, a warning should be shown. * * @param pluginsToInstall The list of plugins to install. * @returns true if the installation can continue. */ async verifyArchived(pluginsToInstall) { let _resolve; const result = new Promise(resolve => { _resolve = resolve; }); pluginsToInstall = pluginsToInstall.filter(plugin => plugin.type === PackageType.ARCHIVED); if (pluginsToInstall.length === 0) { return Promise.resolve(true); } const initialState = { id: EcosystemWizards.ARCHIVED_CONFIRM }; const modalOptions = { initialState }; const wizard = this.wizardModalService.show(modalOptions); wizard.content.onClose.subscribe(confirmed => { _resolve(confirmed); }); return result; } /** * @description * Compares currently deployed application version with application version tagged as "latest" * * @param {string} currentApplicationVersion Deployed application version * @param {object} latestApp Latest application version object * * @returns {boolean} Returns true if latest version is greater than current, otherwise false */ shouldUpgradePackage(currentApplicationVersion, latestApp) { const latestApplicationVersion = latestApp?.version; if (!latestApplicationVersion || !currentApplicationVersion) { return false; } return gt(coerce(latestApplicationVersion), coerce(currentApplicationVersion)); } /** * @description * Gets an object that contains searched tag * * @param {array} applicationVersions Array with all available versions * @param {string} tagName Searched tag * * @returns {object} Returns an object with searched tag */ getApplicationVersionObjectByTag(applicationVersions, tagName) { return applicationVersions?.find(element => element.tags.includes(tagName)); } async getApplication(appId) { return (await this.applicationService.detail(appId)).data; } getApplications(customFilter = {}) { const filter = { pageSize: 2000, withTotalPages: true }; Object.assign(filter, customFilter); const currentTenant = this.appStateService.currentTenant.value; return this.applicationService.listByTenant(currentTenant.name, filter); } async getMicroservices() { const apps = (await this.getApplications()).data; const microservices = apps.filter(app => this.isMicroservice(app)); return microservices.sort((a, b) => a.name.localeCompare(b.name)); } async getWebApplications(customFilter = {}) { const apps = (await this.getApplications(customFilter)).data; const webApps = apps.filter(app => this.isApplication(app)); return webApps.sort((a, b) => a.name.localeCompare(b.name)); } async getFeatureApplications(customFilter = {}) { const apps = (await this.getApplications(customFilter)).data; const webApps = apps.filter(app => this.isFeature(app)); return webApps.sort((a, b) => a.name.localeCompare(b.name)); } async getPackageApplications(customFilter = {}) { const filterCallback = app => this.isPackage(app); return this.getApplicationsFiltered(customFilter, filterCallback); } async getHostedAndPackageApplications(customFilter = {}) { const filterCallback = app => this.isPackage(app) || this.isApplication(app); return this.getApplicationsFiltered(customFilter, filterCallback); } async getApplicationsFiltered(customFilter = {}, filterCallback = (app) => !!app) { const filter = Object.assign({}, customFilter); const sharedFilter = Object.assign({ availability: ApplicationAvailability.SHARED, type: 'HOSTED', pageSize: 2000 }, customFilter); const [{ data: apps }, { data: shared }] = await Promise.all([ this.getApplications(filter), this.applicationService.list(sharedFilter) ]); const webApps = [...apps, ...shared].filter(filterCallback); // an app could be subscribed to a tenant, but also have it's availability set to SHARED, in that case it would occur twice. const uniqWebApps = uniqBy(webApps, (app) => app.id); return uniqWebApps.sort((a, b) => a.name.localeCompare(b.name)); } async isMicroserviceHostingAllowed() { const { data: apps } = await this.applicationService.listByName('feature-microservice-hosting'); return !!apps.filter(app => app.owner?.tenant?.id === 'management').length; } canOpenAppInBrowser(app) { const isNotAFeature = !this.isFeature(app); const hasProperType = [ApplicationType.HOSTED, ApplicationType.EXTERNAL].includes(app.type); const isNotPackage = !this.isPackage(app); return isNotAFeature && hasProperType && isNotPackage; } openApp(app) { window.open(this.applicationService.getHref(app), '_blank', 'noopener,noreferrer'); } async canDeleteApp(app) { return (this.isOwner(app) && (!this.isCurrentApp(app) || (await this.hasSubscribedAppParent(app)))); } isOwner(app) { const currentTenant = this.appStateService.currentTenant.value; const appOwner = get(app, 'owner.tenant.id'); return currentTenant?.name === appOwner; } isFeature(app) { return !!app.name.match(/feature-/); } isMicroservice(app) { return app.type === 'MICROSERVICE'; } isExternal(app) { return app.type === 'EXTERNAL'; } isPackage(app) { return app.manifest?.isPackage === true; } isPlugin(app) { return app.manifest?.package === 'plugin'; } cancelAppCreation(app) { if (this.xhr) { this.xhr.abort(); } if (app) { this.applicationService.delete(app); } } updateUploadProgress(event) { if (event.lengthComputable) { const currentProgress = this.progress.value; this.progress.next(currentProgress + (event.loaded / event.total) * (95 - currentProgress)); } } setAppActiveVersion(app, activeVersionId) { return this.applicationService.update({ id: app.id, activeVersionId }); } setPackageVersionTag(app, version, tags) { return this.applicationService.setPackageVersionTag(app, version, tags); } deletePackageVersion(app, params) { return this.applicationService.deleteVersionPackage(app, params); } getHumanizedAppName(app) { return this.humanizeAppName.transform(app.name).pipe(debounceTime(250), take(1)).toPromise(); } createConfig(app, formGroupValue) { const { id, type, availability } = app; let config = pick(formGroupValue, ['name', 'key', 'contextPath']); config = { ...config, isSetup: true, id, type, availability }; return config; } async listArchives(appId) { const filter = { pageSize: 100 }; return (await this.applicationService.binary(appId).list(filter)).data; } async deleteArchive(archive, app) { const humanizedArchiveName = await this.getHumanizedAppName(archive); try { await this.modal.confirm(gettext('Delete archive'), this.translateService.instant(gettext(`You are about to delete archive "{{ humanizedArchiveName }}". Do you want to proceed?`), { humanizedArchiveName }), Status.DANGER, { ok: gettext('Delete'), cancel: gettext('Cancel') }); await this.applicationService.binary(app).delete(archive.id); this.alertService.success(gettext('Archive deleted.')); } catch (ex) { if (ex) { this.alertService.danger(get(ex, 'data.message'), ex.data); } throw new Error('Cancelled'); } } async getArchiveManagedObject(binaryId) { return (await this.inventoryService.detail(binaryId)).data; } async downloadArchive(app, archive) { try { const binary = await this.getBinary(app, archive); const fileBinary = new Blob([binary], { type: 'application/x-zip-compressed' }); saveAs(fileBinary, archive.name); } catch (e) { // empty } } async updateApp(app, deleteOnFailure = false) { try { return await this.applicationService.update(app); } catch (ex) { this.alertError(ex); if (deleteOnFailure) { await this.applicationService.delete(app.id); throw new EcosystemError(ERROR_TYPE.APPLICATION_CREATION_FAILED); } } } async deleteApp(app, silent = false) { const humanizedAppName = await this.getHumanizedAppName(app); if (!silent) { await this.modal.confirm(gettext('Delete application'), this.translateService.instant(gettext(`You are about to delete application "{{ humanizedAppName }}". Do you want to proceed?`), { humanizedAppName }), Status.DANGER, { ok: gettext('Delete'), cancel: gettext('Cancel') }); } await this.applicationService.delete(app.id); if (!silent) { this.alertService.success(gettext('Application deleted.')); } this.appDeleted.emit(app); } async checkIfSubscribed(app) { const currentTenant = await this.tenantService.current(); const subscribedApps = currentTenant.data.applications.references; return subscribedApps.some(application => application.application.id === app.id); } async subscribeApp(app) { const currentTenant = this.appStateService.currentTenant.value; try { await this.tenantService.subscribeApplication(currentTenant, app); this.alertService.success(gettext('Successfully subscribed to application.')); } catch (ex) { this.alertError(ex); } } async unsubscribeApp(app) { const currentTenant = this.appStateService.currentTenant.value; try { await this.tenantService.unsubscribeApplication(currentTenant, app); this.alertService.success(gettext('Successfully unsubscribed from application.')); } catch (ex) { this.alertError(ex); } } async isValidAppType(archive, appType) { try { const currentType = await this.getAppType(archive); if (currentType !== appType) { throw new EcosystemError(ERROR_TYPE.TYPE_VALIDATION); } else { this.progress.next(this.progress.value + 10); return true; } } catch (ex) { throw new EcosystemError(ERROR_TYPE.TYPE_VALIDATION); } } async uploadArchiveToApp(archive, app, isNewVersion = false) { let uploadOverrides; if (isNewVersion) { uploadOverrides = await this.getUploadOverrides(archive, app); } const binaryService = this.applicationService.binary(app); this.xhr = binaryService.uploadWithProgressXhr(archive, this.updateUploadProgress.bind(this), '', uploadOverrides); const binaryMo = await binaryService.getXMLHttpResponse(this.xhr); // TODO commented it due to: https://cumulocity.atlassian.net/browse/MTM-48553 // Add it back when BE fixes issues with activeVersion. // if (isNewVersion) { // return await this.getApplication(app); // } const notInitialPackage = app.applicationVersions?.length > 0; if (notInitialPackage) { return (await this.applicationService.update({ id: app.id })).data; } return (await this.setAppActiveVersion(app, (binaryMo.binaryId || binaryMo.id))).data; } async validateArchiveToAppCompatibility(archive, app) { const appType = await this.getAppType(archive); if (appType !== app.type) { throw new EcosystemError(ERROR_TYPE.INVALID_APPLICATION); } else { this.progress.next(this.progress.value + 10); } if (this.isMicroservice(app)) { return; } const manifest = await this.getCumulocityJson(archive); // A user can upload an app without a Cumulocity JSON file (e.g. a react app). // This is allowed and should not trigger a validation error. if (!manifest) { return; } await this.validatePackageKeyAndContextPath(manifest, app); } async getCumulocityJson(archive) { try { const c8yManifest = await this.getCumulocityJson$(archive).toPromise(); return c8yManifest; } catch (ex) { return null; } } async createAppForArchive(archive, isPackageTypeArchive = false) { let isPackage = false; const appType = await this.getAppType(archive); let appModel = {}; const supportedAppTypes = [ApplicationType.HOSTED, ApplicationType.MICROSERVICE]; if (supportedAppTypes.includes(appType)) { try { appModel = await this.getCumulocityJson$(archive).toPromise(); isPackage = appModel.isPackage; } catch (e) { // do nothing, we allow having HOSTED applications without the manifest file } } const name = this.getBaseNameFromArchiveOrAppModel(archive, appType, appModel); const clearedName = this.removeForbiddenCharacters(name); const key = this.getAppKey(appModel, clearedName); const contextPath = this.getContextPath(appModel, name); const appToSave = { type: appType, name, key, contextPath }; if (isPackageTypeArchive && !isPackage) { throw new EcosystemError(ERROR_TYPE.INVALID_PACKAGE); } else if (!isPackageTypeArchive && isPackage) { throw new EcosystemError(ERROR_TYPE.INVALID_APPLICATION); } else if (this.isNameLengthExceeded(name, appType)) { const error = new Error(); error.name = ERROR_TYPE.MICROSERVICE_NAME_TOO_LONG; error.message = this.translateService.instant(ERROR_MESSAGES[error.name], { name, maxChars: MICROSERVICE_NAME_MAX_LENGTH }); throw error; } return (await this.applicationService.create({ ...appToSave, manifest: { isPackage, ...(appModel?.package && { package: appModel.package }) } })).data; } async reactivateArchive(app) { try { await this.applicationService.reactivateArchive(app.id); this.alertService.success(gettext('Application reactivated.')); } catch (ex) { this.alertError(ex); } } async removeOldestArchive(app, archives) { try { await this.modal.confirm(gettext('Delete oldest archive and continue'), gettext('Up to 6 archives can be saved in the platform. If you upload a new archive, the oldest archive that is not active will be deleted. Do you want to proceed?'), Status.INFO, { ok: gettext('Delete and continue') }); const archiveToDelete = archives[archives.length - 2]; await this.applicationService.binary(app).delete(archiveToDelete.id); this.alertService.success(gettext('Archive deleted.')); } catch (ex) { if (ex) { this.alertError(ex); } else { return Promise.reject('cancelled'); } } } async deployApp(selectedPackage, formGroupValue, model) { // Create new app config const config = this.createConfig(selectedPackage, formGroupValue); const requestedVersion = model.selected.version; let cleanManifest; try { const manifest = await this.applicationService.getAppManifest(selectedPackage, requestedVersion); cleanManifest = omit(manifest, ['name', 'contextPath', 'key']); } catch (ex) { throw new EcosystemError(ERROR_TYPE.VERSION_NOT_FOUND); } config.isSetup = true; config.manifest = cleanManifest; config.availability = ApplicationAvailability.PRIVATE; config.manifest.isPackage = false; config.manifest.source = selectedPackage.id; config.manifest.package = 'blueprint'; // because of a issue with SHARED availability we always should check again // if the app not exist already const allExistingApps = await this.getHostedAndPackageApplications(); const doesAppKeyOrContextPathExist = this.checkIfAppNameKeyPathExists(allExistingApps, config); if (doesAppKeyOrContextPathExist) { throw new EcosystemError(ERROR_TYPE.ALREADY_EXIST); } // Create new app const newApp = (await this.applicationService.create(config)).data; try { // Binary upload can fail if SHARED package // in this case, catch error and fall back to // clone API. await this.uploadBinaryFromOtherPackage(selectedPackage, newApp, model.selected.binaryId, requestedVersion); } catch (error) { if (error?.res?.status === 404) { await this.fallbackToClone(newApp, selectedPackage, requestedVersion); } } // update the icon if a custom one is selected if (formGroupValue.icon) { await this.applicationService.update({ id: newApp.id, config: { icon: formGroupValue.icon } }); } return newApp; } async fallbackToClone(application, selectedPackage, requestedVersion) { let wasSuccess = true; let clonedPkg; try { clonedPkg = (await this.applicationService.clone(selectedPackage, requestedVersion)).data; await this.uploadBinaryFromOtherPackage(selectedPackage, application, clonedPkg.activeVersionId, requestedVersion, clonedPkg); } catch (error) { this.alertError(error); wasSuccess = false; } finally { await this.deleteApp(clonedPkg, true); } return wasSuccess; } async uploadBinaryFromOtherPackage(selectedPackage, applicationToUploadBinaryTo, binaryId, requestedVersion, useBinariesFrom) { const { data: binaryDetails } = await this.inventoryService.detail(binaryId); // Get binary from specific package version const binary = await this.getBinary(useBinariesFrom || selectedPackage, { id: binaryId }); // Create zip const fileBinary = new Blob([binary], { type: binaryDetails.contentType }); const file = new File([fileBinary], binaryDetails.name, { type: binaryDetails.contentType }); // Upload binary to new app await this.uploadArchiveToApp(file, applicationToUploadBinaryTo); // update the app manifest await this.updateAppManifest(applicationToUploadBinaryTo, selectedPackage, requestedVersion); } getAppState(app) { if (!this.isOwner(app)) { return APP_STATE.SUBSCRIBED; } else if (this.isUnpacked(app)) { return APP_STATE.UNPACKED; } else if (app.type === ApplicationType.EXTERNAL) { return APP_STATE.EXTERNAL; } return APP_STATE.CUSTOM; } getPackageContentState(app) { if (!this.isPackage(app)) { return; } if (this.isPackageBlueprint(app)) { return APP_STATE.PACKAGE_BLUEPRINT; } if (this.isPluginsPackage(app)) { return APP_STATE.PACKAGE_PLUGIN; } return APP_STATE.PACKAGE_UNKNOWN; } isPackageBlueprint(app) { return this.isPackage(app) && app.manifest.package === 'blueprint'; } isPluginsPackage(app) { return this.isPackage(app) && app.manifest.package === 'plugin'; } isUnpacked(app) { return !!app.manifest?.source; } hasExports(app) { return !!app.manifest?.exports?.length; } isApplication(app) { return (app.type !== ApplicationType.MICROSERVICE && !this.isFeature(app) && !this.isPackage(app)); } isCustomMicroservice(app) { return this.isOwner(app) && app.type === ApplicationType.MICROSERVICE; } async getBinary(app, archive) { let binary; try { const res = await this.applicationService.binary(app).downloadArchive(archive.id); binary = await res.arrayBuffer(); } catch (ex) { const msg = gettext('Could not get the binary.'); this.alertService.danger(msg); } return binary; } async isOverwrittenByCustomApp(app) { return !this.isOwner(app) && (await this.hasSubscribedAppParent(app)); } async hasSubscribedAppParent(app) { const appsGroupedByContextPath = await this.appsGroupedByContextPath$.pipe(take(1)).toPromise(); return app.contextPath && appsGroupedByContextPath[app.contextPath]?.length === 2; } /** * @deprecated */ setAvailabilityToPrivateIfNotSetAlready(app) { app.availability = ApplicationAvailability.PRIVATE; return app; } /** * Shows an error dialog. * @param error Either a server error or an internal [[EcosystemError]]. */ alertError(error) { if (error instanceof EcosystemError) { this.alertService.danger(error.message); } else { this.alertService.addServerFailure(error); } } async validatePackageKeyAndContextPath(manifest, app) { const contextPath = get(manifest, 'contextPath'); const appKey = get(manifest, 'key'); if (contextPath !== app.contextPath || appKey !== app.key) { throw new EcosystemError(ERROR_TYPE.KEY_OR_CONTEXT_PATH_MISMATCH); } } filterContainString(name, filterTerm) { const term = filterTerm.toLowerCase().trim(); return name && name.toLowerCase().indexOf(term) > -1; } async updateAppManifest(application, selectedPackage, requestedVersion) { const { id } = application; const cleanedApp = this.removeAppProperties(application); let manifest = selectedPackage.manifest || {}; if (requestedVersion) { try { const versionedAppManifest = await this.applicationService.getAppManifest(selectedPackage, requestedVersion); manifest = versionedAppManifest; } catch (ex) { throw new EcosystemError(ERROR_TYPE.VERSION_NOT_FOUND); } } cleanedApp.manifest = manifest; cleanedApp.manifest.isPackage = false; cleanedApp.manifest.source = selectedPackage.id; return await this.applicationService .binary(id) .updateFiles([{ path: CUMULOCITY_JSON, contents: JSON.stringify(cleanedApp) }]); } /** * Creates object containing properties for filtering applications on lists. * * @param {object} app Application to create filter properties from. * @returns {object} Properties to filter by applications list. */ getAppFilterProps(app) { return { type: this.pluginService.getPackageType(app), availability: this.getAppState(app)?.label, content: this.getPackageContentState(app)?.label }; } checkIfAppNameKeyPathExists(existingApps, app, retryNo) { if (isUndefined(retryNo)) { return existingApps.find(existingApp => existingApp.name === app.name || existingApp.key === app.key || existingApp.contextPath === app.contextPath); } return existingApps.find(existingApp => existingApp.name === app.name || existingApp.key === app.key || existingApp.contextPath === app.contextPath || existingApp.name === [app.name, retryNo].join('-') || existingApp.key === [app.key, retryNo].join('-') || existingApp.contextPath === [app.contextPath, retryNo].join('-')); } async showModal(packageType, isPlugin, apiVersion, sdkVersion) { try { const messages = { plugin: { title: gettext('Plugin installation'), body: sdkVersion ? gettext('The current version of the plugin that you are trying to install does not satisfy the requirements for the following API version "{{ apiVersion }}" or SDK version "{{ sdkVersion }}". Do you want to proceed?') : gettext('The current version of the plugin that you are trying to install does not satisfy the requirements for the following API version "{{ apiVersion }}". Do you want to proceed?') }, blueprint: { title: gettext('Blueprint installation'), body: sdkVersion ? gettext('The current version of the blueprint that you are trying to install does not satisfy the requirements for the following API version "{{ apiVersion }}" or SDK version "{{ sdkVersion }}". Do you want to proceed?') : gettext('The current version of the blueprint that you are trying to install does not satisfy the requirements for the following API version "{{ apiVersion }}". Do you want to proceed?') } }; const entityType = isPlugin ? 'plugin' : 'blueprint'; const selectedMessages = messages[entityType]; const translatedBody = this.translateService.instant(selectedMessages.body, { name: packageType.name, apiVersion, sdkVersion }); await this.modal.confirm(selectedMessages.title, translatedBody, 'warning', { ok: gettext('Continue'), cancel: gettext('Cancel') }); } catch { // modal canceled return false; } return true; } async getPlatformVersion() { return await this.appStateService.state$ .pipe(take(1), map(state => state?.versions?.backend), filter(backendVersion => !!backendVersion)) .toPromise(); } getAppKey(appModel, name) { let key = appModel?.key; if (!key) { key = `${kebabCase(name)}-key`; } return key; } getContextPath(appModel, name) { return appModel?.contextPath || name.toLowerCase(); } removeForbiddenCharacters(str) { return str.replace(/[^a-zA-Z0-9-_]/g, ''); } isCurrentApp(app) { const currentApp = this.appStateService.state.app; return currentApp.contextPath === app.contextPath; } getCumulocityJson$(archive) { return this.zipService.getJsonData(archive, { filename: CUMULOCITY_JSON }); } getAppType(archive) { return this.getCumulocityJson$(archive) .toPromise() .then(data => get(data, 'type') || (get(data, 'apiVersion') ? ApplicationType.MICROSERVICE : ApplicationType.HOSTED)) .catch(() => ApplicationType.HOSTED); } getBaseNameFromArchiveOrAppModel(archive, appType, appModel) { let baseName = appModel?.name || archive.name.replace(/\.zip$/i, ''); if (appType === 'MICROSERVICE') { baseName = this.removeVersionFromName(baseName); } return baseName; } removeAppProperties(app) { const tempApp = cloneDeep(app); const propertiesToRemove = ['id', 'owner', 'activeVersionId', 'self', 'type']; propertiesToRemove.forEach(prop => delete tempApp[prop]); return tempApp; } async getUploadOverrides(archive, app) { const { version } = await this.getCumulocityJson$(archive).toPromise(); const isInitialPackage = app.applicationVersions?.length === 0; return { listUrl: 'versions', headers: { Accept: 'application/vnd.com.nsn.cumulocity.applicationVersion+json;charset=UTF-8;ver=0.9' }, bodyFileProperty: 'applicationBinary', requestBody: { applicationVersion: { version, ...(isInitialPackage && { tags: ['latest'] }) } } }; } removeVersionFromName(name) { const versionRegExp = /-\d+\.\d+\.\d+(\.\d+)?(-\d+)?(.*)$/; return name.replace(versionRegExp, ''); } isNameLengthExceeded(name, appType) { return name.length > MICROSERVICE_NAME_MAX_LENGTH && appType === ApplicationType.MICROSERVICE; } static { this.ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: EcosystemService, deps: [{ token: i1.ModalService }, { token: i1.AlertService }, { token: i1.HumanizeAppNamePipe }, { token: i2.TranslateService }, { token: i3.ApplicationService }, { token: i1.AppStateService }, { token: i1.ZipService }, { token: i3.TenantService }, { token: i3.InventoryService }, { token: i1.WizardModalService }, { token: i1.PluginsService }], target: i0.ɵɵFactoryTarget.Injectable }); } static { this.ɵprov = i0.ɵɵngDeclareInjectable({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: EcosystemService, providedIn: 'root' }); } } i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "18.2.13", ngImport: i0, type: EcosystemService, decorators: [{ type: Injectable, args: [{ providedIn: 'root' }] }], ctorParameters: () => [{ type: i1.ModalService }, { type: i1.AlertService }, { type: i1.HumanizeAppNamePipe }, { type: i2.TranslateService }, { type: i3.ApplicationService }, { type: i1.AppStateService }, { type: i1.ZipService }, { type: i3.TenantService }, { type: i3.InventoryService }, { type: i1.WizardModalService }, { type: i1.PluginsService }] }); //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZWNvc3lzdGVtLnNlcnZpY2UuanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi8uLi8uLi8uLi9lY29zeXN0ZW0vc2hhcmVkL2Vjb3N5c3RlbS5zZXJ2aWNlLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLE9BQU8sRUFBRSxZQUFZLEVBQUUsVUFBVSxFQUFFLE1BQU0sZUFBZSxDQUFDO0FBRXpELE9BQU8sRUFDTCx1QkFBdUIsRUFDdkIsa0JBQWtCLEVBQ2xCLGVBQWUsRUFXZixnQkFBZ0IsRUFJaEIsYUFBYSxFQUNkLE1BQU0sYUFBYSxDQUFDO0FBQ3JCLE9BQU8sRUFDTCxZQUFZLEVBRVosZUFBZSxFQUNmLE9BQU8sRUFDUCxtQkFBbUIsRUFDbkIsWUFBWSxFQUNaLFdBQVcsRUFDWCxjQUFjLEVBQ2QsTUFBTSxFQUNOLGtCQUFrQixFQUNsQixVQUFVLEVBQ1gsTUFBTSxxQkFBcUIsQ0FBQztBQUM3QixPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUN2RCxPQUFPLEVBQUUsTUFBTSxFQUFFLE1BQU0sWUFBWSxDQUFDO0FBQ3BDLE9BQU8sRUFBRSxTQUFTLEVBQUUsR0FBRyxFQUFFLE9BQU8sRUFBRSxTQUFTLEVBQUUsSUFBSSxFQUFFLE1BQU0sRUFBRSxJQUFJLEVBQUUsV0FBVyxFQUFFLE1BQU0sV0FBVyxDQUFDO0FBQ2hHLE9BQU8sRUFBRSxlQUFlLEVBQWMsS0FBSyxFQUFFLE1BQU0sTUFBTSxDQUFDO0FBQzFELE9BQU8sRUFBRSxZQUFZLEVBQUUsSUFBSSxFQUFFLEdBQUcsRUFBRSxNQUFNLEVBQUUsV0FBVyxFQUFFLE1BQU0sZ0JBQWdCLENBQUM7QUFDOUUsT0FBTyxFQUFFLEVBQUUsRUFBRSxNQUFNLEVBQUUsU0FBUyxFQUFFLE1BQU0sUUFBUSxDQUFDO0FBQy9DLE9BQU8sRUFBRSxjQUFjLEVBQUUsTUFBTSxtQkFBbUIsQ0FBQztBQUNuRCxPQUFPLEVBQW9CLFNBQVMsRUFBRSxjQUFjLEVBQUUsTUFBTSx1QkFBdUIsQ0FBQztBQUNwRixPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsVUFBVSxFQUE2QixNQUFNLG1CQUFtQixDQUFDOzs7OztBQUc1RixNQUFNLGVBQWUsR0FBRyxpQkFBaUIsQ0FBQztBQUMxQyxNQUFNLDRCQUE0QixHQUFHLEVBQUUsQ0FBQztBQUt4QyxNQUFNLE9BQU8sZ0JBQWdCO0lBTTNCLFlBQ1UsS0FBbUIsRUFDbkIsWUFBMEIsRUFDMUIsZUFBb0MsRUFDcEMsZ0JBQWtDLEVBQ2xDLGtCQUFzQyxFQUN0QyxlQUFnQyxFQUNoQyxVQUFzQixFQUN0QixhQUE0QixFQUM1QixnQkFBa0MsRUFDbEMsa0JBQXNDLEVBQ3RDLGFBQTZCO1FBVjdCLFVBQUssR0FBTCxLQUFLLENBQWM7UUFDbkIsaUJBQVksR0FBWixZQUFZLENBQWM7UUFDMUIsb0JBQWUsR0FBZixlQUFlLENBQXFCO1FBQ3BDLHFCQUFnQixHQUFoQixnQkFBZ0IsQ0FBa0I7UUFDbEMsdUJBQWtCLEdBQWxCLGtCQUFrQixDQUFvQjtRQUN0QyxvQkFBZSxHQUFmLGVBQWUsQ0FBaUI7UUFDaEMsZUFBVSxHQUFWLFVBQVUsQ0FBWTtRQUN0QixrQkFBYSxHQUFiLGFBQWEsQ0FBZTtRQUM1QixxQkFBZ0IsR0FBaEIsZ0JBQWdCLENBQWtCO1FBQ2xDLHVCQUFrQixHQUFsQixrQkFBa0IsQ0FBb0I7UUFDdEMsa0JBQWEsR0FBYixhQUFhLENBQWdCO1FBaEJ2QyxlQUFVLEdBQUcsSUFBSSxZQUFZLEVBQWdCLENBQUM7UUFDOUMsYUFBUSxHQUE0QixJQUFJLGVBQWUsQ0FBUyxJQUFJLENBQUMsQ0FBQztRQWlCcEUsSUFBSSxDQUFDLHlCQUF5QixHQUFHLEtBQUssQ0FBQyxHQUFHLEVBQUUsQ0FBQyxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQyxDQUFDLElBQUksQ0FDMUUsR0FBRyxDQUFDLE9BQU8sQ0FBQyxFQUFFLENBQUMsT0FBTyxDQUFDLE9BQU8sRUFBRSxhQUFhLENBQUMsQ0FBQyxFQUMvQyxXQUFXLENBQUMsRUFBRSxVQUFVLEVBQUUsQ0FBQyxFQUFFLFFBQVEsRUFBRSxJQUFJLEVBQUUsQ0FBQyxDQUMvQyxDQUFDO0lBQ0osQ0FBQztJQUVELGtCQUFrQixDQUFDLE1BQW9CLEVBQUUsWUFBNEI7UUFDbkUsSUFBSSxHQUFHLEdBQWlCO1lBQ3RCLElBQUksRUFBRSxNQUFNLENBQUMsSUFBSTtZQUNqQixHQUFHLEVBQUUsTUFBTSxDQUFDLEdBQUc7WUFDZixXQUFXLEVBQUUsTUFBTSxDQUFDLFdBQVc7U0FDaEMsQ0FBQztRQUNGLEtBQUssSUFBSSxPQUFPLEdBQUcsQ0FBQyxFQUFFLE9BQU8sR0FBRyxDQUFDLEdBQUksQ0FBQztZQUNwQyxJQUFJLElBQUksQ0FBQywyQkFBMkIsQ0FBQyxZQUFZLEVBQUUsR0FBRyxFQUFFLE9BQU8sQ0FBQyxFQUFFLENBQUM7Z0JBQ2pFLE9BQU8sRUFBRSxDQUFDO2dCQUNWLEdBQUcsR0FBRztvQkFDSixJQUFJLEVBQUUsQ0FBQyxNQUFNLENBQUMsSUFBSSxFQUFFLE9BQU8sQ0FBQyxDQUFDLElBQUksQ0FBQyxHQUFHLENBQUM7b0JBQ3RDLEdBQUcsRUFBRSxDQUFDLE1BQU0sQ0FBQyxHQUFHLEVBQUUsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUFDLEdBQUcsQ0FBQztvQkFDcEMsV0FBVyxFQUFFLENBQUMsTUFBTSxDQUFDLFdBQVcsRUFBRSxPQUFPLENBQUMsQ0FBQyxJQUFJLENBQUMsR0FBRyxDQUFDO2lCQUNyRCxDQUFDO1lBQ0osQ0FBQztpQkFBTSxDQUFDO2dCQUNOLE9BQU8sR0FBRyxDQUFDO1lBQ2IsQ0FBQztRQUNILENBQUM7UUFDRCxPQUFPLEdBQUcsQ0FBQztJQUNiLENBQUM7SUFFRDs7Ozs7O09BTUc7SUFDSCxLQUFLLENBQUMsb0NBQW9DLENBQUMsU0FBNkI7UUFDdEUsTUFBTSxHQUFHLEdBQUcsTUFBTSxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUM1QyxJQUFJLFNBQVMsQ0FBQyxnQkFBZ0IsRUFBRSxDQUFDO1lBQy9CLElBQUksQ0FBQztnQkFDSCxNQUFNLGdCQUFnQixHQUFHLFNBQVMsQ0FBQyxnQkFBZ0IsQ0FBQyxTQUFTLENBQUMsT0FBTyxDQUFDLENBQUMsR0FBRyxDQUFDO2dCQUMzRSxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsRUFBRSxnQkFBZ0IsQ0FBQyxFQUFFLENBQUM7b0JBQ3RDLE9BQU8sTUFBTSxJQUFJLENBQUMsU0FBUyxDQUFDLFNBQVMsRUFBRSxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUM7Z0JBQ3JELENBQUM7WUFDSCxDQUFDO1lBQUMsTUFBTSxDQUFDO2dCQUNQLE9BQU8sTUFBTSxJQUFJLENBQUMsU0FBUyxDQUFDLFNBQVMsRUFBRSxLQUFLLEVBQUUsR0FBRyxDQUFDLENBQUM7WUFDckQsQ0FBQztRQUNILENBQUM7UUFDRCxPQUFPLElBQUksQ0FBQztJQUNkLENBQUM7SUFFRDs7Ozs7OztPQU9HO0lBQ0gsS0FBSyxDQUFDLGlDQUFpQyxDQUNyQyxnQkFBcUMsRUFDckMsR0FBaUI7UUFFakIsTUFBTSxHQUFHLEdBQUcsTUFBTSxJQUFJLENBQUMsa0JBQWtCLEVBQUUsQ0FBQztRQUM1QyxNQUFNLEdBQUcsR0FBRyxHQUFHLENBQUMsUUFBUSxFQUFFLGFBQWEsQ0FBQztRQUN4QyxLQUFLLE1BQU0sTUFBTSxJQUFJLGdCQUFnQixFQUFFLENBQUM7WUFDdEMsSUFBSSxNQUFNLENBQUMsZ0JBQWdCLEVBQUUsQ0FBQztnQkFDNUIsSUFBSSxDQUFDO29CQUNILE1BQU0sZ0JBQWdCLEdBQUcsTUFBTSxDQUFDLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQyxHQUFHLENBQUM7b0JBQ3JFLE1BQU0sZ0JBQWdCLEdBQUcsTUFBTSxDQUFDLGdCQUFnQixDQUFDLE1BQU0sQ0FBQyxPQUFPLENBQUMsQ0FBQyxHQUFHLENBQUM7b0JBQ3JFLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxFQUFFLGdCQUFnQixDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxFQUFFLGdCQUFnQixDQUFDLEVBQUUsQ0FBQzt3QkFDM0UsT0FBTyxNQUFNLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFLElBQUksRUFBRSxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUM7b0JBQ3RELENBQUM7Z0JBQ0gsQ0FBQztnQkFBQyxNQUFNLENBQUM7b0JBQ1AsT0FBTyxNQUFNLElBQUksQ0FBQyxTQUFTLENBQUMsTUFBTSxFQUFFLEtBQUssRUFBRSxHQUFHLEVBQUUsR0FBRyxDQUFDLENBQUM7Z0JBQ3ZELENBQUM7WUFDSCxDQUFDO1FBQ0gsQ0FBQztRQUVELE9BQU8sSUFBSSxDQUFDO0lBQ2QsQ0FBQztJQUVEOzs7Ozs7T0FNRztJQUNILEtBQUssQ0FBQyxjQUFjLENBQUMsZ0JBQWtEO1FBQ3JFLElBQUksUUFBUSxDQUFDO1FBQ2IsTUFBTSxNQUFNLEdBQXFCLElBQUksT0FBTyxDQUFDLE9BQU8sQ0FBQyxFQUFFO1lBQ3JELFFBQVEsR0FBRyxPQUFPLENBQUM7UUFDckIsQ0FBQyxDQUFDLENBQUM7UUFFSCxnQkFBZ0IsR0FBRyxnQkFBZ0IsQ0FBQyxNQUFNLENBQ3hDLE1BQU0sQ0FBQyxFQUFFLENBQUMsTUFBTSxDQUFDLElBQUksS0FBSyxXQUFXLENBQUMsTUFBTSxJQUFJLE1BQU0sQ0FBQyxJQUFJLEtBQUssV0FBVyxDQUFDLFFBQVEsQ0FDckYsQ0FBQztRQUVGLElBQUksZ0JBQWdCLENBQUMsTUFBTSxLQUFLLENBQUMsRUFBRSxDQUFDO1lBQ2xDLE9BQU8sT0FBTyxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQztRQUMvQixDQUFDO1FBRUQsTUFBTSxZQUFZLEdBQVE7WUFDeEIsRUFBRSxFQUFFLGdCQUFnQixDQUFDLGVBQWU7WUFDcEMscUJBQXFCLEVBQUU7Z0JBQ3JCLGdCQUFnQjthQUNqQjtTQUNGLENBQUM7UUFDRixNQUFNLFlBQVksR0FBRyxFQUFFLFlBQVksRUFBRSxDQUFDO1FBQ3RDLE1BQU0sTUFBTSxHQUFHLElBQUksQ0FBQyxrQkFBa0IsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLENBQUM7UUFFMUQsTUFBTSxDQUFDLE9BQU8sQ0FBQyxPQUFPLENBQUMsU0FBUyxDQUFDLFNBQVMsQ0FBQyxFQUFFO1lBQzNDLFFBQVEsQ0FBQyxTQUFTLENBQUMsQ0FBQztRQUN0QixDQUFDLENBQUMsQ0FBQztRQUVILE9BQU8sTUFBTSxDQUFDO0lBQ2hCLENBQUM7SUFFRDs7Ozs7T0FLRztJQUNILEtBQUssQ0FBQyxjQUFjLENBQUMsZ0JBQWtEO1FBQ3JFLElBQUksUUFBUSxDQUFDO1FBQ2IsTUFBTSxNQUFNLEdBQXFCLElBQUksT0FBTyxDQUFDLE9BQU8sQ0FBQyxFQUFFO1lBQ3JELFFBQVEsR0FBRyxPQUFPLENBQUM7UUFDckIsQ0FBQyxDQUFDLENBQUM7UUFFSCxnQkFBZ0IsR0FBRyxnQkFBZ0IsQ0FBQyxNQUFNLENBQUMsTUFBTSxDQUFDLEVBQUUsQ0FBQyxNQUFNLENBQUMsSUFBSSxLQUFLLFdBQVcsQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUUzRixJQUFJLGdCQUFnQixDQUFDLE1BQU0sS0FBSyxDQUFDLEVBQUUsQ0FBQztZQUNsQyxPQUFPLE9BQU8sQ0FBQyxPQUFPLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDL0IsQ0FBQztRQUVELE1BQU0sWUFBWSxHQUFRO1lBQ3hCLEVBQUUsRUFBRSxnQkFBZ0IsQ0FBQyxnQkFBZ0I7U0FDdEMsQ0FBQztRQUNGLE1BQU0sWUFBWSxHQUFHLEVBQUUsWUFBWSxFQUFFLENBQUM7UUFDdEMsTUFBTSxNQUFNLEdBQUcsSUFBSSxDQUFDLGtCQUFrQixDQUFDLElBQUksQ0FBQyxZQUFZLENBQUMsQ0FBQztRQUUxRCxNQUFNLENBQUMsT0FBTyxDQUFDLE9BQU8sQ0FBQyxTQUFTLENBQUMsU0FBUyxDQUFDLEVBQUU7WUFDM0MsUUFBUSxDQUFDLFNBQVMsQ0FBQyxDQUFDO1FBQ3RCLENBQUMsQ0FBQyxDQUFDO1FBRUgsT0FBTyxNQUFNLENBQUM7SUFDaEIsQ0FBQztJQUVEOzs7Ozs7OztPQVFHO0lBQ0gsb0JBQW9CLENBQUMseUJBQWlDLEVBQUUsU0FBOEI7UUFDcEYsTUFBTSx3QkFBd0IsR0FBRyxTQUFTLEVBQUUsT0FBTyxDQUFDO1FBRXBELElBQUksQ0FBQyx3QkFBd0IsSUFBSSxDQUFDLHlCQUF5QixFQUFFLENBQUM7WUFDNUQsT0FBTyxLQUFLLENBQUM7UUFDZixDQUFDO1FBRUQsT0FBTyxFQUFFLENBQUMsTUFBTSxDQUFDLHdCQUF3QixDQUFDLEVBQUUsTUFBTSxDQUFDLHlCQUF5QixDQUFDLENBQUMsQ0FBQztJQUNqRixDQUFDO0lBRUQ7Ozs7Ozs7O09BUUc7SUFDSCxnQ0FBZ0MsQ0FDOUIsbUJBQTBDLEVBQzFDLE9BQWU7UUFFZixPQUFPLG1CQUFtQixFQUFFLElBQUksQ0FBQyxPQUFPLENBQUMsRUFBRSxDQUFDLE9BQU8sQ0FBQyxJQUFJLENBQUMsUUFBUSxDQUFDLE9BQU8sQ0FBQyxDQUFDLENBQUM7SUFDOUUsQ0FBQztJQUVELEtBQUssQ0FBQyxjQUFjLENBQUMsS0FBa0I7UUFDckMsT0FBTyxDQUFDLE1BQU0sSUFBSSxDQUFDLGtCQUFrQixDQUFDLE1BQU0sQ0FBQyxLQUFLLENBQUMsQ0FBQyxDQUFDLElBQUksQ0FBQztJQUM1RCxDQUFDO0lBRUQsZUFBZSxDQUFDLGVBQW9CLEVBQUU7UUFDcEMsTUFBTSxNQUFNLEdBQVc7WUFDckIsUUFBUSxFQUFFLElBQUk7WUFDZCxjQUFjLEVBQUUsSUFBSTtTQUNyQixDQUFDO1FBQ0YsTUFBTSxDQUFDLE1BQU0sQ0FBQyxNQUFNLEVBQUUsWUFBWSxDQUFDLENBQUM7UUFDcEMsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLGVBQWUsQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDO1FBQy9ELE9BQU8sSUFBSSxDQUFDLGtCQUFrQixDQUFDLFlBQVksQ0FBQyxhQUFhLENBQUMsSUFBSSxFQUFFLE1BQU0sQ0FBQyxDQUFDO0lBQzFFLENBQUM7SUFFRCxLQUFLLENBQUMsZ0JBQWdCO1FBQ3BCLE1BQU0sSUFBSSxHQUFHLENBQUMsTUFBTSxJQUFJLENBQUMsZUFBZSxFQUFFLENBQUMsQ0FBQyxJQUFJLENBQUM7UUFDakQsTUFBTSxhQUFhLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxjQUFjLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUNuRSxPQUFPLGFBQWEsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUNwRSxDQUFDO0lBRUQsS0FBSyxDQUFDLGtCQUFrQixDQUFDLGVBQW9CLEVBQUU7UUFDN0MsTUFBTSxJQUFJLEdBQUcsQ0FBQyxNQUFNLElBQUksQ0FBQyxlQUFlLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7UUFDN0QsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxhQUFhLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUM1RCxPQUFPLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUM5RCxDQUFDO0lBRUQsS0FBSyxDQUFDLHNCQUFzQixDQUFDLGVBQW9CLEVBQUU7UUFDakQsTUFBTSxJQUFJLEdBQUcsQ0FBQyxNQUFNLElBQUksQ0FBQyxlQUFlLENBQUMsWUFBWSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUM7UUFDN0QsTUFBTSxPQUFPLEdBQUcsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLElBQUksQ0FBQyxTQUFTLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQztRQUN4RCxPQUFPLE9BQU8sQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDLEVBQUUsQ0FBQyxFQUFFLEVBQUUsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLGFBQWEsQ0FBQyxDQUFDLENBQUMsSUFBSSxDQUFDLENBQUMsQ0FBQztJQUM5RCxDQUFDO0lBRUQsS0FBSyxDQUFDLHNCQUFzQixDQUFDLGVBQW9CLEVBQUU7UUFDakQsTUFBTSxjQUFjLEdBQUcsR0FBRyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQ2xELE9BQU8sSUFBSSxDQUFDLHVCQUF1QixDQUFDLFlBQVksRUFBRSxjQUFjLENBQUMsQ0FBQztJQUNwRSxDQUFDO0lBRUQsS0FBSyxDQUFDLCtCQUErQixDQUFDLGVBQW9CLEVBQUU7UUFDMUQsTUFBTSxjQUFjLEdBQUcsR0FBRyxDQUFDLEVBQUUsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxJQUFJLElBQUksQ0FBQyxhQUFhLENBQUMsR0FBRyxDQUFDLENBQUM7UUFDN0UsT0FBTyxJQUFJLENBQUMsdUJBQXVCLENBQUMsWUFBWSxFQUFFLGNBQWMsQ0FBQyxDQUFDO0lBQ3BFLENBQUM7SUFFRCxLQUFLLENBQUMsdUJBQXVCLENBQzNCLGVBQW9CLEVBQUUsRUFDdEIsaUJBQWlCLENBQUMsR0FBaUIsRUFBRSxFQUFFLENBQUMsQ0FBQyxDQUFDLEdBQUc7UUFFN0MsTUFBTSxNQUFNLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FBQyxFQUFFLEVBQUUsWUFBWSxDQUFDLENBQUM7UUFDL0MsTUFBTSxZQUFZLEdBQUcsTUFBTSxDQUFDLE1BQU0sQ0FDaEM7WUFDRSxZQUFZLEVBQUUsdUJBQXVCLENBQUMsTUFBTTtZQUM1QyxJQUFJLEVBQUUsUUFBUTtZQUNkLFFBQVEsRUFBRSxJQUFJO1NBQ2YsRUFDRCxZQUFZLENBQ2IsQ0FBQztRQUNGLE1BQU0sQ0FBQyxFQUFFLElBQUksRUFBRSxJQUFJLEVBQUUsRUFBRSxFQUFFLElBQUksRUFBRSxNQUFNLEVBQUUsQ0FBQyxHQUFHLE1BQU0sT0FBTyxDQUFDLEdBQUcsQ0FBQztZQUMzRCxJQUFJLENBQUMsZUFBZSxDQUFDLE1BQU0sQ0FBQztZQUM1QixJQUFJLENBQUMsa0JBQWtCLENBQUMsSUFBSSxDQUFDLFlBQVksQ0FBQztTQUMzQyxDQUFDLENBQUM7UUFDSCxNQUFNLE9BQU8sR0FBRyxDQUFDLEdBQUcsSUFBSSxFQUFFLEdBQUcsTUFBTSxDQUFDLENBQUMsTUFBTSxDQUFDLGNBQWMsQ0FBQyxDQUFDO1FBQzVELDRIQUE0SDtRQUM1SCxNQUFNLFdBQVcsR0FBRyxNQUFNLENBQUMsT0FBTyxFQUFFLENBQUMsR0FBaUIsRUFBRSxFQUFFLENBQUMsR0FBRyxDQUFDLEVBQUUsQ0FBQyxDQUFDO1FBQ25FLE9BQU8sV0FBVyxDQUFDLElBQUksQ0FBQyxDQUFDLENBQUMsRUFBRSxDQUFDLEVBQUUsRUFBRSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsYUFBYSxDQUFDLENBQUMsQ0FBQyxJQUFJLENBQUMsQ0FBQyxDQUFDO0lBQ2xFLENBQUM7SUFFRCxLQUFLLENBQUMsNEJBQTRCO1FBQ2hDLE1BQU0sRUFBRSxJQUFJLEVBQUUsSUFBSSxFQUFFLEdBQUcsTUFBTSxJQUFJLENBQUMsa0JBQWtCLENBQUMsVUFBVSxDQUFDLDhCQUE4QixDQUFDLENBQUM7UUFDaEcsT0FBTyxDQUFDLENBQUMsSUFBSSxDQUFDLE1BQU0sQ0FBQyxHQUFHLENBQUMsRUFBRSxDQUFDLEdBQUcsQ0FBQyxLQUFLLEVBQUUsTUFBTSxFQUFFLEVBQUUsS0FBSyxZQUFZLENBQUMsQ0FBQyxNQUFNLENBQUM7SUFDN0UsQ0FBQztJQUVELG1CQUFtQixDQUFDLEdBQWlCO1FBQ25DLE1BQU0sYUFBYSxHQUFHLENBQUMsSUFBSSxDQUFDLFNBQVMsQ0FBQyxHQUFHLENBQUMsQ0FBQztRQUMzQyxNQUFNLGFBQWEsR0FBRyxDQUFDLGVBQWUsQ0FBQyxNQUFNLEVBQUUsZUFBZSxDQUFDLFFBQVEsQ0FBQyxDQUFDLFFBQVEsQ0FBQyxHQUFHLENBQUMsSUFBSSxDQUFDLENBQUM7UUFDNUYsTUFBTSxZQUFZLEdBQUcsQ0FBQyxJQUFJLENBQUMsU0FBUyxDQUFDLEdBQUcsQ0FBQyxDQUFDO1FBQzFDLE9BQU8sYUFBYSxJQUFJLGFBQWEsSUFBSSxZQUFZLENBQUM7SUFDeEQsQ0FBQztJQUVELE9BQU8sQ0FBQyxHQUFHO1FBQ1QsTUFBTSxDQUFDLElBQUksQ0FBQyxJQUFJLENBQUMsa0JBQWtCLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxFQUFFLFFBQVEsRUFBRSxxQkFBcUIsQ0FBQyxDQUFDO0lBQ3JGLENBQUM7SUFFRCxLQUFLLENBQUMsWUFBWSxDQUFDLEdBQWlCO1FBQ2xDLE9BQU8sQ0FDTCxJQUFJLENBQUMsT0FBTyxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsQ0FBQyxJQUFJLENBQUMsWUFBWSxDQUFDLEdBQUcsQ0FBQyxJQUFJLENBQUMsTUFBTSxJQUFJLENBQUMsc0JBQXNCLENBQUMsR0FBRyxDQUFDLENBQUMsQ0FBQyxDQUMzRixDQUFDO0lBQ0osQ0FBQztJQUVELE9BQU8sQ0FBQyxHQUFpQjtRQUN2QixNQUFNLGFBQWEsR0FBbUIsSUFBSSxDQUFDLGVBQWUsQ0FBQyxhQUFhLENBQUMsS0FBSyxDQUFDO1FBQy9FLE1BQU0sUUFBUSxHQUFHLEdBQUcsQ0FBQyxHQUFHLEVBQUUsaUJBQWlCLENBQUMsQ0FBQztRQUM3QyxPQUFPLGFBQWEsRUFBRSxJQUFJLEtBQUssUUFBUSxDQUFDO0lBQzFDLENBQUM7SUFFRCxTQUFTLENBQUMsR0FBaUI7UUFDekIsT0FBTyxDQUFDLENBQUMsR0FBRyxDQUFDLElBQUksQ0FBQyxLQUFLLENBQUMsVUFBVSxDQUFDLENBQUM7SUFDdEMsQ0FBQztJQUVELGNBQWMsQ0FBQyxHQUFpQjtRQUM5QixPQUFPLEdBQUcsQ0FBQyxJQUFJLEtBQUssY0FBYyxDQUFDO0lBQ3JDLENBQUM7SUFFRCxVQUFVLENBQUMsR0FBaUI7UUFDMUIsT0FBTyxHQUFHLENBQUMsSUFBSSxLQUFLLFVBQVUsQ0FBQztJQUNqQyxDQUFDO0lBRUQsU0FBUyxDQUFDLEdBQWlCO1FBQ3pCLE9BQU8sR0FBRyxDQUFDLFFBQVEsRUFBRSxTQUFTLEtBQUssSUFBSSxDQUFDO0lBQzFDLENBQUM7SUFFRCxRQUFRLENBQUMsR0FBaUI7UUFDeEIsT0FBTyxHQUFHLENBQUMsUUFBUSxFQUFFLE9BQU8sS0FBS