Files
cloudron-box/dashboard/src/components/app/Updates.vue
T
2026-02-24 05:31:13 +01:00

187 lines
6.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<script setup>
import { ref, onMounted, useTemplateRef, inject } from 'vue';
import { marked } from 'marked';
import { Button, Switch, Dialog, Checkbox } from '@cloudron/pankow';
import { ISTATES } from '../../constants.js';
import SettingsItem from '../SettingsItem.vue';
import AppsModel from '../../models/AppsModel.js';
import ProfileModel from '../../models/ProfileModel.js';
import TasksModel from '../../models/TasksModel.js';
const props = defineProps([ 'app', 'refresh-app' ]);
const appsModel = AppsModel.create();
const profileModel = ProfileModel.create();
const tasksModel = TasksModel.create();
const features = inject('features');
const dialog = useTemplateRef('dialog');
const profile = ref({});
const busyUpdate = ref(false);
const busyCheck = ref(false);
const skipBackup = ref(false);
const updateError = ref('');
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;
}
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);
}
async function onCheck() {
busyCheck.value = true;
const [error] = await appsModel.checkUpdate(props.app.id);
if (error) return console.error(error);
await props.refreshApp();
busyCheck.value = false;
}
async function onUpdate() {
busyUpdate.value = true;
updateError.value = '';
let appData = '';
if (props.app.appStoreId) {
appData = { manifest: props.app.updateInfo.manifest };
} else if (props.app.versionsUrl) {
appData = { versionsUrl: `${props.app.versionsUrl}@${props.app.updateInfo.manifest.version}` };
}
const [error, result] = await appsModel.update(props.app.id, appData, skipBackup.value);
if (error) {
busyUpdate.value = false;
if (error.status === 400) updateError.value = error.body ? error.body.message : 'Internal error';
return console.error(error);
}
dialog.value.close();
waitForTask(result);
}
function onAskUpdate() {
busyUpdate.value = false;
dialog.value.open();
}
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"
:confirm-busy="busyUpdate"
@confirm="onUpdate()"
>
<div>
<div class="unstable-warning text-danger" v-if="app.updateInfo.unstable">{{ $t('app.updateDialog.unstableWarning') }}</div>
<div>{{ $t('app.updateDialog.changelogHeader', { version: app.updateInfo.manifest.version }) }}</div>
<div class="changelog" v-html="marked.parse(app.updateInfo.manifest.changelog)"></div>
<Checkbox class="skip-backup" v-model="skipBackup" :label="$t('app.updateDialog.skipBackupCheckbox')" />
</div>
</Dialog>
<SettingsItem>
<div>
<label>{{ $t('app.updates.auto.title') }}</label>
<div v-if="app.appStoreId || app.versionsUrl" v-html="$t('app.updates.auto.description')"></div>
<div v-else>{{ $t('app.updates.info.customAppUpdateInfo') }}</div>
</div>
<Switch v-if="app.appStoreId || app.versionsUrl" v-model="autoUpdatesEnabled" :disabled="autoUpdatesEnabledBusy" @change="onAutoUpdatesEnabledChange"/>
</SettingsItem>
<hr style="margin-top: 20px"/>
<div v-if="app.appStoreId || app.versionsUrl">
<label>{{ $t('app.updatesTabTitle') }}</label>
<div v-html="$t('app.updates.updates.description', { appStoreLink: 'https://www.cloudron.io/store/index.html' })"></div>
</div>
<br/>
<Button v-if="app.appStoreId || app.versionsUrl" @click="onCheck()" :disabled="busyCheck" :loading="busyCheck">{{ $t('settings.updates.checkForUpdatesAction') }}</Button>
<hr v-if="app.updateInfo" style="margin-top: 20px"/>
<div v-if="app.updateInfo">
<label>{{ $t('settings.updates.updateAvailableAction') }}</label>
<div>{{ $t('app.updateDialog.changelogHeader', { version: app.updateInfo.manifest.version }) }}</div>
<div class="changelog" v-html="marked.parse(app.updateInfo.manifest.changelog)"></div>
<div class="error-label" style="margin-top: 12px" v-if="!features.appUpdates">{{ $t('app.updateDialog.subscriptionExpired') }}</div>
<div class="error-label" style="margin-top: 12px" v-if="updateError">{{ updateError }}</div>
<div class="error-label" style="margin-top: 12px" v-if="app.updateInfo.unstable">{{ $t('app.updateDialog.unstableWarning') }}</div>
</div>
<br/>
<Button v-if="app.updateInfo && features.appUpdates" :danger="app.updateInfo.unstable ? true : null" :success="app.updateInfo.unstable ? null : true" @click="onAskUpdate()" :disabled="app.taskId || (app.error && app.error.installationState !== ISTATES.PENDING_UPDATE) || app.runState === 'stopped' || app.installationState === 'pending_update'">{{ $t('app.updateDialog.updateAction') }}</Button>
<Button v-else-if="app.updateInfo && !features.appUpdates && profile.isAtLeastOwner" success href="/#/cloudron-account">{{ $t('app.updateDialog.setupSubscriptionAction') }}</Button>
</div>
</template>
<style scoped>
.changelog {
max-height: 20lh;
overflow-y: auto;
padding-right: 0.5rem; /* space so scrollbar doesnt overlap text */
}
.unstable-warning {
padding-bottom: 20px;
}
.skip-backup {
padding-top: 10px;
}
</style>
<style>
.changelog > ul {
margin-bottom: 0;
}
</style>