diff --git a/dashboard/src/components/app/Repair.vue b/dashboard/src/components/app/Repair.vue index 6c309ae70..5e44524c9 100644 --- a/dashboard/src/components/app/Repair.vue +++ b/dashboard/src/components/app/Repair.vue @@ -61,14 +61,14 @@ onMounted(() => {
- +

{{ $t('app.repair.taskError.description') }}

-

An error occurred during the {{ taskNameFromInstallationState(app.error.installationState) }} operation: {{ app.error.reason + ': ' + app.error.message }}

- +

An error occurred during the {{ taskNameFromInstallationState(app.error.details?.installationState) }} operation: {{ app.error.reason + ': ' + app.error.message }}

+ diff --git a/dashboard/src/components/app/Uninstall.vue b/dashboard/src/components/app/Uninstall.vue index 2ed6d95a8..aa8d25a69 100644 --- a/dashboard/src/components/app/Uninstall.vue +++ b/dashboard/src/components/app/Uninstall.vue @@ -4,10 +4,10 @@ import { useI18n } from 'vue-i18n'; const i18n = useI18n(); const t = i18n.t; -import { ref, onMounted, computed, useTemplateRef } from 'vue'; +import { ref, onMounted, useTemplateRef } from 'vue'; import { Button, InputDialog } from '@cloudron/pankow'; import { prettyLongDate } from '@cloudron/pankow/utils'; -import { APP_TYPES } from '../../constants.js'; +import { APP_TYPES, ISTATES, RSTATES } from '../../constants.js'; import AppsModel from '../../models/AppsModel.js'; const appsModel = AppsModel.create(); @@ -17,18 +17,36 @@ const inputDialog = useTemplateRef('inputDialog'); const props = defineProps([ 'app' ]); const latestBackup = ref(null); -const isAppStopped = computed(() => { - return appsModel.isStopped(props.app); -}); +function isAppStopped() { + if (props.app.error) { + if (props.app.error.details?.installationState === ISTATES.PENDING_START) return true; + else return false; + } else if (props.app.installationState === ISTATES.PENDING_START || props.app.installationState === ISTATES.PENDING_STOP) { + return props.app.installationState === ISTATES.PENDING_START; + } else { + return props.app.runState === RSTATES.STOPPED; + } +} +const toggleRunStateBusy = ref(false); async function onToggleRunState() { - if (isAppStopped.value) { + // on error just retry + if (props.app.error) { + toggleRunStateBusy.value = true; + const [error] = await appsModel.repair(props.app.id, {}); + if (error) return console.error(error); + } + + if (isAppStopped()) { + toggleRunStateBusy.value = true; const [error] = await appsModel.start(props.app.id); if (error) return console.error(error); } else { const [error] = await appsModel.stop(props.app.id); if (error) return console.error(error); } + + setTimeout(() => toggleRunStateBusy.value = false, 2000); } async function onUninstall() { @@ -84,10 +102,10 @@ onMounted(async () => {

{{ $t('app.uninstall.startStop.description') }}

diff --git a/dashboard/src/models/AppsModel.js b/dashboard/src/models/AppsModel.js index d8380f4a8..5fc287279 100644 --- a/dashboard/src/models/AppsModel.js +++ b/dashboard/src/models/AppsModel.js @@ -171,13 +171,6 @@ function create() { return { name: 'AppsModel', getTask, - isStopped(app) { - if (app.installationState === ISTATES.PENDING_START || app.installationState === ISTATES.PENDING_STOP) { - return app.installationState === ISTATES.PENDING_START; - } else { - return app.runState === RSTATES.STOPPED; - } - }, async install(manifest, config) { const data = { appStoreId: manifest.id + '@' + manifest.version, diff --git a/dashboard/src/views/AppConfigureView.vue b/dashboard/src/views/AppConfigureView.vue index e4594c64f..8e4e535e9 100644 --- a/dashboard/src/views/AppConfigureView.vue +++ b/dashboard/src/views/AppConfigureView.vue @@ -4,7 +4,7 @@ import { useI18n } from 'vue-i18n'; const i18n = useI18n(); const t = i18n.t; -import { ref, onMounted, onBeforeUnmount, computed, useTemplateRef } from 'vue'; +import { ref, onMounted, onBeforeUnmount, useTemplateRef } from 'vue'; import { Button, ButtonGroup, SingleSelect, InputDialog } from '@cloudron/pankow'; import PostInstallDialog from '../components/PostInstallDialog.vue'; import SftpInfoDialog from '../components/SftpInfoDialog.vue'; @@ -47,10 +47,6 @@ const postInstallDialog = useTemplateRef('postInstallDialog'); const sftpInfoDialog = useTemplateRef('sftpInfoDialog'); const inputDialog = useTemplateRef('inputDialog'); -const isAppStopped = computed(() => { - return appsModel.isStopped(app.value); -}); - function onSetView(newView) { if (!isViewEnabled(newView, app.value.error?.details?.installationState)) { currentView.value = 'info'; @@ -62,30 +58,6 @@ function onSetView(newView) { window.location.hash = `/app/${id.value}/${newView}`; } -const toggleRunStateBusy = ref(false); -async function onToggleRunState() { - if (isAppStopped.value) { - toggleRunStateBusy.value = true; - const [error] = await appsModel.start(app.value.id); - if (error) return console.error(error); - } else { - const confirmed = await inputDialog.value.confirm({ - message: t('app.stopDialog.title', { app: app.value.label || app.value.fqdn }), - confirmStyle: 'danger', - confirmLabel: t('main.dialog.yes'), - rejectLabel: t('main.dialog.no'), - rejectStyle: 'secondary', - }); - - if (!confirmed) return; - - toggleRunStateBusy.value = true; - const [error] = await appsModel.stop(app.value.id); - if (error) return console.error(error); - } - setTimeout(() => toggleRunStateBusy.value = false, 2000); -} - let refreshTimer = null; async function refresh() { const [error, result] = await appsModel.get(id.value); @@ -270,13 +242,6 @@ onBeforeUnmount(() => {