diff --git a/dashboard/src/components/AppInstallDialog.vue b/dashboard/src/components/AppInstallDialog.vue index d47113e3f..2b78c24a5 100644 --- a/dashboard/src/components/AppInstallDialog.vue +++ b/dashboard/src/components/AppInstallDialog.vue @@ -7,8 +7,6 @@ import { prettyDate, prettyBinarySize } from '@cloudron/pankow/utils'; import AccessControl from './AccessControl.vue'; import PortBindings from './PortBindings.vue'; import AppsModel from '../models/AppsModel.js'; -import CommunityModel from '../models/CommunityModel.js'; -import AppstoreModel from '../models/AppstoreModel.js'; import DomainsModel from '../models/DomainsModel.js'; import UsersModel from '../models/UsersModel.js'; import GroupsModel from '../models/GroupsModel.js'; @@ -20,9 +18,7 @@ const STEP = Object.freeze({ INSTALL: Symbol('install'), }); -const appstoreModel = AppstoreModel.create(); const appsModel = AppsModel.create(); -const communityModel = CommunityModel.create(); const domainsModel = DomainsModel.create(); const usersModel = UsersModel.create(); const groupsModel = GroupsModel.create(); @@ -33,7 +29,7 @@ const dashboardDomain = inject('dashboardDomain'); // reactive const busy = ref(false); const formError = ref({}); -const appData = ref({}); +const packageData = ref({}); const manifest = ref({}); const step = ref(STEP.DETAILS); const dialog = useTemplateRef('dialogHandle'); @@ -159,7 +155,7 @@ async function onSubmit(overwriteDns) { if (manifest.value.id === PROXY_APP_ID) config.upstreamUri = upstreamUri.value; - const [error, result] = await appsModel.install(appData.value, config); + const [error, result] = await appsModel.install(packageData.value, config); if (!error) { dialog.value.close(); @@ -213,36 +209,14 @@ function onScreenshotNext() { } defineExpose({ - open: async function(upstreamRef, appCountExceeded, domainList) { + open: async function(pd, appCountExceeded, domainList) { busy.value = false; step.value = STEP.LOADING; formError.value = {}; - // give it some time to fetch before showing loading - const openTimer = setTimeout(dialog.value.open, 200); - - if (upstreamRef.appStoreId) { - const [id, version] = upstreamRef.appStoreId.split('@'); - const [error, result] = await appstoreModel.get(id, version); - if (error) { - clearTimeout(openTimer); - dialog.value.close(); - throw new Error('App not found'); - } - appData.value = { ...result, ...upstreamRef }; - } else if (upstreamRef.versionsUrl) { - const [url, version] = upstreamRef.versionsUrl.split('@'); - const [error, result] = await communityModel.getApp(url, version); - if (error) { - clearTimeout(openTimer); - dialog.value.close(); - throw new Error(error.body?.message || 'Failed to fetch community app'); - } - appData.value = { ...result, ...upstreamRef }; - } - + packageData.value = pd; appMaxCountExceeded.value = appCountExceeded; - manifest.value = appData.value.manifest; + manifest.value = packageData.value.manifest; location.value = ''; accessRestrictionOption.value = ACL_OPTIONS.ANY; accessRestrictionAcl.value = { users: [], groups: [] }; @@ -281,6 +255,7 @@ defineExpose({ currentScreenshotPos = 0; step.value = STEP.DETAILS; + dialog.value.open(); }, close() { dialog.value.close(); @@ -296,13 +271,13 @@ defineExpose({
-
-
{{ manifest.title }}
-
{{ manifest.title }} {{ appData.manifest.upstreamVersion }} - {{ $t('app.updates.info.packageVersion') }} {{ appData.manifest.version }}
-
{{ $t('appstore.installDialog.lastUpdated', { date: prettyDate(appData.creationDate) }) }}
+
+
{{ manifest.title }}
+
{{ manifest.title }} {{ packageData.manifest.upstreamVersion }} - {{ $t('app.updates.info.packageVersion') }} {{ packageData.manifest.version }}
+
{{ $t('appstore.installDialog.lastUpdated', { date: prettyDate(packageData.creationDate) }) }}
{{ $t('appstore.installDialog.memoryRequirement', { size: prettyBinarySize(manifest.memoryLimit || (256 * 1024 * 1024)) }) }}
- +
diff --git a/dashboard/src/components/CommunityAppDialog.vue b/dashboard/src/components/CommunityAppDialog.vue index 387a44c46..463eccc21 100644 --- a/dashboard/src/components/CommunityAppDialog.vue +++ b/dashboard/src/components/CommunityAppDialog.vue @@ -2,31 +2,48 @@ import { ref, useTemplateRef } from 'vue'; import { Dialog, TextInput, FormGroup } from '@cloudron/pankow'; +import CommunityModel from '../models/CommunityModel.js'; + +const communityModel = CommunityModel.create(); const emit = defineEmits([ 'success' ]); const dialog = useTemplateRef('dialog'); +const form = useTemplateRef('form'); const formError = ref({}); -const url = ref(''); -const version = ref('latest'); +const versionsUrl = ref(''); +const busy = ref(false); -function onSubmit() { +const isFormValid = ref(false); +function validateForm() { + isFormValid.value = form.value ? form.value.checkValidity() : false; +} + +async function onSubmit() { + if (!form.value.reportValidity()) return; + + busy.value = true; formError.value = {}; - if (!url.value || !version.value) { - formError.value.generic = 'URL and version are required'; - return; + const [error, result] = await communityModel.getApp(versionsUrl.value, 'latest'); + if (error) { + formError.value.generic = error.body ? error.body.message : 'Internal error'; + busy.value = false; + return console.error(error); } - emit('success', { url: url.value, version: version.value }); + const packageData = { ...result, versionsUrl: versionsUrl.value + '@latest' }; + + emit('success', packageData); + dialog.value.close(); + busy.value = false; } defineExpose({ open() { - url.value = ''; - version.value = 'latest'; + versionsUrl.value = ''; formError.value = {}; dialog.value.open(); } @@ -37,26 +54,24 @@ defineExpose({ diff --git a/dashboard/src/views/AppstoreView.vue b/dashboard/src/views/AppstoreView.vue index 5b556a68c..db8ba6753 100644 --- a/dashboard/src/views/AppstoreView.vue +++ b/dashboard/src/views/AppstoreView.vue @@ -144,17 +144,18 @@ async function onHashChange() { const params = new URLSearchParams(window.location.hash.slice(window.location.hash.indexOf('?'))); const version = params.get('version') || 'latest'; - // const [error, appData] = await appstoreModel.get(appId, version); - // if (error) { - // console.error(error); - // return inputDialog.value.info({ - // title: t('appstore.appNotFoundDialog.title'), - // message: t('appstore.appNotFoundDialog.description', { appId, version }), - // confirmLabel: t('main.dialog.close'), - // }); - // } + const [error, result] = await appstoreModel.get(appId, version); + if (error) { + console.error(error); + return inputDialog.value.info({ + title: t('appstore.appNotFoundDialog.title'), + message: t('appstore.appNotFoundDialog.description', { appId, version }), + confirmLabel: t('main.dialog.close'), + }); + } - appInstallDialog.value.open({ appStoreId: `${appId}@${version}` }, installedApps.value.length >= features.value.appMaxCount, domains.value); + const packageData = { ...result, appStoreId: `${appId}@${version}` }; + appInstallDialog.value.open(packageData, installedApps.value.length >= features.value.appMaxCount, domains.value); } } @@ -194,10 +195,8 @@ function onInstallCommunityApp() { communityAppDialog.value.open(); } -function onCommunityAppSuccess({ url, version }) { - // Construct versionsUrl in url@version format - const versionsUrl = `${url}@${version}`; - appInstallDialog.value.open({ versionsUrl }, installedApps.value.length >= features.value.appMaxCount, domains.value); +function onCommunityAppSuccess(packageData) { + appInstallDialog.value.open(packageData, installedApps.value.length >= features.value.appMaxCount, domains.value); } onActivated(async () => { diff --git a/src/apps.js b/src/apps.js index 259f250bc..f1ae8ed90 100644 --- a/src/apps.js +++ b/src/apps.js @@ -2498,7 +2498,7 @@ async function clone(app, data, user, auditSource) { }; const taskId = await addTask(newAppId, exports.ISTATE_PENDING_CLONE, task, auditSource); - const newApp = Object.assign({}, _.omit(obj, ['icon']), { appStoreId, manifest, subdomain, domain, portBindings }); + const newApp = Object.assign({}, _.omit(obj, ['icon']), { appStoreId, versionsUrl, manifest, subdomain, domain, portBindings }); newApp.fqdn = dns.fqdn(newApp.subdomain, newApp.domain); newApp.secondaryDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, ad.domain); }); newApp.redirectDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, ad.domain); }); @@ -2573,7 +2573,7 @@ async function unarchive(archive, data, auditSource) { const taskId = await addTask(appId, obj.installationState, task, auditSource); - const newApp = Object.assign({}, _.omit(obj, ['icon']), { appStoreId, manifest, subdomain, domain, portBindings }); + const newApp = Object.assign({}, _.omit(obj, ['icon']), { appStoreId, versionsUrl, manifest, subdomain, domain, portBindings }); newApp.fqdn = dns.fqdn(newApp.subdomain, newApp.domain); newApp.secondaryDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, ad.domain); }); newApp.redirectDomains.forEach(function (ad) { ad.fqdn = dns.fqdn(ad.subdomain, ad.domain); }); diff --git a/src/notifications.js b/src/notifications.js index 61bf6e8f1..a72c4f7c4 100644 --- a/src/notifications.js +++ b/src/notifications.js @@ -203,7 +203,7 @@ async function appUpdated(eventId, app, fromManifest, toManifest) { assert.strictEqual(typeof fromManifest, 'object'); assert.strictEqual(typeof toManifest, 'object'); - if (!app.appStoreId) return; // skip notification of dev apps + if (!app.appStoreId && !app.versionsUrl) return; // skip notification of dev apps const tmp = toManifest.description.match(/(.*)<\/upstream>/i); const upstreamVersion = (tmp && tmp[1]) ? tmp[1] : '';