Files
cloudron-box/dashboard/src/components/app/Updates.vue
T

154 lines
5.1 KiB
Vue
Raw Normal View History

2025-02-21 20:58:43 +01:00
<script setup>
2025-06-25 21:16:59 +02:00
import { ref, onMounted, useTemplateRef } from 'vue';
2025-02-21 20:58:43 +01:00
import { marked } from 'marked';
2025-09-25 15:46:36 +02:00
import { Button, Switch, Dialog, Checkbox } from '@cloudron/pankow';
import { ISTATES } from '../../constants.js';
2025-06-25 21:16:59 +02:00
import SettingsItem from '../SettingsItem.vue';
2025-02-21 20:58:43 +01:00
import AppsModel from '../../models/AppsModel.js';
import ProfileModel from '../../models/ProfileModel.js';
import TasksModel from '../../models/TasksModel.js';
2025-02-21 20:58:43 +01:00
const props = defineProps([ 'app' ]);
const appsModel = AppsModel.create();
const profileModel = ProfileModel.create();
const tasksModel = TasksModel.create();
2025-02-21 20:58:43 +01:00
const dialog = useTemplateRef('dialog');
const profile = ref({});
const busyUpdate = ref(false);
const busyCheck = ref(false);
const skipBackup = ref(false);
const updateError = ref('');
2025-02-21 20:58:43 +01:00
const autoUpdatesEnabled = ref(false);
const autoUpdatesEnabledBusy = ref(false);
async function onAutoUpdatesEnabledChange(value) {
autoUpdatesEnabledBusy.value = true;
const [error] = await appsModel.configure(props.app.id, 'automatic_update', { enable: value });
if (error) {
autoUpdatesEnabled.value = !value;
console.error(error);
}
autoUpdatesEnabledBusy.value = false;
}
2025-02-21 20:58:43 +01:00
async function waitForTask(id) {
if (!id) return;
const [error, result] = await tasksModel.get(id);
if (error) return console.error(error);
// task done, refresh menu
if (!result.active) {
await onCheck();
return;
}
setTimeout(waitForTask.bind(null, id), 2000);
}
2025-02-21 20:58:43 +01:00
async function onCheck() {
busyCheck.value = true;
const [error] = await appsModel.checkUpdate(props.app.id);
2025-02-21 20:58:43 +01:00
if (error) return console.error(error);
busyCheck.value = false;
}
async function onUpdate() {
busyUpdate.value = true;
updateError.value = '';
const [error, result] = await appsModel.update(props.app.id, props.app.updateInfo.manifest, skipBackup.value);
2025-02-21 20:58:43 +01:00
if (error) {
busyUpdate.value = false;
if (error.status === 400) updateError.value = error.body ? error.body.message : 'Internal error';
2025-02-21 20:58:43 +01:00
return console.error(error);
}
dialog.value.close();
waitForTask(result);
2025-02-21 20:58:43 +01:00
}
function onAskUpdate() {
busyUpdate.value = false;
dialog.value.open();
}
function onSetupSubscription() {
2025-04-22 18:03:01 +02:00
// TODO payment
2025-02-21 20:58:43 +01:00
}
onMounted(async () => {
busyUpdate.value = false;
busyCheck.value = false;
autoUpdatesEnabled.value = props.app.enableAutomaticUpdate;
const [error, result] = await profileModel.get();
if (error) return console.error(error);
profile.value = result;
});
</script>
<template>
<div>
<Dialog v-if="app.updateInfo" ref="dialog"
:title="$t('app.updateDialog.title', { app: app.fqdn })"
:reject-label="$t('main.dialog.cancel')"
reject-style="secondary"
:confirm-label="$t('app.updateDialog.updateAction')"
:confirm-active="!busyUpdate && app.updateInfo.manifest.dockerImage"
:confirm-busy="busyUpdate"
:alternate-label="!app.updateInfo.manifest.dockerImage && profile.isAtLeastOwner ? $t('app.updateDialog.setupSubscriptionAction') : ''"
alternate-style="success"
@confirm="onUpdate()"
@alternate="onSetupSubscription()"
>
<div>
<Checkbox v-if="app.updateInfo.manifest.dockerImage" v-model="skipBackup" :label="$t('app.updateDialog.skipBackupCheckbox')" />
</div>
</Dialog>
2025-06-25 21:16:59 +02:00
<SettingsItem>
<div>
<label>{{ $t('app.updates.auto.title') }}</label>
<div v-if="!app.appStoreId">{{ $t('app.updates.info.customAppUpdateInfo') }}</div>
2025-09-30 14:10:07 +02:00
<div v-else>{{ $t('app.updates.auto') }}</div>
2025-06-25 21:16:59 +02:00
</div>
<Switch v-if="app.appStoreId" v-model="autoUpdatesEnabled" :disabled="autoUpdatesEnabledBusy" @change="onAutoUpdatesEnabledChange"/>
</SettingsItem>
2025-02-21 20:58:43 +01:00
2025-09-25 15:02:16 +02:00
<hr style="margin-top: 20px"/>
<div v-if="app.appStoreId">
<label>{{ $t('app.updatesTabTitle') }}</label>
<div v-html="$t('app.updates.auto.description', { appStoreLink: 'https://www.cloudron.io/store/index.html' })"></div>
</div>
<br/>
2025-09-21 14:57:58 +02:00
<Button v-if="app.appStoreId" @click="onCheck()" :disabled="busyCheck" :loading="busyCheck">{{ $t('settings.updates.checkForUpdatesAction') }}</Button>
2025-02-21 20:58:43 +01:00
2025-09-25 15:02:16 +02:00
<hr v-if="app.updateInfo" style="margin-top: 20px"/>
2025-09-25 15:02:16 +02:00
<div v-if="app.updateInfo">
<label>{{ $t('settings.updates.updateAvailableAction') }}</label>
<div class="error-label" v-if="!app.updateInfo.manifest.dockerImage">{{ $t('app.updateDialog.subscriptionExpired') }}</div>
<div class="error-label" v-if="updateError">{{ updateError }}</div>
2025-09-25 15:02:16 +02:00
<div class="text-danger" v-if="app.updateInfo.unstable">{{ $t('app.updateDialog.unstableWarning') }}</div>
2025-02-21 20:58:43 +01:00
2025-09-25 15:02:16 +02:00
<div>{{ $t('app.updateDialog.changelogHeader', { version: app.updateInfo.manifest.version }) }}</div>
<div v-html="marked.parse(app.updateInfo.manifest.changelog)"></div>
</div>
<br/>
<Button v-if="app.updateInfo" :danger="app.updateInfo.unstable ? true : null" :success="app.updateInfo.unstable ? null : true" @click="onAskUpdate()" :disabled="app.taskId || (app.error && app.error.details.installationState !== ISTATES.PENDING_UPDATE) || app.runState === 'stopped' || app.installationState === 'pending_update'">{{ $t('app.updateDialog.updateAction') }}</Button>
2025-02-21 20:58:43 +01:00
</div>
</template>