Individual BackupSchedule.vue is gone
This commit is contained in:
@@ -1,266 +0,0 @@
|
||||
<script setup>
|
||||
|
||||
import { useI18n } from 'vue-i18n';
|
||||
const i18n = useI18n();
|
||||
const t = i18n.t;
|
||||
|
||||
import { ref, onMounted, useTemplateRef, computed } from 'vue';
|
||||
import { Button, InputDialog, Dialog, FormGroup, MultiSelect } from '@cloudron/pankow';
|
||||
import { prettyLongDate } from '@cloudron/pankow/utils';
|
||||
import Section from '../components/Section.vue';
|
||||
import { TASK_TYPES } from '../constants.js';
|
||||
import TasksModel from '../models/TasksModel.js';
|
||||
import BackupsModel from '../models/BackupsModel.js';
|
||||
|
||||
const props = defineProps({
|
||||
profile: Object
|
||||
});
|
||||
|
||||
const backupsModel = BackupsModel.create();
|
||||
const tasksModel = TasksModel.create();
|
||||
|
||||
const backupRetentions = [
|
||||
{ name: '2 days', id: { keepWithinSecs: 2 * 24 * 60 * 60 }},
|
||||
{ name: '1 week', id: { keepWithinSecs: 7 * 24 * 60 * 60 }}, // default
|
||||
{ name: '1 month', id: { keepWithinSecs: 30 * 24 * 60 * 60 }},
|
||||
{ name: '3 months', id: { keepWithinSecs: 3 * 30 * 24 * 60 * 60 }},
|
||||
{ name: '2 daily, 4 weekly', id: { keepDaily: 2, keepWeekly: 4 }},
|
||||
{ name: '3 daily, 4 weekly, 6 monthly', id: { keepDaily: 3, keepWeekly: 4, keepMonthly: 6 }},
|
||||
{ name: '7 daily, 4 weekly, 12 monthly', id: { keepDaily: 7, keepWeekly: 4, keepMonthly: 12 }},
|
||||
{ name: 'Forever', id: { keepWithinSecs: -1 }}
|
||||
];
|
||||
|
||||
// values correspond to cron days
|
||||
const cronDays = [
|
||||
{ id: 0, name: 'Sunday' },
|
||||
{ id: 1, name: 'Monday' },
|
||||
{ id: 2, name: 'Tuesday' },
|
||||
{ id: 3, name: 'Wednesday' },
|
||||
{ id: 4, name: 'Thursday' },
|
||||
{ id: 5, name: 'Friday' },
|
||||
{ id: 6, name: 'Saturday' },
|
||||
];
|
||||
|
||||
// generates 24h time sets (instead of american 12h) to avoid having to translate everything to locales eg. 12:00
|
||||
const cronHours = Array.from({ length: 24 }).map(function (v, i) { return { id: i, name: (i < 10 ? '0' : '') + i + ':00' }; });
|
||||
|
||||
function prettyBackupSchedule(pattern) {
|
||||
if (!pattern) return '';
|
||||
|
||||
const tmp = pattern.split(' ');
|
||||
const hours = tmp[2].split(',');
|
||||
const days = tmp[5].split(',');
|
||||
|
||||
let prettyDay;
|
||||
if (days.length === 7 || days[0] === '*') {
|
||||
prettyDay = 'Every day';
|
||||
} else {
|
||||
prettyDay = days.map(function (day) { return cronDays[parseInt(day, 10)].name.substr(0, 3); }).join(', ');
|
||||
}
|
||||
|
||||
const prettyHour = hours.map(function (hour) { return cronHours[parseInt(hour, 10)].name; }).join(', ');
|
||||
|
||||
return prettyDay + ' at ' + prettyHour;
|
||||
};
|
||||
|
||||
function prettyBackupRetention(retention) {
|
||||
const retentionString = JSON.stringify(retention);
|
||||
const tmp = backupRetentions.find(function (p) { return JSON.stringify(p.id) === retentionString; });
|
||||
return tmp ? tmp.name : '';
|
||||
};
|
||||
|
||||
const inputDialog = useTemplateRef('inputDialog');
|
||||
const taskLogsMenu = ref([]);
|
||||
const policy = ref({
|
||||
schedule: '',
|
||||
retention: ''
|
||||
});
|
||||
const cleanupTask = ref({});
|
||||
|
||||
async function onCleanup() {
|
||||
const yes = await inputDialog.value.confirm({
|
||||
// title: t('backups.cleanupBackups.title'),
|
||||
message: t('backups.cleanupBackups.description'),
|
||||
confirmStyle: 'danger',
|
||||
confirmLabel: t('main.dialog.yes'),
|
||||
rejectLabel: t('main.dialog.no')
|
||||
});
|
||||
|
||||
if (!yes) return;
|
||||
|
||||
const [error] = await backupsModel.cleanup();
|
||||
if (error) return console.error(error);
|
||||
|
||||
await refreshTasks();
|
||||
}
|
||||
|
||||
const configureBusy = ref(false);
|
||||
const configureError = ref('');
|
||||
const configureDialog = useTemplateRef('configureDialog');
|
||||
const configureDays = ref([]);
|
||||
const configureHours = ref([]);
|
||||
const configureRetention = ref('');
|
||||
const isConfigureValid = computed(() => {
|
||||
return !!configureDays.value.length && !!configureHours.value.length;
|
||||
});
|
||||
|
||||
function onConfigure() {
|
||||
configureBusy.value = false;
|
||||
configureError.value = false;
|
||||
|
||||
const currentRetentionString = JSON.stringify(policy.value.retention);
|
||||
let selectedRetention = backupRetentions.find(function (x) { return JSON.stringify(x.id) === currentRetentionString; });
|
||||
if (!selectedRetention) selectedRetention = backupRetentions[0];
|
||||
configureRetention.value = selectedRetention.id;
|
||||
|
||||
const tmp = policy.value.schedule.split(' ');
|
||||
const hours = tmp[2].split(',');
|
||||
const days = tmp[5].split(',');
|
||||
if (days[0] === '*') configureDays.value = cronDays.map((day) => { return day.id; });
|
||||
else configureDays.value = days.map((day) => { return parseInt(day, 10); });
|
||||
|
||||
configureHours.value = hours.map((hour) => { return parseInt(hour, 10); });
|
||||
|
||||
configureDialog.value.open();
|
||||
}
|
||||
|
||||
async function onSubmitConfigure() {
|
||||
if (!isConfigureValid.value) return;
|
||||
|
||||
configureBusy.value = true;
|
||||
|
||||
let daysPattern;
|
||||
if (configureDays.value.length === 7) daysPattern = '*';
|
||||
else daysPattern = configureDays.value;
|
||||
|
||||
let hoursPattern;
|
||||
if (configureHours.value.length === 24) hoursPattern = '*';
|
||||
else hoursPattern = configureHours.value;
|
||||
|
||||
const policy = {
|
||||
retention: configureRetention.value,
|
||||
schedule: `00 00 ${hoursPattern} * * ${daysPattern}`
|
||||
};
|
||||
|
||||
const [error] = await backupsModel.setPolicy(policy);
|
||||
if (error) {
|
||||
configureBusy.value = false;
|
||||
configureError.value = error.body ? error.body.message : 'Internal error';
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
configureDialog.value.close();
|
||||
await refreshPolicy();
|
||||
configureBusy.value = false;
|
||||
}
|
||||
|
||||
async function waitForTask() {
|
||||
if (!cleanupTask.value.id) return;
|
||||
|
||||
const [error, result] = await tasksModel.get(cleanupTask.value.id);
|
||||
if (error) return console.error(error);
|
||||
|
||||
cleanupTask.value = result;
|
||||
|
||||
// task done, refresh menu
|
||||
if (!result.active) return await refreshTasks();
|
||||
|
||||
setTimeout(waitForTask, 2000);
|
||||
}
|
||||
|
||||
async function refreshTasks() {
|
||||
const [error, result] = await tasksModel.getByType(TASK_TYPES.TASK_CLEAN_BACKUPS);
|
||||
if (error) return console.error(error);
|
||||
|
||||
cleanupTask.value = result[0] || {};
|
||||
|
||||
// limit to last 10
|
||||
taskLogsMenu.value = result.slice(0,10).map(t => {
|
||||
return {
|
||||
icon: 'fa-solid ' + ((!t.active && t.success) ? 'status-active fa-check-circle' : (t.active ? 'fa-circle-notch fa-spin' : 'status-error fa-times-circle')),
|
||||
label: prettyLongDate(t.ts),
|
||||
action: () => { window.open(`/logs.html?taskId=${t.id}`); }
|
||||
};
|
||||
});
|
||||
|
||||
// if last task is currently active, start polling
|
||||
if (cleanupTask.value.active) waitForTask();
|
||||
}
|
||||
|
||||
async function refreshPolicy() {
|
||||
const [error, result] = await backupsModel.getPolicy();
|
||||
if (error) return console.error(error);
|
||||
|
||||
policy.value = result;
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
refreshTasks();
|
||||
refreshPolicy();
|
||||
});
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Section :title="$t('backups.schedule.title')">
|
||||
<InputDialog ref="inputDialog" />
|
||||
<Dialog ref="configureDialog"
|
||||
:title="$t('backups.configureBackupSchedule.title')"
|
||||
reject-style="secondary"
|
||||
:reject-label="configureBusy ? null : $t('main.dialog.cancel')"
|
||||
:confirm-label="$t('main.dialog.save')"
|
||||
:confirm-busy="configureBusy"
|
||||
:confirm-active="isConfigureValid"
|
||||
@confirm="onSubmitConfigure()"
|
||||
>
|
||||
<p class="has-error text-center" v-show="configureError">{{ configureError }}</p>
|
||||
|
||||
<form @submit.prevent="onSubmitConfigure()" autocomplete="off">
|
||||
<fieldset>
|
||||
<FormGroup>
|
||||
<label for="daysInput">{{ $t('backups.configureBackupSchedule.schedule') }}</label>
|
||||
<p v-html="$t('backups.configureBackupSchedule.scheduleDescription')"></p>
|
||||
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<div>{{ $t('backups.configureBackupSchedule.days') }}: <MultiSelect id="daysInput" v-model="configureDays" :options="cronDays" option-key="id" option-label="name"></MultiSelect></div>
|
||||
<div>{{ $t('backups.configureBackupSchedule.hours') }}: <MultiSelect v-model="configureHours" :options="cronHours" option-key="id" option-label="name"></MultiSelect></div>
|
||||
</div>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup>
|
||||
<label for="retentionInput">{{ $t('backups.configureBackupSchedule.retentionPolicy') }}</label>
|
||||
<select id="retentionInput" v-model="configureRetention">
|
||||
<option v-for="elem in backupRetentions" :key="elem.id" :value="elem.id">{{ elem.name }}</option>
|
||||
</select>
|
||||
</FormGroup>
|
||||
</fieldset>
|
||||
</form>
|
||||
</Dialog>
|
||||
|
||||
<template #header-buttons>
|
||||
<Button tool secondary :menu="taskLogsMenu" :disabled="!taskLogsMenu.length">{{ $t('main.action.logs') }}</Button>
|
||||
</template>
|
||||
|
||||
<p v-html="$t('backups.schedule.description')"></p>
|
||||
|
||||
<div class="info-row">
|
||||
<div class="info-label">{{ $t('backups.schedule.schedule') }}</div>
|
||||
<div class="info-value">{{ prettyBackupSchedule(policy.schedule) }}</div>
|
||||
</div>
|
||||
|
||||
<div class="info-row">
|
||||
<div class="info-label">{{ $t('backups.schedule.retentionPolicy') }}</div>
|
||||
<div class="info-value">{{ prettyBackupRetention(policy.retention) }}</div>
|
||||
</div>
|
||||
|
||||
<div v-if="cleanupTask.active">
|
||||
<ProgressBar :value="cleanupTask.percent" :busy="true" />
|
||||
<div>{{ cleanupTask.message }}</div>
|
||||
</div>
|
||||
|
||||
<div class="button-bar">
|
||||
<Button @click="onCleanup()" :disabled="cleanupTask.active" :loading="cleanupTask.active">{{ $t('backups.listing.cleanupBackups') }}</Button>
|
||||
<Button v-show="props.profile.isAtLeastOwner" @click="onConfigure()">{{ $t('backups.schedule.configure') }}</Button>
|
||||
</div>
|
||||
</Section>
|
||||
</template>
|
||||
Reference in New Issue
Block a user