Add backup listing
This commit is contained in:
@@ -4,21 +4,28 @@ import { useI18n } from 'vue-i18n';
|
||||
const i18n = useI18n();
|
||||
const t = i18n.t;
|
||||
|
||||
import { ref, onMounted, useTemplateRef, computed } from 'vue';
|
||||
import { Button, InputDialog, Dialog, FormGroup, TableView } from 'pankow';
|
||||
import { ref, onMounted, useTemplateRef } from 'vue';
|
||||
import { Button, ProgressBar, TableView, Dialog } from 'pankow';
|
||||
import { prettyLongDate } from 'pankow/utils';
|
||||
import { TASK_TYPES } from '../constants.js';
|
||||
import { TASK_TYPES, SECRET_PLACEHOLDER } from '../constants.js';
|
||||
import Section from '../components/Section.vue';
|
||||
import BackupsModel from '../models/BackupsModel.js';
|
||||
import AppsModel from '../models/AppsModel.js';
|
||||
import TasksModel from '../models/TasksModel.js';
|
||||
import DashboardModel from '../models/DashboardModel.js';
|
||||
import { download } from '../utils.js';
|
||||
|
||||
const props = defineProps({
|
||||
config: Object
|
||||
});
|
||||
|
||||
const backupsModel = BackupsModel.create();
|
||||
const appsModel = AppsModel.create();
|
||||
const tasksModel = TasksModel.create();
|
||||
const dashboardModel = DashboardModel.create();
|
||||
|
||||
const columns = {
|
||||
archived: {},
|
||||
preserveSecs: {}, // archived
|
||||
packageVersion: { label: t('backups.listing.version'), sort: true },
|
||||
creationTime: { label: t('main.table.date'), sort: true },
|
||||
content: { label: t('backups.listing.contents'), sort: false },
|
||||
@@ -28,15 +35,15 @@ const columns = {
|
||||
const busy = ref(true);
|
||||
const backups = ref([]);
|
||||
const taskLogsMenu = ref([]);
|
||||
const backupTask = ref({});
|
||||
const lastTask = ref({});
|
||||
|
||||
async function waitForTask() {
|
||||
if (!backupTask.value.id) return;
|
||||
if (!lastTask.value.id) return;
|
||||
|
||||
const [error, result] = await tasksModel.get(backupTask.value.id);
|
||||
const [error, result] = await tasksModel.get(lastTask.value.id);
|
||||
if (error) return console.error(error);
|
||||
|
||||
backupTask.value = result;
|
||||
lastTask.value = result;
|
||||
|
||||
// task done, refresh menu
|
||||
if (!result.active) return await refreshTasks();
|
||||
@@ -48,7 +55,7 @@ async function refreshTasks() {
|
||||
const [error, result] = await tasksModel.getByType(TASK_TYPES.TASK_BACKUP);
|
||||
if (error) return console.error(error);
|
||||
|
||||
backupTask.value = result[0] || {};
|
||||
lastTask.value = result[0] || {};
|
||||
|
||||
// limit to last 10
|
||||
taskLogsMenu.value = result.slice(0,10).map(t => {
|
||||
@@ -60,7 +67,7 @@ async function refreshTasks() {
|
||||
});
|
||||
|
||||
// if last task is currently active, start polling
|
||||
if (backupTask.value.active) waitForTask();
|
||||
if (lastTask.value.active) waitForTask();
|
||||
}
|
||||
|
||||
async function refreshBackups() {
|
||||
@@ -103,6 +110,58 @@ async function refreshBackups() {
|
||||
|
||||
backups.value = result;
|
||||
}
|
||||
|
||||
async function onStartBackup() {
|
||||
const [error] = await backupsModel.create();
|
||||
if (error) {
|
||||
if (error.status === 409 && error.message.indexOf('full_backup') !== -1) {
|
||||
// TODO
|
||||
// $scope.createBackup.errorMessage = 'Backup already in progress. Please retry later.';
|
||||
} else if (error.statusCode === 409) {
|
||||
// TODO
|
||||
// $scope.createBackup.errorMessage = 'App task is currently in progress. Please retry later.';
|
||||
}
|
||||
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
await refreshTasks();
|
||||
}
|
||||
|
||||
async function onStopBackup() {
|
||||
const [error] = await tasksModel.stop(lastTask.value.id);
|
||||
if (error) return console.error(error);
|
||||
}
|
||||
|
||||
async function onDownloadConfig(backup) {
|
||||
const [error, result] = await dashboardModel.getConfig();
|
||||
if (error) return console.error(error);
|
||||
|
||||
// secrets and tokens already come with placeholder characters we remove them
|
||||
const tmp = {
|
||||
remotePath: backup.remotePath,
|
||||
encrypted: !!props.config.password // we add this just to help the import UI
|
||||
};
|
||||
|
||||
Object.keys(props.config).forEach((k) => {
|
||||
if (props.config[k] !== SECRET_PLACEHOLDER) tmp[k] = props.config[k];
|
||||
});
|
||||
|
||||
const filename = `${result.adminFqdn}-backup-config-${(new Date(backup.creationTime)).toISOString().split('T')[0]}.json`;
|
||||
download(filename, JSON.stringify(tmp, null, 4));
|
||||
}
|
||||
|
||||
function onEdit(backup) {
|
||||
console.log('edit', backup);
|
||||
}
|
||||
|
||||
const infoDialog = useTemplateRef('infoDialog');
|
||||
const infoBackup = ref({ contents: [] });
|
||||
function onInfo(backup) {
|
||||
infoBackup.value = backup;
|
||||
infoDialog.value.open();
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await refreshBackups();
|
||||
await refreshTasks();
|
||||
@@ -112,13 +171,51 @@ onMounted(async () => {
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Dialog ref="infoDialog"
|
||||
:title="$t('backups.backupDetails.title')"
|
||||
:reject-label="$t('main.dialog.cancel')"
|
||||
>
|
||||
<div class="info-row">
|
||||
<div class="info-label">{{ $t('backups.backupDetails.id') }}</div>
|
||||
<div class="info-value">{{ infoBackup.id }}</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">{{ $t('backups.backupEdit.label') }}</div>
|
||||
<div class="info-value">{{ infoBackup.label }}</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">{{ $t('backups.backupEdit.remotePath') }}</div>
|
||||
<div class="info-value">{{ infoBackup.remotePath }}</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">{{ $t('backups.backupDetails.date') }}</div>
|
||||
<div class="info-value">{{ prettyLongDate(infoBackup.creationTime) }}</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">{{ $t('backups.backupDetails.version') }}</div>
|
||||
<div class="info-value">{{ infoBackup.packageVersion }}</div>
|
||||
</div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">{{ $t('backups.backupDetails.format') }}</div>
|
||||
<div class="info-value">{{ infoBackup.format }}</div>
|
||||
</div>
|
||||
|
||||
<br/>
|
||||
|
||||
<p class="text-muted">{{ $t('backups.backupDetails.list', { appCount: infoBackup.contents.length }) }}:</p>
|
||||
<span v-for="content in infoBackup.contents" :key="content.id">
|
||||
<a v-if="content.fqdn" ng-href="/#/app/{{content.id}}/backups">{{ content.label || content.fqdn }}</a>
|
||||
<a v-else :href="`/#/eventlog?search=${content.id}`">{{ content.id }}</a>
|
||||
</span>
|
||||
</Dialog>
|
||||
|
||||
<Section :title="$t('backups.listing.title')">
|
||||
<template #header-buttons>
|
||||
<Button tool icon="fas fa-align-left" v-tooltip="$t('settings.updates.showLogsAction')" :menu="taskLogsMenu" :disabled="!taskLogsMenu.length"/>
|
||||
</template>
|
||||
|
||||
<TableView :columns="columns" :model="backups" :busy="busy">
|
||||
<template #archived="slotProps">
|
||||
<TableView :columns="columns" :model="backups" :busy="busy" @row-click="onInfo">
|
||||
<template #preserveSecs="slotProps">
|
||||
<i class="fas fa-archive" v-show="slotProps.preserveSecs === -1" v-tooltip="$t('backups.listing.tooltipPreservedBackup')"></i>
|
||||
</template>
|
||||
|
||||
@@ -131,10 +228,21 @@ onMounted(async () => {
|
||||
|
||||
<template #actions="slotProps">
|
||||
<div class="table-actions">
|
||||
<Button tool secondary small icon="fa-solid fa-pencil-alt" v-tooltip="$t('backups.listing.tooltipEditBackup')" @click="onEdit(slotProps)"></Button>
|
||||
<Button tool secondary small icon="fa-solid fa-file-alt" v-tooltip="$t('backups.listing.tooltipDownloadBackupConfig')" @click="onDownloadConfig(slotProps)"></Button>
|
||||
<Button tool secondary small icon="fa-solid fa-pencil-alt" v-tooltip="$t('backups.listing.tooltipEditBackup')" @click.stop="onEdit(slotProps)"></Button>
|
||||
<Button tool secondary small icon="fa-solid fa-file-alt" v-tooltip="$t('backups.listing.tooltipDownloadBackupConfig')" @click.stop="onDownloadConfig(slotProps)"></Button>
|
||||
</div>
|
||||
</template>
|
||||
</TableView>
|
||||
|
||||
<div v-if="lastTask.active">
|
||||
<ProgressBar :value="lastTask.percent" />
|
||||
<div>{{ lastTask.message }}</div>
|
||||
</div>
|
||||
|
||||
<!-- TODO show last task error message if any -->
|
||||
<br/>
|
||||
|
||||
<Button danger @click="onStopBackup()" v-if="lastTask.active">{{ $t('backups.listing.stopTask') }}</Button>
|
||||
<Button @click="onStartBackup()" v-else>{{ $t('backups.listing.backupNow') }}</Button>
|
||||
</Section>
|
||||
</template>
|
||||
|
||||
Reference in New Issue
Block a user