From 50e69b095cf20ee9e66d5bac0c8e1f1fa75bc555 Mon Sep 17 00:00:00 2001 From: Juergen Kunz Date: Mon, 25 May 2026 03:03:03 +0000 Subject: [PATCH] feat(appstore): use shared resolver --- changelog.md | 6 + package.json | 3 +- pnpm-lock.yaml | 22 ++- pnpm-workspace.yaml | 1 + servezone.appstore.json | 129 ++++++++++++++ ts/classes.cloudly.ts | 10 +- .../classes.appstoremanager.ts} | 159 ++++++++---------- ts/plugins.ts | 3 +- ts_web/elements/views/services/index.ts | 2 +- 9 files changed, 230 insertions(+), 105 deletions(-) create mode 100644 servezone.appstore.json rename ts/{manager.appcatalog/classes.appcatalogmanager.ts => manager.appstore/classes.appstoremanager.ts} (60%) diff --git a/changelog.md b/changelog.md index 177ffa6..093bc50 100644 --- a/changelog.md +++ b/changelog.md @@ -2,6 +2,12 @@ ## Pending +### Breaking Changes + +- switch App Store APIs to the shared appstore client + - Replaces Cloudly App Store manager naming and request methods with App Store naming + - Uses `@serve.zone/appstore` for app metadata parsing and source resolution + - Adds `servezone.appstore.json` as Cloudly's source-owned App Store manifest ## 2026-05-24 - 5.9.0 diff --git a/package.json b/package.json index 802246e..80d1db7 100644 --- a/package.json +++ b/package.json @@ -79,7 +79,8 @@ "@push.rocks/taskbuffer": "^8.0.2", "@push.rocks/webjwt": "^1.0.10", "@serve.zone/api": "^5.3.8", - "@serve.zone/interfaces": "^5.10.0", + "@serve.zone/appstore": "^0.2.0", + "@serve.zone/interfaces": "^6.0.0", "@tsclass/tsclass": "^9.5.1" }, "files": [ diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 23b988e..cd2b4e4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -143,9 +143,12 @@ importers: '@serve.zone/api': specifier: ^5.3.8 version: 5.3.8(@push.rocks/smartserve@2.0.4) + '@serve.zone/appstore': + specifier: ^0.2.0 + version: 0.2.0 '@serve.zone/interfaces': - specifier: ^5.10.0 - version: 5.10.0 + specifier: ^6.0.0 + version: 6.0.0 '@tsclass/tsclass': specifier: ^9.5.1 version: 9.5.1 @@ -2030,11 +2033,14 @@ packages: '@serve.zone/api@5.3.8': resolution: {integrity: sha512-k3IU4mcHuk5pKB+X7rhYWGK+j5hyyDzFoqR3ytzG1iidvgDEIIToQJq+mB3E1v6X1+tI3WyYUaMN/TaZRz0l0w==} + '@serve.zone/appstore@0.2.0': + resolution: {integrity: sha512-qt2LVaRpzfJdUywllm+F0njwnN3aHc2aZHEcjc9REn1VDT47UuUEGaKkfNiosGK0GJqb1hPI/GwyuGMe4H4q7w==} + '@serve.zone/interfaces@5.10.0': resolution: {integrity: sha512-8ZnP1A43UZlYwfd2j+S0Yin//didacIX2Rou9MobRuSFFgi1RQOqQcIWqOINcDk80wBDuYkyMCwHygYxD5i+Ig==} - '@serve.zone/interfaces@5.9.0': - resolution: {integrity: sha512-XMXyTXTMcB8AX6zYOMO+Jt5bOv9ujyXj5miE6lrgyT8g+eJ/I6sUFqVNUKJ3LiMk/yFWsPln7HtZeZKDEhaCwQ==} + '@serve.zone/interfaces@6.0.0': + resolution: {integrity: sha512-nCidhOH0XlX+7e6xaJDq6fwnwaWasB/4w2LHkV7A96G+m+7EXZqbbaKSboUlaiGDly0dWNajk2FrYFo64ZucPA==} '@shikijs/engine-oniguruma@3.23.0': resolution: {integrity: sha512-1nWINwKXxKKLqPibT5f4pAFLej9oZzQTsby8942OTlsJzOBZ0MWKiwzMsd+jhzu8YPCHAswGnnN1YtQfirL35g==} @@ -7774,18 +7780,22 @@ snapshots: '@push.rocks/smartpromise': 4.2.4 '@push.rocks/smartrx': 3.0.10 '@push.rocks/smartstream': 3.4.2 - '@serve.zone/interfaces': 5.9.0 + '@serve.zone/interfaces': 5.10.0 '@tsclass/tsclass': 9.5.1 transitivePeerDependencies: - '@push.rocks/smartserve' + '@serve.zone/appstore@0.2.0': + dependencies: + '@serve.zone/interfaces': 6.0.0 + '@serve.zone/interfaces@5.10.0': dependencies: '@api.global/typedrequest-interfaces': 3.0.19 '@push.rocks/smartlog-interfaces': 3.0.2 '@tsclass/tsclass': 9.5.1 - '@serve.zone/interfaces@5.9.0': + '@serve.zone/interfaces@6.0.0': dependencies: '@api.global/typedrequest-interfaces': 3.0.19 '@push.rocks/smartlog-interfaces': 3.0.2 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index dff757e..3d9398a 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,5 +1,6 @@ minimumReleaseAgeExclude: - '@serve.zone/api' + - '@serve.zone/appstore' - '@serve.zone/interfaces' allowBuilds: diff --git a/servezone.appstore.json b/servezone.appstore.json new file mode 100644 index 0000000..f4a53fc --- /dev/null +++ b/servezone.appstore.json @@ -0,0 +1,129 @@ +{ + "schemaVersion": 1, + "app": { + "id": "cloudly", + "name": "Cloudly", + "description": "Multi-node serve.zone control plane for clusters, workload services, domains, and deployments.", + "category": "Dev Tools", + "iconName": "server", + "tags": ["serve.zone", "control-plane", "clusters", "deployments"], + "maintainer": "serve.zone", + "links": { + "Source": "https://code.foss.global/serve.zone/cloudly", + "Docs": "https://serve.zone" + } + }, + "latestVersion": "latest", + "source": { + "type": "dockerImage", + "image": "code.foss.global/serve.zone/cloudly:latest", + "tracking": "digest" + }, + "runtime": { + "image": "code.foss.global/serve.zone/cloudly:latest", + "port": 80, + "envVars": [ + { + "key": "SERVEZONE_ENVIRONMENT", + "value": "production", + "description": "Cloudly runtime environment.", + "required": true + }, + { + "key": "SERVEZONE_URL", + "value": "${SERVICE_DOMAIN}", + "description": "Public Cloudly hostname without protocol.", + "required": true + }, + { + "key": "SERVEZONE_PORT", + "value": "80", + "description": "Internal Cloudly HTTP port inside the container.", + "required": true + }, + { + "key": "SERVEZONE_SSLMODE", + "value": "external", + "description": "Use external TLS termination through Onebox or dcrouter.", + "required": true + }, + { + "key": "SERVEZONE_ADMINACCOUNT", + "value": "", + "description": "Initial admin account in username:password format. Only used when Cloudly has no human users yet.", + "required": true + }, + { + "key": "MONGODB_URL", + "value": "${MONGODB_URI}", + "description": "Authenticated MongoDB connection URL provisioned by Onebox.", + "required": true + }, + { + "key": "MONGODB_NAME", + "value": "${MONGODB_DATABASE}", + "description": "MongoDB database name provisioned by Onebox.", + "required": true + }, + { + "key": "MONGODB_USER", + "value": "${MONGODB_USERNAME}", + "description": "MongoDB username provisioned by Onebox.", + "required": true + }, + { + "key": "MONGODB_PASS", + "value": "${MONGODB_PASSWORD}", + "description": "MongoDB password provisioned by Onebox.", + "required": true + }, + { + "key": "S3_ENDPOINT", + "value": "onebox-minio", + "description": "S3 endpoint host for the MinIO service provisioned by Onebox.", + "required": true + }, + { + "key": "S3_PORT", + "value": "9000", + "description": "S3 endpoint port for the MinIO service provisioned by Onebox.", + "required": true + }, + { + "key": "S3_USESSL", + "value": "false", + "description": "Use plain HTTP for internal MinIO traffic on the Onebox network.", + "required": true + }, + { + "key": "S3_BUCKET", + "value": "${S3_BUCKET}", + "description": "S3 bucket provisioned by Onebox for Cloudly's registry.", + "required": true + }, + { + "key": "S3_ACCESSKEY", + "value": "${S3_ACCESS_KEY}", + "description": "S3 access key provisioned by Onebox.", + "required": true + }, + { + "key": "S3_SECRETKEY", + "value": "${S3_SECRET_KEY}", + "description": "S3 secret key provisioned by Onebox.", + "required": true + } + ], + "platformRequirements": { + "mongodb": true, + "s3": true + }, + "minOneboxVersion": "1.24.2", + "backupBeforeUpgrade": true, + "healthCheck": { + "path": "/status", + "port": 80, + "expectedStatus": 200 + } + } +} diff --git a/ts/classes.cloudly.ts b/ts/classes.cloudly.ts index f06426a..2c28d45 100644 --- a/ts/classes.cloudly.ts +++ b/ts/classes.cloudly.ts @@ -34,7 +34,7 @@ import { CloudlySettingsManager } from './manager.settings/classes.settingsmanag import { CloudlyPlatformManager } from './manager.platform/classes.platformmanager.js'; import { CloudlyBackupManager } from './manager.backup/classes.backupmanager.js'; import { CloudlyBaseOsManager } from './manager.baseos/classes.baseosmanager.js'; -import { CloudlyAppCatalogManager } from './manager.appcatalog/classes.appcatalogmanager.js'; +import { CloudlyAppStoreManager } from './manager.appstore/classes.appstoremanager.js'; import { CloudlyJumpManager } from './manager.jump/classes.jumpmanager.js'; /** @@ -81,7 +81,7 @@ export class Cloudly { public nodeManager: CloudlyNodeManager; public baremetalManager: CloudlyBaremetalManager; public baseOsManager: CloudlyBaseOsManager; - public appCatalogManager: CloudlyAppCatalogManager; + public appStoreManager: CloudlyAppStoreManager; public jumpManager: CloudlyJumpManager; private readyDeferred = new plugins.smartpromise.Deferred(); @@ -119,7 +119,7 @@ export class Cloudly { this.backupManager = new CloudlyBackupManager(this); this.baseOsManager = new CloudlyBaseOsManager(this); this.secretManager = new CloudlySecretManager(this); - this.appCatalogManager = new CloudlyAppCatalogManager(this); + this.appStoreManager = new CloudlyAppStoreManager(this); this.nodeManager = new CloudlyNodeManager(this); this.baremetalManager = new CloudlyBaremetalManager(this); this.jumpManager = new CloudlyJumpManager(this); @@ -151,7 +151,7 @@ export class Cloudly { await this.taskManager.init(); await this.backupManager.start(); await this.baseOsManager.start(); - await this.appCatalogManager.start(); + await this.appStoreManager.start(); await this.registryManager.start(); await this.domainManager.init(); @@ -186,7 +186,7 @@ export class Cloudly { await this.backupManager.stop(); await this.baseOsManager.stop(); await this.registryManager.stop(); - await this.appCatalogManager.stop(); + await this.appStoreManager.stop(); await this.externalRegistryManager.stop(); } } diff --git a/ts/manager.appcatalog/classes.appcatalogmanager.ts b/ts/manager.appstore/classes.appstoremanager.ts similarity index 60% rename from ts/manager.appcatalog/classes.appcatalogmanager.ts rename to ts/manager.appstore/classes.appstoremanager.ts index 03cd674..bd5e5d4 100644 --- a/ts/manager.appcatalog/classes.appcatalogmanager.ts +++ b/ts/manager.appstore/classes.appstoremanager.ts @@ -5,19 +5,18 @@ import { Service } from '../manager.service/classes.service.js'; import { SecretBundle } from '../manager.secret/classes.secretbundle.js'; import { PlatformBinding } from '../manager.platform/classes.platformbinding.js'; -type ICatalogApp = plugins.servezoneInterfaces.appcatalog.ICatalogApp; -type ICatalog = plugins.servezoneInterfaces.appcatalog.ICatalog; -type IAppMeta = plugins.servezoneInterfaces.appcatalog.IAppMeta; -type IAppVersionConfig = plugins.servezoneInterfaces.appcatalog.IAppVersionConfig; -type IInstallOptions = plugins.servezoneInterfaces.appcatalog.IAppInstallRequest; -type IUpgradeableCatalogService = plugins.servezoneInterfaces.appcatalog.IUpgradeableAppService; +type IAppStoreApp = plugins.servezoneInterfaces.appstore.IAppStoreApp; +type IAppStoreIndex = plugins.servezoneInterfaces.appstore.IAppStoreIndex; +type IAppStoreAppMeta = plugins.servezoneInterfaces.appstore.IAppStoreAppMeta; +type IAppStoreVersionConfig = plugins.servezoneInterfaces.appstore.IAppStoreVersionConfig; +type IAppStoreInstallOptions = plugins.servezoneInterfaces.appstore.IAppStoreInstallRequest; +type IUpgradeableAppStoreService = plugins.servezoneInterfaces.appstore.IUpgradeableAppStoreService; -export class CloudlyAppCatalogManager { +export class CloudlyAppStoreManager { public typedrouter = new plugins.typedrequest.TypedRouter(); - private catalogCache: ICatalog | null = null; - private lastFetchTime = 0; - private readonly repoBaseUrl = process.env.APPCATALOG_URL || 'https://code.foss.global/serve.zone/appstore-apptemplates/raw/branch/main'; - private readonly cacheTtlMs = 5 * 60 * 1000; + private readonly appStoreResolver = new plugins.servezoneAppstore.AppStoreResolver({ + baseUrl: process.env.APPSTORE_URL || 'https://code.foss.global/serve.zone/appstore/raw/branch/main', + }); constructor(private cloudlyRef: Cloudly) { this.cloudlyRef.typedrouter.addTypedRouter(this.typedrouter); @@ -28,88 +27,74 @@ export class CloudlyAppCatalogManager { public async stop() {} private registerHandlers() { - const addCatalogListHandler = (methodArg: string) => { - this.typedrouter.addTypedHandler( - new plugins.typedrequest.TypedHandler(methodArg, async (dataArg) => { + this.typedrouter.addTypedHandler( + new plugins.typedrequest.TypedHandler( + 'getAppStoreTemplates', + async (dataArg) => { await this.passAdminIdentity(dataArg); return { apps: await this.getApps() }; - }), - ); - }; - addCatalogListHandler('getAppCatalogTemplates'); - addCatalogListHandler('getAppTemplates'); + }, + ), + ); - const addConfigHandler = (methodArg: string) => { - this.typedrouter.addTypedHandler( - new plugins.typedrequest.TypedHandler(methodArg, async (dataArg) => { + this.typedrouter.addTypedHandler( + new plugins.typedrequest.TypedHandler( + 'getAppStoreConfig', + async (dataArg) => { await this.passAdminIdentity(dataArg); return { config: await this.getAppVersionConfig(dataArg.appId, dataArg.version), appMeta: await this.getAppMeta(dataArg.appId), }; - }), - ); - }; - addConfigHandler('getAppCatalogConfig'); - addConfigHandler('getAppConfig'); + }, + ), + ); - const addInstallHandler = (methodArg: string) => { - this.typedrouter.addTypedHandler( - new plugins.typedrequest.TypedHandler(methodArg, async (dataArg) => { + this.typedrouter.addTypedHandler( + new plugins.typedrequest.TypedHandler( + 'installAppStoreApp', + async (dataArg) => { await this.passAdminIdentity(dataArg); - const service = await this.installApp(dataArg.install || dataArg); + const service = await this.installApp(dataArg.install); return { service: await service.createSavableObject() }; - }), - ); - }; - addInstallHandler('installAppCatalogApp'); - addInstallHandler('installAppTemplate'); + }, + ), + ); - const addUpgradeableHandler = (methodArg: string) => { - this.typedrouter.addTypedHandler( - new plugins.typedrequest.TypedHandler(methodArg, async (dataArg) => { + this.typedrouter.addTypedHandler( + new plugins.typedrequest.TypedHandler( + 'getUpgradeableAppStoreServices', + async (dataArg) => { await this.passAdminIdentity(dataArg); - return { services: await this.getUpgradeableServices() }; - }), - ); - }; - addUpgradeableHandler('getUpgradeableAppCatalogServices'); - addUpgradeableHandler('getUpgradeableServices'); + return { services: await this.getUpgradeableAppStoreServices() }; + }, + ), + ); } - public async getCatalog(): Promise { - const now = Date.now(); - if (this.catalogCache && now - this.lastFetchTime < this.cacheTtlMs) { - return this.catalogCache; - } - const catalog = await this.fetchJson('catalog.json') as ICatalog; - if (!catalog || !Array.isArray(catalog.apps)) { - throw new Error('Invalid app catalog format'); - } - this.catalogCache = catalog; - this.lastFetchTime = now; - return catalog; + public async getAppStore(): Promise { + return await this.appStoreResolver.getAppStoreIndex(); } - public async getApps(): Promise { - return (await this.getCatalog()).apps; + public async getApps(): Promise { + return await this.appStoreResolver.getApps(); } - public async getAppMeta(appIdArg: string): Promise { - return await this.fetchJson(`apps/${appIdArg}/app.json`) as IAppMeta; + public async getAppMeta(appIdArg: string): Promise { + return await this.appStoreResolver.getAppMeta(appIdArg); } - public async getAppVersionConfig(appIdArg: string, versionArg?: string): Promise { + public async getAppVersionConfig(appIdArg: string, versionArg?: string): Promise { if (!versionArg) { versionArg = (await this.getAppMeta(appIdArg)).latestVersion; } - return await this.fetchJson(`apps/${appIdArg}/versions/${versionArg}/config.json`) as IAppVersionConfig; + return await this.appStoreResolver.getAppVersionConfig(appIdArg, versionArg); } - public async getUpgradeableServices(): Promise { - const catalog = await this.getCatalog(); + public async getUpgradeableAppStoreServices(): Promise { + const appStore = await this.getAppStore(); const services = await this.cloudlyRef.serviceManager.CService.getInstances({}); - const upgradeableServices: IUpgradeableCatalogService[] = []; + const upgradeableServices: IUpgradeableAppStoreService[] = []; for (const service of services) { const serviceData = service.data as plugins.servezoneInterfaces.data.IService['data'] & { @@ -119,15 +104,15 @@ export class CloudlyAppCatalogManager { if (!serviceData.appTemplateId || !serviceData.appTemplateVersion) { continue; } - const catalogApp = catalog.apps.find((appArg) => appArg.id === serviceData.appTemplateId); - if (!catalogApp || catalogApp.latestVersion === serviceData.appTemplateVersion) { + const appStoreApp = appStore.apps.find((appArg) => appArg.id === serviceData.appTemplateId); + if (!appStoreApp || appStoreApp.latestVersion === serviceData.appTemplateVersion) { continue; } upgradeableServices.push({ serviceName: serviceData.name, appTemplateId: serviceData.appTemplateId, currentVersion: serviceData.appTemplateVersion, - latestVersion: catalogApp.latestVersion, + latestVersion: appStoreApp.latestVersion, hasMigration: false, }); } @@ -135,18 +120,19 @@ export class CloudlyAppCatalogManager { return upgradeableServices; } - public async installApp(optionsArg: IInstallOptions): Promise { + public async installApp(optionsArg: IAppStoreInstallOptions): Promise { const appMeta = await this.getAppMeta(optionsArg.appId); const version = optionsArg.version || appMeta.latestVersion; const config = await this.getAppVersionConfig(optionsArg.appId, version); + const appStoreVersion = config.appStoreVersion || version; const webPort = optionsArg.port || config.port; this.assertSupportedPlatformRequirements(config); - const envVars = this.getCatalogEnvVars(config, optionsArg.envVars || {}); + const envVars = this.getAppStoreEnvVars(config, optionsArg.envVars || {}); if (this.requiresTemplateValue(envVars, 'SERVICE_DOMAIN') && !optionsArg.domain) { throw new Error('A domain is required because the app template uses ${SERVICE_DOMAIN}'); } - const image = await this.createCatalogImage(optionsArg.serviceName, config.image, appMeta.description); + const image = await this.createAppStoreImage(optionsArg.serviceName, config.image, appMeta.description); const secretBundle = await this.createServiceSecretBundle(optionsArg.serviceName, image.id); const serviceData = { name: optionsArg.serviceName, @@ -155,7 +141,7 @@ export class CloudlyAppCatalogManager { imageVersion: this.getImageTag(config.image), deployOnPush: false, appTemplateId: optionsArg.appId, - appTemplateVersion: version, + appTemplateVersion: appStoreVersion, environment: envVars, secretBundleId: secretBundle.id, additionalSecretBundleIds: [], @@ -181,11 +167,11 @@ export class CloudlyAppCatalogManager { return service; } - private async createCatalogImage(serviceNameArg: string, imageRefArg: string, descriptionArg: string): Promise { + private async createAppStoreImage(serviceNameArg: string, imageRefArg: string, descriptionArg: string): Promise { const image = new Image(); image.id = await Image.getNewId(); image.data = { - name: `${serviceNameArg}-catalog-image`, + name: `${serviceNameArg}-appstore-image`, description: descriptionArg, location: { internal: false, @@ -210,8 +196,8 @@ export class CloudlyAppCatalogManager { const secretBundle = new SecretBundle(); secretBundle.id = plugins.smartunique.shortId(8); secretBundle.data = { - name: `${serviceNameArg} catalog secrets`, - description: `Generated catalog secret bundle for ${serviceNameArg}`, + name: `${serviceNameArg} appstore secrets`, + description: `Generated appstore secret bundle for ${serviceNameArg}`, type: 'service', includedSecretGroupIds: [], includedTags: [], @@ -222,7 +208,7 @@ export class CloudlyAppCatalogManager { return secretBundle; } - private async createPlatformBindings(serviceArg: Service, configArg: IAppVersionConfig) { + private async createPlatformBindings(serviceArg: Service, configArg: IAppStoreVersionConfig) { const requirements = configArg.platformRequirements || {}; if (requirements.mongodb) { await PlatformBinding.upsertBinding({ @@ -244,7 +230,7 @@ export class CloudlyAppCatalogManager { } } - private normalizeVolumes(volumesArg: IAppVersionConfig['volumes'] = []) { + private normalizeVolumes(volumesArg: IAppStoreVersionConfig['volumes'] = []) { return volumesArg.map((volumeArg) => { if (typeof volumeArg === 'string') { return { mountPath: volumeArg }; @@ -253,7 +239,7 @@ export class CloudlyAppCatalogManager { }).filter((volumeArg) => Boolean(volumeArg.mountPath)); } - private getCatalogEnvVars(configArg: IAppVersionConfig, overridesArg: Record): Record { + private getAppStoreEnvVars(configArg: IAppStoreVersionConfig, overridesArg: Record): Record { const envVars: Record = {}; const missingRequiredEnvVars: string[] = []; for (const envVar of configArg.envVars || []) { @@ -274,12 +260,12 @@ export class CloudlyAppCatalogManager { return Object.values(envVarsArg).some((value) => value.includes(`\${${templateNameArg}}`)); } - private assertSupportedPlatformRequirements(configArg: IAppVersionConfig) { + private assertSupportedPlatformRequirements(configArg: IAppStoreVersionConfig) { const unsupported = Object.entries(configArg.platformRequirements || {}) .filter(([key, enabled]) => enabled && key !== 'mongodb' && key !== 's3') .map(([key]) => key); if (unsupported.length > 0) { - throw new Error(`Cloudly catalog install does not yet support platform requirement(s): ${unsupported.join(', ')}`); + throw new Error(`Cloudly App Store install does not yet support platform requirement(s): ${unsupported.join(', ')}`); } } @@ -294,13 +280,4 @@ export class CloudlyAppCatalogManager { this.cloudlyRef.authManager.adminIdentityGuard, ]); } - - private async fetchJson(pathArg: string): Promise { - const url = `${this.repoBaseUrl.replace(/\/+$/, '')}/${pathArg}`; - const response = await fetch(url); - if (!response.ok) { - throw new Error(`HTTP ${response.status} for ${url}`); - } - return response.json(); - } } diff --git a/ts/plugins.ts b/ts/plugins.ts index 70c7dfd..2f937c1 100644 --- a/ts/plugins.ts +++ b/ts/plugins.ts @@ -86,5 +86,6 @@ export { // @servezone scope import * as servezoneInterfaces from '@serve.zone/interfaces'; +import * as servezoneAppstore from '@serve.zone/appstore'; -export { servezoneInterfaces }; +export { servezoneAppstore, servezoneInterfaces }; diff --git a/ts_web/elements/views/services/index.ts b/ts_web/elements/views/services/index.ts index af6251e..5172758 100644 --- a/ts_web/elements/views/services/index.ts +++ b/ts_web/elements/views/services/index.ts @@ -472,7 +472,7 @@ export class CloudlyViewServices extends DeesElement { private async loadUpgradeInfo(serviceArg: plugins.interfaces.data.IService) { try { - const response = await this.fireTypedRequest('getUpgradeableServices', {}) as { services: any[] }; + const response = await this.fireTypedRequest('getUpgradeableAppStoreServices', {}) as { services: any[] }; this.upgradeInfo = response.services?.find((upgradeArg) => upgradeArg.serviceName === serviceArg.data.name) || null; } catch { this.upgradeInfo = null;