dashboard: rename backupTargets to backupSites
This commit is contained in:
@@ -16,7 +16,7 @@ import AppsView from './views/AppsView.vue';
|
||||
import AppConfigureView from './views/AppConfigureView.vue';
|
||||
import AppearanceView from './views/AppearanceView.vue';
|
||||
import AppstoreView from './views/AppstoreView.vue';
|
||||
import BackupTargetsView from './views/BackupTargetsView.vue';
|
||||
import BackupSitesView from './views/BackupSitesView.vue';
|
||||
import BackupAppArchivesView from './views/BackupAppArchivesView.vue';
|
||||
import BackupListView from './views/BackupListView.vue';
|
||||
import CloudronAccountView from './views/CloudronAccountView.vue';
|
||||
@@ -46,7 +46,7 @@ const VIEWS = {
|
||||
APPEARANCE: 'appearance',
|
||||
APPS: 'apps',
|
||||
APPSTORE: 'appstore',
|
||||
BACKUP_TARGETS: 'backup-targets',
|
||||
BACKUP_SITES: 'backup-sites',
|
||||
BACKUP_LIST: 'backup-list',
|
||||
BACKUP_APP_ARCHIVES: 'backup-app-archives',
|
||||
CLOUDRON_ACCOUNT: 'cloudron-account',
|
||||
@@ -145,8 +145,8 @@ function onHashChange() {
|
||||
view.value = VIEWS.APP;
|
||||
} else if (v === VIEWS.APPEARANCE && profile.value.isAtLeastAdmin) {
|
||||
view.value = VIEWS.APPEARANCE;
|
||||
} else if (v === VIEWS.BACKUP_TARGETS && profile.value.isAtLeastAdmin) {
|
||||
view.value = VIEWS.BACKUP_TARGETS;
|
||||
} else if (v === VIEWS.BACKUP_SITES && profile.value.isAtLeastAdmin) {
|
||||
view.value = VIEWS.BACKUP_SITES;
|
||||
} else if (v === VIEWS.BACKUP_LIST && profile.value.isAtLeastAdmin) {
|
||||
view.value = VIEWS.BACKUP_LIST;
|
||||
} else if (v === VIEWS.BACKUP_APP_ARCHIVES && profile.value.isAtLeastAdmin) {
|
||||
@@ -282,7 +282,7 @@ onMounted(async () => {
|
||||
<Transition name="sidebar-item-group-animation">
|
||||
<div class="sidebar-item-group" v-if="activeSidebarGroup === 'backup'">
|
||||
<a class="sidebar-item" :class="{ active: activeSidebarItem === 'backup-list' }" href="#/backup-list" @click="onSidebarClose()"><i class="fa fa-fw fa-list-check"></i> {{ $t('backups.listing.title') }}</a>
|
||||
<a class="sidebar-item" :class="{ active: activeSidebarItem === 'backup-targets' }" href="#/backup-targets" @click="onSidebarClose()"><i class="fa fa-fw fa-hard-drive"></i> Storage</a>
|
||||
<a class="sidebar-item" :class="{ active: activeSidebarItem === 'backup-sites' }" href="#/backup-sites" @click="onSidebarClose()"><i class="fa fa-fw fa-hard-drive"></i> Storage</a>
|
||||
<a class="sidebar-item" :class="{ active: activeSidebarItem === 'backup-app-archives' }" href="#/backup-app-archives" @click="onSidebarClose()"><i class="fa fa-fw fa-grip"></i> App Archives</a>
|
||||
</div>
|
||||
</Transition>
|
||||
@@ -335,7 +335,7 @@ onMounted(async () => {
|
||||
<AppConfigureView v-else-if="view === VIEWS.APP" />
|
||||
<AppearanceView v-else-if="view === VIEWS.APPEARANCE" />
|
||||
<AppstoreView v-else-if="view === VIEWS.APPSTORE" />
|
||||
<BackupTargetsView v-else-if="view === VIEWS.BACKUP_TARGETS" />
|
||||
<BackupSitesView v-else-if="view === VIEWS.BACKUP_SITES" />
|
||||
<BackupListView v-else-if="view === VIEWS.BACKUP_LIST" />
|
||||
<BackupAppArchivesView v-else-if="view === VIEWS.BACKUP_APP_ARCHIVES" />
|
||||
<CloudronAccountView v-else-if="view === VIEWS.CLOUDRON_ACCOUNT" />
|
||||
|
||||
@@ -2,11 +2,11 @@
|
||||
|
||||
import { ref, useTemplateRef, computed } from 'vue';
|
||||
import { Dialog, FormGroup, MultiSelect } from '@cloudron/pankow';
|
||||
import BackupTargetsModel from '../models/BackupTargetsModel.js';
|
||||
import BackupSitesModel from '../models/BackupSitesModel.js';
|
||||
|
||||
const emit = defineEmits([ 'success' ]);
|
||||
|
||||
const backupTargetsModel = BackupTargetsModel.create();
|
||||
const backupSitesModel = BackupSitesModel.create();
|
||||
|
||||
const backupRetentions = [
|
||||
{ name: '2 days', id: { keepWithinSecs: 2 * 24 * 60 * 60 }},
|
||||
@@ -57,14 +57,14 @@ async function onSubmit() {
|
||||
if (hours.value.length === 24) hoursPattern = '*';
|
||||
else hoursPattern = hours.value;
|
||||
|
||||
let [error] = await backupTargetsModel.setSchedule(id.value, `00 00 ${hoursPattern} * * ${daysPattern}`);
|
||||
let [error] = await backupSitesModel.setSchedule(id.value, `00 00 ${hoursPattern} * * ${daysPattern}`);
|
||||
if (error) {
|
||||
busy.value = false;
|
||||
formError.value = error.body ? error.body.message : 'Internal error';
|
||||
return console.error(error);
|
||||
}
|
||||
|
||||
[error] = await backupTargetsModel.setRetention(id.value, configureRetention.value);
|
||||
[error] = await backupSitesModel.setRetention(id.value, configureRetention.value);
|
||||
if (error) {
|
||||
busy.value = false;
|
||||
formError.value = error.body ? error.body.message : 'Internal error';
|
||||
@@ -78,17 +78,17 @@ async function onSubmit() {
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
async open(target) {
|
||||
id.value = target.id;
|
||||
async open(site) {
|
||||
id.value = site.id;
|
||||
busy.value = false;
|
||||
formError.value = false;
|
||||
|
||||
const currentRetentionString = JSON.stringify(target.retention);
|
||||
const currentRetentionString = JSON.stringify(site.retention);
|
||||
let selectedRetention = backupRetentions.find(function (x) { return JSON.stringify(x.id) === currentRetentionString; });
|
||||
if (!selectedRetention) selectedRetention = backupRetentions[0];
|
||||
configureRetention.value = selectedRetention.id;
|
||||
|
||||
const tmp = target.schedule.split(' ');
|
||||
const tmp = site.schedule.split(' ');
|
||||
const tmpHours = tmp[2].split(',');
|
||||
const tmpDays = tmp[5].split(',');
|
||||
|
||||
|
||||
+6
-6
@@ -6,18 +6,18 @@ import { prettyBinarySize } from '@cloudron/pankow/utils';
|
||||
import { REGIONS_CONTABO, REGIONS_VULTR, REGIONS_IONOS, REGIONS_OVH, REGIONS_LINODE, REGIONS_SCALEWAY, REGIONS_WASABI } from '../constants.js';
|
||||
import { mountlike, s3like } from '../utils.js';
|
||||
import BackupProviderForm from './BackupProviderForm.vue';
|
||||
import BackupTargetsModel from '../models/BackupTargetsModel.js';
|
||||
import BackupSitesModel from '../models/BackupSitesModel.js';
|
||||
import SystemModel from '../models/SystemModel.js';
|
||||
|
||||
const emit = defineEmits([ 'success' ]);
|
||||
|
||||
const backupTargetsModel = BackupTargetsModel.create();
|
||||
const backupSitesModel = BackupSitesModel.create();
|
||||
const systemModel = SystemModel.create();
|
||||
|
||||
const dialog = useTemplateRef('dialog');
|
||||
const form = useTemplateRef('form');
|
||||
const step = ref('storage');
|
||||
const newTargetId = ref('');
|
||||
const newSiteId = ref('');
|
||||
const name = ref('');
|
||||
const useEncryption = ref(false);
|
||||
const encryptionPassword = ref('');
|
||||
@@ -146,7 +146,7 @@ async function onSubmit() {
|
||||
|
||||
formError.value = {};
|
||||
busy.value = true;
|
||||
const [error, result] = await backupTargetsModel.add(name.value, format.value, provider.value, data, schedulePattern, retention, limitsConfig);
|
||||
const [error, result] = await backupSitesModel.add(name.value, format.value, provider.value, data, schedulePattern, retention, limitsConfig);
|
||||
if (error) {
|
||||
formError.value.generic = error.body ? error.body.message : 'Internal error';
|
||||
busy.value = false;
|
||||
@@ -154,7 +154,7 @@ async function onSubmit() {
|
||||
}
|
||||
|
||||
// stash for encryption password step
|
||||
newTargetId.value = result;
|
||||
newSiteId.value = result;
|
||||
|
||||
busy.value = false;
|
||||
formError.value = {};
|
||||
@@ -178,7 +178,7 @@ async function onSetupEncryption() {
|
||||
|
||||
busy.value = true;
|
||||
|
||||
const [error] = await backupTargetsModel.setEncryption(newTargetId.value, encryptionPassword.value, encryptedFilenames.value, encryptionPasswordHint.value);
|
||||
const [error] = await backupSitesModel.setEncryption(newSiteId.value, encryptionPassword.value, encryptedFilenames.value, encryptionPasswordHint.value);
|
||||
if (error) {
|
||||
if (error.body && error.body.message.indexOf('password') === 0) {
|
||||
formError.value.password = error.body.message;
|
||||
+26
-26
@@ -4,19 +4,19 @@ import { ref, useTemplateRef } from 'vue';
|
||||
import { Dialog, FormGroup, TextInput } from '@cloudron/pankow';
|
||||
import { prettyBinarySize } from '@cloudron/pankow/utils';
|
||||
import { mountlike, s3like } from '../utils.js';
|
||||
import BackupTargetsModel from '../models/BackupTargetsModel.js';
|
||||
import BackupSitesModel from '../models/BackupSitesModel.js';
|
||||
import SystemModel from '../models/SystemModel.js';
|
||||
|
||||
const emit = defineEmits([ 'success' ]);
|
||||
|
||||
const backupTargetsModel = BackupTargetsModel.create();
|
||||
const backupSitesModel = BackupSitesModel.create();
|
||||
const systemModel = SystemModel.create();
|
||||
|
||||
const minMemoryLimit = ref(1024 * 1024 * 1024); // 1 GB
|
||||
const maxMemoryLimit = ref(minMemoryLimit.value); // set later
|
||||
|
||||
const dialog = useTemplateRef('dialog');
|
||||
const target = ref({});
|
||||
const site = ref({});
|
||||
const formError = ref({});
|
||||
const busy = ref(false);
|
||||
const name = ref('');
|
||||
@@ -29,7 +29,7 @@ const copyConcurrency = ref(0);
|
||||
async function onSubmit() {
|
||||
busy.value = true;
|
||||
|
||||
let [error] = await backupTargetsModel.setName(target.value.id, name.value);
|
||||
let [error] = await backupSitesModel.setName(site.value.id, name.value);
|
||||
if (error) {
|
||||
formError.value.generic = error.body ? error.body.message : 'Internal error';
|
||||
busy.value = false;
|
||||
@@ -44,7 +44,7 @@ async function onSubmit() {
|
||||
copyConcurrency: parseInt(copyConcurrency.value),
|
||||
};
|
||||
|
||||
[error] = await backupTargetsModel.setLimits(target.value.id, limits);
|
||||
[error] = await backupSitesModel.setLimits(site.value.id, limits);
|
||||
if (error) {
|
||||
formError.value.generic = error.body ? error.body.message : 'Internal error';
|
||||
busy.value = false;
|
||||
@@ -67,7 +67,7 @@ defineExpose({
|
||||
async open(t) {
|
||||
formError.value = {};
|
||||
busy.value = false;
|
||||
target.value = t;
|
||||
site.value = t;
|
||||
|
||||
name.value = t.name || '';
|
||||
memoryLimit.value = t.limits.memoryLimit || 1024 * 1024 * 1024; // 1 GB
|
||||
@@ -97,33 +97,33 @@ defineExpose({
|
||||
<div>
|
||||
<div class="info-row">
|
||||
<div class="info-label">{{ $t('backups.configureBackupStorage.provider') }}</div>
|
||||
<div class="info-value">{{ target.provider }}</div>
|
||||
<div class="info-value">{{ site.provider }}</div>
|
||||
</div>
|
||||
<div class="info-row" v-if="target.provider !== 'noop'">
|
||||
<div class="info-row" v-if="site.provider !== 'noop'">
|
||||
<div class="info-label">{{ $t('backups.configureBackupStorage.format') }}</div>
|
||||
<div class="info-value">{{ target.format }} <i v-if="target.encrypted" class="fa-solid fa-lock"></i></div>
|
||||
<div class="info-value">{{ site.format }} <i v-if="site.encrypted" class="fa-solid fa-lock"></i></div>
|
||||
</div>
|
||||
<div class="info-row" v-if="target.provider !== 'noop'">
|
||||
<div class="info-row" v-if="site.provider !== 'noop'">
|
||||
<div class="info-label">{{ $t('backups.location.location') }}</div>
|
||||
<div class="info-value" style="overflow: auto;">
|
||||
<span v-if="mountlike(target.provider) || target.provider === 'filesystem'">
|
||||
<span v-if="target.provider === 'filesystem'">{{ target.config.backupDir }}{{ (target.config.prefix ? `/${target.config.prefix}` : '') }}</span>
|
||||
<span v-if="target.provider === 'disk' || target.provider === 'ext4' || target.provider === 'xfs' || target.provider === 'mountpoint'">{{ target.config.mountOptions.diskPath || target.config.mountPoint }}{{ (target.config.prefix ? '/' : '') + target.config.prefix }}</span>
|
||||
<span v-if="target.provider === 'cifs' || target.provider === 'nfs' || target.provider === 'sshfs'">{{ target.config.mountOptions.host }}:{{ target.config.mountOptions.remoteDir }}{{ (target.config.prefix ? '/' : '') + target.config.prefix }}</span>
|
||||
<span v-if="mountlike(site.provider) || site.provider === 'filesystem'">
|
||||
<span v-if="site.provider === 'filesystem'">{{ site.config.backupDir }}{{ (site.config.prefix ? `/${site.config.prefix}` : '') }}</span>
|
||||
<span v-if="site.provider === 'disk' || site.provider === 'ext4' || site.provider === 'xfs' || site.provider === 'mountpoint'">{{ site.config.mountOptions.diskPath || site.config.mountPoint }}{{ (site.config.prefix ? '/' : '') + site.config.prefix }}</span>
|
||||
<span v-if="site.provider === 'cifs' || site.provider === 'nfs' || site.provider === 'sshfs'">{{ site.config.mountOptions.host }}:{{ site.config.mountOptions.remoteDir }}{{ (site.config.prefix ? '/' : '') + site.config.prefix }}</span>
|
||||
</span>
|
||||
|
||||
<span v-if="target.provider !== 's3' && target.provider !== 'minio' && (s3like(target.provider) || target.provider === 'gcs')">{{ target.config.bucket + (target.config.prefix ? '/' : '') + target.config.prefix }}</span>
|
||||
<span v-if="target.provider === 's3'">{{ target.config.region + ' ' + target.config.bucket + (target.config.prefix ? '/' : '') + target.config.prefix }}</span>
|
||||
<span v-if="target.provider === 'minio'">{{ target.config.endpoint + ' ' + target.config.bucket + (target.config.prefix ? '/' : '') + target.config.prefix }}</span>
|
||||
<span v-if="site.provider !== 's3' && site.provider !== 'minio' && (s3like(site.provider) || site.provider === 'gcs')">{{ site.config.bucket + (site.config.prefix ? '/' : '') + site.config.prefix }}</span>
|
||||
<span v-if="site.provider === 's3'">{{ site.config.region + ' ' + site.config.bucket + (site.config.prefix ? '/' : '') + site.config.prefix }}</span>
|
||||
<span v-if="site.provider === 'minio'">{{ site.config.endpoint + ' ' + site.config.bucket + (site.config.prefix ? '/' : '') + site.config.prefix }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="info-row" v-if="target.provider !== 'minio' && target.config?.endpoint">
|
||||
<div class="info-row" v-if="site.provider !== 'minio' && site.config?.endpoint">
|
||||
<div class="info-label">{{ $t('backups.location.endpoint') }}</div>
|
||||
<div class="info-value">{{ target.config.endpoint || target.config.region }}</div>
|
||||
<div class="info-value">{{ site.config.endpoint || site.config.region }}</div>
|
||||
</div>
|
||||
<div class="info-row" v-if="target.encrypted">
|
||||
<div class="info-row" v-if="site.encrypted">
|
||||
<div class="info-label">{{ $t('backups.configureBackupStorage.encryptionHint') }}</div>
|
||||
<div class="info-value">{{ target.encryptionPasswordHint }}</div>
|
||||
<div class="info-value">{{ site.encryptionPasswordHint }}</div>
|
||||
</div>
|
||||
|
||||
<form @submit.prevent="onSubmit()" autocomplete="off" ref="form">
|
||||
@@ -147,7 +147,7 @@ defineExpose({
|
||||
<input type="range" id="memoryLimitInput" v-model="memoryLimit" :step="256*1024*1024" :min="minMemoryLimit" :max="maxMemoryLimit" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="s3like(target.provider)">
|
||||
<FormGroup v-if="s3like(site.provider)">
|
||||
<label for="uploadPartSizeInput">{{ $t('backups.configureBackupStorage.uploadPartSize') }}: <b>{{ prettyBinarySize(uploadPartSize, 'Default (50 MiB)') }}</b></label>
|
||||
<p class="small">{{ $t('backups.configureBackupStorage.uploadPartSizeDescription') }}</p>
|
||||
<input type="range" id="uploadPartSizeInput" v-model="uploadPartSize" list="uploadPartSizeTicks" :step="1024*1024" :min="10*1024*1024" :max="1024*1024*1024" />
|
||||
@@ -161,22 +161,22 @@ defineExpose({
|
||||
</datalist>
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="target.format === 'rsync' && target.provider !== 'noop'">
|
||||
<FormGroup v-if="site.format === 'rsync' && site.provider !== 'noop'">
|
||||
<label for="syncConcurrencyInput">{{ $t('backups.configureBackupStorage.uploadConcurrency') }}: <b>{{ syncConcurrency }}</b></label>
|
||||
<div class="small">{{ $t('backups.configureBackupStorage.uploadConcurrencyDescription') }}</div>
|
||||
<input type="range" id="syncConcurrencyInput" v-model="syncConcurrency" step="10" min="10" max="200" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="target.format === 'rsync' && (s3like(target.provider) || target.provider === 'gcs')">
|
||||
<FormGroup v-if="site.format === 'rsync' && (s3like(site.provider) || site.provider === 'gcs')">
|
||||
<label for="downloadConcurrencyInput">{{ $t('backups.configureBackupStorage.downloadConcurrency') }}: <b>{{ downloadConcurrency }}</b></label>
|
||||
<div class="small">{{ $t('backups.configureBackupStorage.downloadConcurrencyDescription') }}</div>
|
||||
<input type="range" id="downloadConcurrencyInput" v-model="downloadConcurrency" step="10" min="10" max="200" />
|
||||
</FormGroup>
|
||||
|
||||
<FormGroup v-if="target.format === 'rsync' && (s3like(target.provider) || target.provider === 'gcs')">
|
||||
<FormGroup v-if="site.format === 'rsync' && (s3like(site.provider) || site.provider === 'gcs')">
|
||||
<label for="copyConcurrencyInput">{{ $t('backups.configureBackupStorage.copyConcurrency') }}: <b>{{ copyConcurrency }}</b></label>
|
||||
<div class="small">{{ $t('backups.configureBackupStorage.copyConcurrencyDescription') }}
|
||||
<span v-show="target.provider === 'digitalocean-spaces'">{{ $t('backups.configureBackupStorage.copyConcurrencyDigitalOceanNote') }}</span>
|
||||
<span v-show="site.provider === 'digitalocean-spaces'">{{ $t('backups.configureBackupStorage.copyConcurrencyDigitalOceanNote') }}</span>
|
||||
</div>
|
||||
<input type="range" id="copyConcurrencyInput" v-model="copyConcurrency" step="10" min="10" max="500" />
|
||||
</FormGroup>
|
||||
@@ -7,18 +7,18 @@ const t = i18n.t;
|
||||
import { ref, onMounted, useTemplateRef } from 'vue';
|
||||
import { Icon, Button, Switch, Checkbox, FormGroup, TextInput, TableView, Menu, Dialog, ProgressBar } from '@cloudron/pankow';
|
||||
import { prettyLongDate } from '@cloudron/pankow/utils';
|
||||
import { API_ORIGIN, RSTATES, TASK_TYPES } from '../../constants.js';
|
||||
import { API_ORIGIN, RSTATES } from '../../constants.js';
|
||||
import { download } from '../../utils.js';
|
||||
import AppImportDialog from '../AppImportDialog.vue';
|
||||
import AppRestoreDialog from '../AppRestoreDialog.vue';
|
||||
import SettingsItem from '../SettingsItem.vue';
|
||||
import AppsModel from '../../models/AppsModel.js';
|
||||
import BackupTargetsModel from '../../models/BackupTargetsModel.js';
|
||||
import BackupSitesModel from '../../models/BackupSitesModel.js';
|
||||
import TasksModel from '../../models/TasksModel.js';
|
||||
import BackupsModel from '../../models/BackupsModel.js';
|
||||
|
||||
const appsModel = AppsModel.create();
|
||||
const backupTargetsModel = BackupTargetsModel.create();
|
||||
const backupSitesModel = BackupSitesModel.create();
|
||||
const tasksModel = TasksModel.create();
|
||||
|
||||
const props = defineProps([ 'app' ]);
|
||||
@@ -38,7 +38,7 @@ const columns = ref({
|
||||
label: t('app.backups.backups.time'),
|
||||
sort: true,
|
||||
},
|
||||
target: {
|
||||
site: {
|
||||
label: t('backup.target.label'),
|
||||
sort: true,
|
||||
},
|
||||
@@ -59,7 +59,7 @@ function onActionMenu(backup, event) {
|
||||
}, {
|
||||
icon: 'fa-solid fa-download',
|
||||
label: t('app.backups.backups.downloadBackupTooltip'),
|
||||
visible: backup.target.format === 'tgz' && props.app.accessLevel === 'admin',
|
||||
visible: backup.site.format === 'tgz' && props.app.accessLevel === 'admin',
|
||||
action: getDownloadLink.bind(null, backup),
|
||||
}, {
|
||||
icon: 'fa-solid fa-file-alt',
|
||||
@@ -102,7 +102,7 @@ const taskLogsMenu = ref([]);
|
||||
const lastTask = ref({});
|
||||
const startBackupBusy = ref(false);
|
||||
const stopBackupBusy = ref(false);
|
||||
let backupTargets = [];
|
||||
let backupSites = [];
|
||||
|
||||
async function onChangeAutoBackups(value) {
|
||||
const [error] = await appsModel.configure(props.app.id, 'automatic_backup', { enable: value });
|
||||
@@ -197,14 +197,14 @@ function getDownloadLink(backup) {
|
||||
}
|
||||
|
||||
async function onDownloadConfig(backup) {
|
||||
const [error, backupTarget] = await backupTargetsModel.get(backup.targetId);
|
||||
const [error, backupSite] = await backupSitesModel.get(backup.siteId);
|
||||
if (error) return console.error(error);
|
||||
|
||||
const tmp = {
|
||||
remotePath: backup.remotePath
|
||||
};
|
||||
for (const k of ['provider', 'config', 'limits', 'format', 'encrypted', 'encryptedFilenames', 'encryptionPasswordHint']) {
|
||||
tmp[k] = backupTarget[k];
|
||||
tmp[k] = backupSite[k];
|
||||
}
|
||||
|
||||
const filename = `${props.app.fqdn}-backup-config-${(new Date(backup.creationTime)).toISOString().split('T')[0]}.json`;
|
||||
@@ -253,7 +253,7 @@ async function refreshBackupList() {
|
||||
if (error) return console.error(error);
|
||||
|
||||
result.forEach(backup => {
|
||||
backup.target = backupTargets.find(t => t.id === backup.targetId);
|
||||
backup.site = backupSites.find(t => t.id === backup.siteId);
|
||||
});
|
||||
backups.value = result;
|
||||
}
|
||||
@@ -261,10 +261,10 @@ async function refreshBackupList() {
|
||||
onMounted(async () => {
|
||||
autoBackupsEnabled.value = props.app.enableBackup;
|
||||
|
||||
const [error, result] = await backupTargetsModel.list();
|
||||
const [error, result] = await backupSitesModel.list();
|
||||
if (error) return console.error(error);
|
||||
|
||||
backupTargets = result;
|
||||
backupSites = result;
|
||||
|
||||
await refreshBackupList();
|
||||
await refreshTasks();
|
||||
@@ -378,8 +378,8 @@ onMounted(async () => {
|
||||
<template #creationTime="backup">
|
||||
{{ prettyLongDate(backup.creationTime) }} <b v-show="backup.label">({{ backup.label }})</b>
|
||||
</template>
|
||||
<template #target="backup">
|
||||
{{ backup.target ? backup.target.name : 'unknown' }}
|
||||
<template #site="backup">
|
||||
{{ backup.site ? backup.site.name : 'unknown' }}
|
||||
</template>
|
||||
<template #actions="backup">
|
||||
<div style="text-align: right;">
|
||||
|
||||
+16
-16
@@ -12,13 +12,13 @@ function create() {
|
||||
|
||||
let error, result;
|
||||
try {
|
||||
result = await fetcher.get(`${API_ORIGIN}/api/v1/backup_targets`, { page, per_page, access_token: accessToken });
|
||||
result = await fetcher.get(`${API_ORIGIN}/api/v1/backup_sites`, { page, per_page, access_token: accessToken });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
|
||||
if (error || result.status !== 200) return [error || result];
|
||||
return [null, result.body.backupTargets];
|
||||
return [null, result.body.backupSites];
|
||||
},
|
||||
async add(name, format, provider, config, schedule, retention, limits = null) {
|
||||
const data = { name, format, provider, config, schedule, retention };
|
||||
@@ -27,7 +27,7 @@ function create() {
|
||||
|
||||
let error, result;
|
||||
try {
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_targets`, data, { access_token: accessToken });
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_sites`, data, { access_token: accessToken });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
@@ -38,7 +38,7 @@ function create() {
|
||||
async get(id) {
|
||||
let error, result;
|
||||
try {
|
||||
result = await fetcher.get(`${API_ORIGIN}/api/v1/backup_targets/${id}`, { access_token: accessToken });
|
||||
result = await fetcher.get(`${API_ORIGIN}/api/v1/backup_sites/${id}`, { access_token: accessToken });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
@@ -50,7 +50,7 @@ function create() {
|
||||
async del(id) {
|
||||
let error, result;
|
||||
try {
|
||||
result = await fetcher.del(`${API_ORIGIN}/api/v1/backup_targets/${id}`, {}, { access_token: accessToken });
|
||||
result = await fetcher.del(`${API_ORIGIN}/api/v1/backup_sites/${id}`, {}, { access_token: accessToken });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
@@ -61,7 +61,7 @@ function create() {
|
||||
async createBackup(id) {
|
||||
let error, result;
|
||||
try {
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_targets/${id}/create_backup`, {}, { access_token: accessToken });
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_sites/${id}/create_backup`, {}, { access_token: accessToken });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
@@ -72,7 +72,7 @@ function create() {
|
||||
async setPrimary(id) {
|
||||
let error, result;
|
||||
try {
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_targets/${id}/configure/primary`, {}, { access_token: accessToken });
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_sites/${id}/configure/primary`, {}, { access_token: accessToken });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
@@ -83,7 +83,7 @@ function create() {
|
||||
async setRetention(id, retention) {
|
||||
let error, result;
|
||||
try {
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_targets/${id}/configure/retention`, { retention }, { access_token: accessToken });
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_sites/${id}/configure/retention`, { retention }, { access_token: accessToken });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
@@ -94,7 +94,7 @@ function create() {
|
||||
async setName(id, name) {
|
||||
let error, result;
|
||||
try {
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_targets/${id}/configure/name`, { name }, { access_token: accessToken });
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_sites/${id}/configure/name`, { name }, { access_token: accessToken });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
@@ -105,7 +105,7 @@ function create() {
|
||||
async setLimits(id, limits) {
|
||||
let error, result;
|
||||
try {
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_targets/${id}/configure/limits`, { limits }, { access_token: accessToken });
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_sites/${id}/configure/limits`, { limits }, { access_token: accessToken });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
@@ -116,7 +116,7 @@ function create() {
|
||||
async setSchedule(id, schedule) {
|
||||
let error, result;
|
||||
try {
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_targets/${id}/configure/schedule`, { schedule }, { access_token: accessToken });
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_sites/${id}/configure/schedule`, { schedule }, { access_token: accessToken });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
@@ -127,7 +127,7 @@ function create() {
|
||||
async setEncryption(id, encryptionPassword, encryptedFilenames, encryptionPasswordHint = '') {
|
||||
let error, result;
|
||||
try {
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_targets/${id}/configure/encryption`, { encryptionPassword, encryptedFilenames, encryptionPasswordHint }, { access_token: accessToken });
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_sites/${id}/configure/encryption`, { encryptionPassword, encryptedFilenames, encryptionPasswordHint }, { access_token: accessToken });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
@@ -138,7 +138,7 @@ function create() {
|
||||
async setConfig(id, config) {
|
||||
let error, result;
|
||||
try {
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_targets/${id}/configure/config`, { config }, { access_token: accessToken });
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_sites/${id}/configure/config`, { config }, { access_token: accessToken });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
@@ -149,7 +149,7 @@ function create() {
|
||||
async cleanup(id) {
|
||||
let error, result;
|
||||
try {
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_targets/${id}/cleanup`, {}, { access_token: accessToken });
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_sites/${id}/cleanup`, {}, { access_token: accessToken });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
@@ -160,7 +160,7 @@ function create() {
|
||||
async status(id) {
|
||||
let error, result;
|
||||
try {
|
||||
result = await fetcher.get(`${API_ORIGIN}/api/v1/backup_targets/${id}/status`, { access_token: accessToken });
|
||||
result = await fetcher.get(`${API_ORIGIN}/api/v1/backup_sites/${id}/status`, { access_token: accessToken });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
@@ -171,7 +171,7 @@ function create() {
|
||||
async remount(id) {
|
||||
let error, result;
|
||||
try {
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_targets/${id}/remount`, {}, { access_token: accessToken });
|
||||
result = await fetcher.post(`${API_ORIGIN}/api/v1/backup_sites/${id}/remount`, {}, { access_token: accessToken });
|
||||
} catch (e) {
|
||||
error = e;
|
||||
}
|
||||
@@ -11,14 +11,14 @@ import { TASK_TYPES } from '../constants.js';
|
||||
import Section from '../components/Section.vue';
|
||||
import SettingsItem from '../components/SettingsItem.vue';
|
||||
import BackupsModel from '../models/BackupsModel.js';
|
||||
import BackupTargetsModel from '../models/BackupTargetsModel.js';
|
||||
import BackupSitesModel from '../models/BackupSitesModel.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 backupsModel = BackupsModel.create();
|
||||
const backupTargetsModel = BackupTargetsModel.create();
|
||||
const backupSitesModel = BackupSitesModel.create();
|
||||
const appsModel = AppsModel.create();
|
||||
const tasksModel = TasksModel.create();
|
||||
const dashboardModel = DashboardModel.create();
|
||||
@@ -44,7 +44,7 @@ const columns = {
|
||||
sort: false,
|
||||
hideMobile: true,
|
||||
},
|
||||
target: {
|
||||
site: {
|
||||
label: t('backup.target.label'),
|
||||
sort(a, b) {
|
||||
return b.name <= a.name ? 1 : -1;
|
||||
@@ -78,13 +78,13 @@ const backups = ref([]);
|
||||
const taskLogsMenu = ref([]);
|
||||
const trackingBackupTask = ref({});
|
||||
const trackingCleanupTask = ref({});
|
||||
const targets = ref([]);
|
||||
const sites = ref([]);
|
||||
const startBackupError = ref('');
|
||||
const startBackupBusy = ref(false);
|
||||
const startCleanupError = ref('');
|
||||
const startCleanupBusy = ref(false);
|
||||
|
||||
let primaryTargetId = null;
|
||||
let primarySiteId = null;
|
||||
|
||||
async function waitForBackupTask(id) {
|
||||
if (!id || (trackingBackupTask.value.id && trackingBackupTask.value.id !== id)) return;
|
||||
@@ -127,8 +127,8 @@ async function waitForCleanupTask(id) {
|
||||
async function refreshTasks() {
|
||||
let tasks = [];
|
||||
|
||||
for (const target of targets.value) {
|
||||
const [error, result] = await tasksModel.getByType(TASK_TYPES.TASK_FULL_BACKUP_PREFIX + target.id);
|
||||
for (const site of sites.value) {
|
||||
const [error, result] = await tasksModel.getByType(TASK_TYPES.TASK_FULL_BACKUP_PREFIX + site.id);
|
||||
if (error) return console.error(error);
|
||||
|
||||
tasks = tasks.concat(result);
|
||||
@@ -137,8 +137,8 @@ async function refreshTasks() {
|
||||
if (result[0] && result[0].active) waitForBackupTask(result[0].id);
|
||||
}
|
||||
|
||||
for (const target of targets.value) {
|
||||
const [error, result] = await tasksModel.getByType(TASK_TYPES.TASK_CLEAN_BACKUPS_PREFIX + target.id);
|
||||
for (const site of sites.value) {
|
||||
const [error, result] = await tasksModel.getByType(TASK_TYPES.TASK_CLEAN_BACKUPS_PREFIX + site.id);
|
||||
if (error) return console.error(error);
|
||||
|
||||
tasks = tasks.concat(result);
|
||||
@@ -176,7 +176,7 @@ async function refreshBackups() {
|
||||
});
|
||||
|
||||
result.forEach(function (backup) {
|
||||
backup.target = targets.value.find(t => t.id === backup.targetId);
|
||||
backup.site = sites.value.find(t => t.id === backup.siteId);
|
||||
backup.contents = []; // { id, label, fqdn }
|
||||
backup.dependsOn.forEach(function (appBackupId) {
|
||||
const match = appBackupId.match(/app_(.*?)_.*/); // *? means non-greedy
|
||||
@@ -205,7 +205,7 @@ async function onStartBackup() {
|
||||
startBackupBusy.value = true;
|
||||
startBackupError.value = '';
|
||||
|
||||
const [error] = await backupTargetsModel.createBackup(primaryTargetId);
|
||||
const [error] = await backupSitesModel.createBackup(primarySiteId);
|
||||
if (error) {
|
||||
if (error.status === 409) {
|
||||
if (error.body.message.indexOf('full_backup') !== -1) startBackupError.value = 'Backup already in progress. Please retry later.';
|
||||
@@ -238,7 +238,7 @@ async function onStartCleanup() {
|
||||
startCleanupBusy.value = true;
|
||||
startCleanupError.value = '';
|
||||
|
||||
const [error] = await backupTargetsModel.cleanup(primaryTargetId);
|
||||
const [error] = await backupSitesModel.cleanup(primarySiteId);
|
||||
if (error) {
|
||||
if (error.status === 409) {
|
||||
if (error.body.message.indexOf('full_backup') !== -1) startCleanupError.value = 'Cleanup already in progress. Please retry later.';
|
||||
@@ -271,14 +271,14 @@ async function onDownloadConfig(backup) {
|
||||
const [error, dashboardConfig] = await dashboardModel.config();
|
||||
if (error) return console.error(error);
|
||||
|
||||
const [backupTargetError, backupTarget] = await backupTargetsModel.get(backup.targetId);
|
||||
if (backupTargetError) return console.error(backupTargetError);
|
||||
const [backupSiteError, backupSite] = await backupSitesModel.get(backup.siteId);
|
||||
if (backupSiteError) return console.error(backupSiteError);
|
||||
|
||||
const tmp = {
|
||||
remotePath: backup.remotePath
|
||||
};
|
||||
for (const k of ['provider', 'config', 'limits', 'format', 'encrypted', 'encryptedFilenames', 'encryptionPasswordHint']) {
|
||||
tmp[k] = backupTarget[k];
|
||||
tmp[k] = backupSite[k];
|
||||
}
|
||||
|
||||
const filename = `${dashboardConfig.adminFqdn}-backup-config-${(new Date(backup.creationTime)).toISOString().split('T')[0]}.json`;
|
||||
@@ -329,15 +329,15 @@ function onCopyToClipboard(value) {
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
const [error, result] = await backupTargetsModel.list();
|
||||
const [error, result] = await backupSitesModel.list();
|
||||
if (error) return console.error(error);
|
||||
|
||||
targets.value = result;
|
||||
sites.value = result;
|
||||
|
||||
const primaryTarget = result.find(t => t.primary);
|
||||
if (!primaryTarget) return;
|
||||
const primarySite = result.find(t => t.primary);
|
||||
if (!primarySite) return;
|
||||
|
||||
primaryTargetId = primaryTarget.id;
|
||||
primarySiteId = primarySite.id;
|
||||
|
||||
await refreshBackups();
|
||||
await refreshTasks();
|
||||
@@ -456,7 +456,7 @@ onMounted(async () => {
|
||||
<span v-if="backup.stats"> ({{ backup.stats.fileCount }} files<span v-if="backup.stats.size"> - {{ prettyFileSize(backup.stats.size) }}</span>)</span>
|
||||
</template>
|
||||
|
||||
<template #target="backup">{{ backup.target.name }}</template>
|
||||
<template #site="backup">{{ backup.site.name }}</template>
|
||||
|
||||
<template #actions="backup">
|
||||
<div style="text-align: right;">
|
||||
|
||||
+76
-76
@@ -11,39 +11,39 @@ import Section from '../components/Section.vue';
|
||||
import SettingsItem from '../components/SettingsItem.vue';
|
||||
import StateLED from '../components/StateLED.vue';
|
||||
import BackupScheduleDialog from '../components/BackupScheduleDialog.vue';
|
||||
import BackupTargetAddDialog from '../components/BackupTargetAddDialog.vue';
|
||||
import BackupTargetEditDialog from '../components/BackupTargetEditDialog.vue';
|
||||
import BackupSiteAddDialog from '../components/BackupSiteAddDialog.vue';
|
||||
import BackupSiteEditDialog from '../components/BackupSiteEditDialog.vue';
|
||||
import { TASK_TYPES } from '../constants.js';
|
||||
import BackupTargetsModel from '../models/BackupTargetsModel.js';
|
||||
import BackupSitesModel from '../models/BackupSitesModel.js';
|
||||
import ProfileModel from '../models/ProfileModel.js';
|
||||
import TasksModel from '../models/TasksModel.js';
|
||||
|
||||
const profileModel = ProfileModel.create();
|
||||
const tasksModel = TasksModel.create();
|
||||
const backupTargetsModel = BackupTargetsModel.create();
|
||||
const backupSitesModels = BackupSitesModel.create();
|
||||
|
||||
const inputDialog = useTemplateRef('inputDialog');
|
||||
|
||||
const profile = ref({});
|
||||
const targets = ref([]);
|
||||
const sites = ref([]);
|
||||
const busy = ref(false);
|
||||
|
||||
const backupTargetAddDialog = useTemplateRef('backupTargetAddDialog');
|
||||
const backupSiteAddDialog = useTemplateRef('backupSiteAddDialog');
|
||||
function onAdd() {
|
||||
backupTargetAddDialog.value.open();
|
||||
backupSiteAddDialog.value.open();
|
||||
}
|
||||
|
||||
const backupTargetEditDialog = useTemplateRef('backupTargetEditDialog');
|
||||
function onEdit(target) {
|
||||
backupTargetEditDialog.value.open(target);
|
||||
const backupSiteEditDialog = useTemplateRef('backupSiteEditDialog');
|
||||
function onEdit(site) {
|
||||
backupSiteEditDialog.value.open(site);
|
||||
}
|
||||
|
||||
const backupScheduleDialog = useTemplateRef('backupScheduleDialog');
|
||||
function onEditSchedule(target) {
|
||||
backupScheduleDialog.value.open(target);
|
||||
function onEditSchedule(site) {
|
||||
backupScheduleDialog.value.open(site);
|
||||
}
|
||||
|
||||
async function onRemoveTarget(target) {
|
||||
async function onRemoveSite(site) {
|
||||
const yes = await inputDialog.value.confirm({
|
||||
title: t('backup.target.removeDialog.title'),
|
||||
message: t('backup.target.removeDialog.description'),
|
||||
@@ -55,27 +55,27 @@ async function onRemoveTarget(target) {
|
||||
|
||||
if (!yes) return;
|
||||
|
||||
const [error] = await backupTargetsModel.del(target.id);
|
||||
const [error] = await backupSitesModels.del(site.id);
|
||||
if (error) console.error(error);
|
||||
|
||||
await refresh();
|
||||
}
|
||||
|
||||
async function onRemount(target) {
|
||||
target.status.busy = true;
|
||||
async function onRemount(site) {
|
||||
site.status.busy = true;
|
||||
|
||||
const [error] = await backupTargetsModel.remount(target.id);
|
||||
const [error] = await backupSitesModels.remount(site.id);
|
||||
if (error) return console.error(error);
|
||||
|
||||
const [statusError, status] = await backupTargetsModel.status(target.id);
|
||||
const [statusError, status] = await backupSitesModels.status(site.id);
|
||||
if (statusError) console.error(statusError);
|
||||
|
||||
target.status.state = status.state === 'active' ? 'success' : 'danger';
|
||||
target.status.busy = false;
|
||||
site.status.state = status.state === 'active' ? 'success' : 'danger';
|
||||
site.status.busy = false;
|
||||
}
|
||||
|
||||
async function onStartBackup(target) {
|
||||
const [error, result] = await backupTargetsModel.createBackup(target.id);
|
||||
async function onStartBackup(site) {
|
||||
const [error, result] = await backupSitesModels.createBackup(site.id);
|
||||
if (error) {
|
||||
if (error.status === 409) {
|
||||
// TODO
|
||||
@@ -89,109 +89,109 @@ async function onStartBackup(target) {
|
||||
const [taskError, task] = await tasksModel.get(result);
|
||||
if (taskError) return console.error(taskError);
|
||||
|
||||
target.task = task;
|
||||
site.task = task;
|
||||
|
||||
setTimeout(waitForTargetTask.bind(null,target), 2000);
|
||||
setTimeout(waitForSiteTask.bind(null,site), 2000);
|
||||
}
|
||||
|
||||
const actionMenuModel = ref([]);
|
||||
|
||||
const actionMenuElement = useTemplateRef('actionMenuElement');
|
||||
function onActionMenu(target, event) {
|
||||
function onActionMenu(site, event) {
|
||||
actionMenuModel.value = [{
|
||||
icon: 'fa-solid fa-plus',
|
||||
label: t('backups.listing.backupNow'),
|
||||
action: onStartBackup.bind(null, target),
|
||||
action: onStartBackup.bind(null, site),
|
||||
}, {
|
||||
icon: 'fa-solid fa-sync-alt',
|
||||
label: t('backups.location.remount'),
|
||||
visible: target.provider === 'sshfs' || target.provider === 'cifs' || target.provider === 'nfs' || target.provider === 'ext4' || target.provider === 'xfs',
|
||||
action: onRemount.bind(null, target),
|
||||
visible: site.provider === 'sshfs' || site.provider === 'cifs' || site.provider === 'nfs' || site.provider === 'ext4' || site.provider === 'xfs',
|
||||
action: onRemount.bind(null, site),
|
||||
}, {
|
||||
separator: true,
|
||||
}, {
|
||||
icon: 'fa-solid fa-clock',
|
||||
label: t('backups.schedule.title'),
|
||||
action: onEditSchedule.bind(null, target),
|
||||
action: onEditSchedule.bind(null, site),
|
||||
}, {
|
||||
icon: 'fa-solid fa-pencil-alt',
|
||||
label: t('main.dialog.edit'),
|
||||
action: onEdit.bind(null, target),
|
||||
action: onEdit.bind(null, site),
|
||||
}, {
|
||||
separator: true
|
||||
}, {
|
||||
icon: 'fa-solid fa-trash',
|
||||
label: t('volumes.removeVolumeDialog.removeAction'),
|
||||
disabled: target.primary,
|
||||
action: onRemoveTarget.bind(null, target),
|
||||
disabled: site.primary,
|
||||
action: onRemoveSite.bind(null, site),
|
||||
}];
|
||||
|
||||
actionMenuElement.value.open(event, event.currentTarget);
|
||||
}
|
||||
|
||||
const primaryTargetId = ref('');
|
||||
const primaryTargetChangeBusy = ref(false);
|
||||
const primarySiteId = ref('');
|
||||
const primarySiteChangeBusy = ref(false);
|
||||
|
||||
async function onPrimaryTargetChanged(value) {
|
||||
primaryTargetChangeBusy.value = true;
|
||||
async function onPrimarySiteChanged(value) {
|
||||
primarySiteChangeBusy.value = true;
|
||||
|
||||
const [error] = await backupTargetsModel.setPrimary(value);
|
||||
const [error] = await backupSitesModels.setPrimary(value);
|
||||
if (error) return console.error(error);
|
||||
|
||||
// update the list to be in sync without flickering
|
||||
targets.value.forEach(t => t.primary = t.id === value);
|
||||
sites.value.forEach(t => t.primary = t.id === value);
|
||||
|
||||
primaryTargetId.value = value;
|
||||
primaryTargetChangeBusy.value = false;
|
||||
primarySiteId.value = value;
|
||||
primarySiteChangeBusy.value = false;
|
||||
}
|
||||
|
||||
async function waitForTargetTask(target) {
|
||||
const [error, result] = await tasksModel.get(target.task.id);
|
||||
async function waitForSiteTask(site) {
|
||||
const [error, result] = await tasksModel.get(site.task.id);
|
||||
if (error) {
|
||||
console.error(error);
|
||||
setTimeout(waitForTargetTask.bind(null, target), 2000);
|
||||
setTimeout(waitForSiteTask.bind(null, site), 2000);
|
||||
} else if (result.active) {
|
||||
target.task = result;
|
||||
setTimeout(waitForTargetTask.bind(null, target), 2000);
|
||||
site.task = result;
|
||||
setTimeout(waitForSiteTask.bind(null, site), 2000);
|
||||
} else {
|
||||
target.task = result;
|
||||
site.task = result;
|
||||
}
|
||||
}
|
||||
|
||||
async function refresh() {
|
||||
busy.value = true;
|
||||
|
||||
const [error, result] = await backupTargetsModel.list();
|
||||
const [error, result] = await backupSitesModels.list();
|
||||
if (error) return console.error(error);
|
||||
|
||||
for (const target of result) {
|
||||
target.status = { busy: true, state: '', message: '' };
|
||||
for (const site of result) {
|
||||
site.status = { busy: true, state: '', message: '' };
|
||||
}
|
||||
|
||||
for (const target of result) {
|
||||
const [error, status] = await backupTargetsModel.status(target.id);
|
||||
for (const site of result) {
|
||||
const [error, status] = await backupSitesModels.status(site.id);
|
||||
if (error) {
|
||||
console.error(error);
|
||||
continue;
|
||||
}
|
||||
|
||||
target.status.state = status.state === 'active' ? 'success' : 'danger';
|
||||
target.status.busy = false;
|
||||
site.status.state = status.state === 'active' ? 'success' : 'danger';
|
||||
site.status.busy = false;
|
||||
|
||||
const [taskError, tasks] = await tasksModel.getByType(TASK_TYPES.TASK_FULL_BACKUP_PREFIX + target.id);
|
||||
const [taskError, tasks] = await tasksModel.getByType(TASK_TYPES.TASK_FULL_BACKUP_PREFIX + site.id);
|
||||
if (taskError) {
|
||||
console.error(error);
|
||||
continue;
|
||||
}
|
||||
|
||||
target.task = tasks[0] || null;
|
||||
site.task = tasks[0] || null;
|
||||
|
||||
if (target.task && target.task.active) waitForTargetTask(target);
|
||||
if (site.task && site.task.active) waitForSiteTask(site);
|
||||
}
|
||||
|
||||
primaryTargetId.value = result.find(t => t.primary)?.id;
|
||||
primarySiteId.value = result.find(t => t.primary)?.id;
|
||||
|
||||
targets.value = result;
|
||||
sites.value = result;
|
||||
busy.value = false;
|
||||
}
|
||||
|
||||
@@ -210,8 +210,8 @@ onMounted(async () => {
|
||||
<div class="content">
|
||||
<Menu ref="actionMenuElement" :model="actionMenuModel" />
|
||||
<InputDialog ref="inputDialog" />
|
||||
<BackupTargetAddDialog ref="backupTargetAddDialog" @success="refresh()"/>
|
||||
<BackupTargetEditDialog ref="backupTargetEditDialog" @success="refresh()"/>
|
||||
<BackupSiteAddDialog ref="backupSiteAddDialog" @success="refresh()"/>
|
||||
<BackupSiteEditDialog ref="backupSiteEditDialog" @success="refresh()"/>
|
||||
<BackupScheduleDialog ref="backupScheduleDialog" @success="refresh()"/>
|
||||
|
||||
<Section :title="$t('backup.target.title')">
|
||||
@@ -221,24 +221,24 @@ onMounted(async () => {
|
||||
|
||||
<div>
|
||||
<ProgressBar mode="indeterminate" v-if="busy" slim :show-label="false" />
|
||||
<div class="backup-target" v-for="target in targets" :key="target.id">
|
||||
<div class="backup-site" v-for="site in sites" :key="site.id">
|
||||
<div style="display: flex; align-items: center; gap: 10px">
|
||||
<StateLED :busy="target.status.busy" :state="target.status.state"/>
|
||||
<div class="backup-target-details">
|
||||
<div><b style="font-size: 16px">{{ target.name }}</b> <span v-if="target.primary" class="text-muted">- used for updates</span></div>
|
||||
<StateLED :busy="site.status.busy" :state="site.status.state"/>
|
||||
<div class="backup-site-details">
|
||||
<div><b style="font-size: 16px">{{ site.name }}</b> <span v-if="site.primary" class="text-muted">- used for updates</span></div>
|
||||
<div>
|
||||
{{ $t('backups.configureBackupStorage.provider') }}: <b>{{ target.provider }}</b> - {{ $t('backups.configureBackupStorage.format') }}: <b>{{ target.format }}</b> <i v-if="target.encrypted" class="fa-solid fa-lock"></i></div>
|
||||
<div class="backup-target-task">
|
||||
<div v-if="target.task && target.task.success">Last backup: <b>{{ prettyLongDate(target.task.ts) }}</b></div>
|
||||
<div v-if="target.task && target.task.error">Error: <b>{{ target.task.error }}</b></div>
|
||||
<div v-else-if="target.task && target.task.running">
|
||||
<ProgressBar :value="target.task.percent" /> {{ target.task.message }}
|
||||
{{ $t('backups.configureBackupStorage.provider') }}: <b>{{ site.provider }}</b> - {{ $t('backups.configureBackupStorage.format') }}: <b>{{ site.format }}</b> <i v-if="site.encrypted" class="fa-solid fa-lock"></i></div>
|
||||
<div class="backup-site-task">
|
||||
<div v-if="site.task && site.task.success">Last backup: <b>{{ prettyLongDate(site.task.ts) }}</b></div>
|
||||
<div v-if="site.task && site.task.error">Error: <b>{{ site.task.error }}</b></div>
|
||||
<div v-else-if="site.task && site.task.running">
|
||||
<ProgressBar :value="site.task.percent" /> {{ site.task.message }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center;">
|
||||
<Button tool plain secondary @click.capture="onActionMenu(target, $event)" icon="fa-solid fa-ellipsis" />
|
||||
<Button tool plain secondary @click.capture="onActionMenu(site, $event)" icon="fa-solid fa-ellipsis" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -251,7 +251,7 @@ onMounted(async () => {
|
||||
<div>{{ $t('backup.updateTarget.description') }}</div>
|
||||
</div>
|
||||
<div style="display: flex; align-items: center">
|
||||
<SingleSelect style="min-width: 160px" :disabled="primaryTargetChangeBusy" v-model="primaryTargetId" :searchThreshold="10" :options="targets" option-key="id" option-label="name" @select="onPrimaryTargetChanged" />
|
||||
<SingleSelect style="min-width: 160px" :disabled="primarySiteChangeBusy" v-model="primarySiteId" :searchThreshold="10" :options="sites" option-key="id" option-label="name" @select="onPrimarySiteChanged" />
|
||||
</div>
|
||||
</SettingsItem>
|
||||
</Section>
|
||||
@@ -260,7 +260,7 @@ onMounted(async () => {
|
||||
|
||||
<style scoped>
|
||||
|
||||
.backup-target {
|
||||
.backup-site {
|
||||
display: flex;
|
||||
border-radius: var(--pankow-border-radius);
|
||||
padding: 10px;
|
||||
@@ -268,17 +268,17 @@ onMounted(async () => {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.backup-target:hover {
|
||||
.backup-site:hover {
|
||||
background-color: var(--pankow-color-background-hover);
|
||||
}
|
||||
|
||||
.backup-target-details {
|
||||
.backup-site-details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.backup-target-action {
|
||||
.backup-site-action {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
flex-direction: column;
|
||||
Reference in New Issue
Block a user