mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 06:23:34 -07:00
backup-functionality to dashboard
This commit is contained in:
parent
2196841f7a
commit
ad71ab6603
10 changed files with 176 additions and 295 deletions
|
@ -1,8 +1,8 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-checkbox
|
||||
v-for="option in options"
|
||||
:key="option.text"
|
||||
v-for="(option, index) in options"
|
||||
:key="index"
|
||||
class="mb-n4 mt-n3"
|
||||
dense
|
||||
:label="option.text"
|
||||
|
@ -61,6 +61,4 @@ export default {
|
|||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped></style>
|
||||
</script>
|
142
frontend/src/components/UI/Dialogs/BackupDialog.vue
Normal file
142
frontend/src/components/UI/Dialogs/BackupDialog.vue
Normal file
|
@ -0,0 +1,142 @@
|
|||
<template>
|
||||
<div>
|
||||
<BaseDialog
|
||||
:title="$t('settings.backup.create-heading')"
|
||||
titleIcon="mdi-database"
|
||||
@submit="createBackup"
|
||||
:submit-text="$t('general.create')"
|
||||
:loading="loading"
|
||||
>
|
||||
<template v-slot:open="{ open }">
|
||||
<v-btn @click="open" class="mx-2" small :color="color"> <v-icon left> mdi-plus </v-icon> Custom </v-btn>
|
||||
</template>
|
||||
<v-card-text class="mt-6">
|
||||
<v-text-field dense :label="$t('settings.backup.backup-tag')" v-model="tag"></v-text-field>
|
||||
</v-card-text>
|
||||
<v-card-actions class="mt-n9 flex-wrap">
|
||||
<v-switch v-model="fullBackup" :label="switchLabel"></v-switch>
|
||||
<v-spacer></v-spacer>
|
||||
</v-card-actions>
|
||||
<v-expand-transition>
|
||||
<div v-if="!fullBackup">
|
||||
<v-card-text class="mt-n4">
|
||||
<v-row>
|
||||
<v-col sm="4">
|
||||
<p>{{ $t("general.options") }}</p>
|
||||
<ImportOptions @update-options="updateOptions" class="mt-5" />
|
||||
</v-col>
|
||||
<v-col>
|
||||
<p>{{ $t("general.templates") }}</p>
|
||||
<v-checkbox
|
||||
v-for="template in availableTemplates"
|
||||
:key="template"
|
||||
class="mb-n4 mt-n3"
|
||||
dense
|
||||
:label="template"
|
||||
@click="appendTemplate(template)"
|
||||
></v-checkbox>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
</BaseDialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import BaseDialog from "./BaseDialog";
|
||||
import ImportOptions from "@/components/FormHelpers/ImportOptions";
|
||||
import { api } from "@/api";
|
||||
export default {
|
||||
props: {
|
||||
color: { default: "primary" },
|
||||
},
|
||||
components: {
|
||||
BaseDialog,
|
||||
ImportOptions,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
tag: null,
|
||||
fullBackup: true,
|
||||
loading: false,
|
||||
options: {
|
||||
recipes: true,
|
||||
settings: true,
|
||||
themes: true,
|
||||
pages: true,
|
||||
users: true,
|
||||
groups: true,
|
||||
},
|
||||
availableTemplates: [],
|
||||
selectedTemplates: [],
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
switchLabel() {
|
||||
if (this.fullBackup) {
|
||||
return this.$t("settings.backup.full-backup");
|
||||
} else return this.$t("settings.backup.partial-backup");
|
||||
},
|
||||
},
|
||||
mounted() {
|
||||
this.resetData();
|
||||
this.getAvailableBackups();
|
||||
},
|
||||
methods: {
|
||||
resetData() {
|
||||
this.tag = null;
|
||||
this.fullBackup = true;
|
||||
this.loading = false;
|
||||
this.options = {
|
||||
recipes: true,
|
||||
settings: true,
|
||||
themes: true,
|
||||
pages: true,
|
||||
users: true,
|
||||
groups: true,
|
||||
};
|
||||
this.availableTemplates = [];
|
||||
this.selectedTemplates = [];
|
||||
},
|
||||
updateOptions(options) {
|
||||
this.options = options;
|
||||
},
|
||||
async getAvailableBackups() {
|
||||
const response = await api.backups.requestAvailable();
|
||||
response.templates.forEach(element => {
|
||||
this.availableTemplates.push(element);
|
||||
});
|
||||
},
|
||||
async createBackup() {
|
||||
this.loading = true;
|
||||
const data = {
|
||||
tag: this.tag,
|
||||
options: {
|
||||
recipes: this.options.recipes,
|
||||
settings: this.options.settings,
|
||||
pages: this.options.pages,
|
||||
themes: this.options.themes,
|
||||
users: this.options.users,
|
||||
groups: this.options.groups,
|
||||
},
|
||||
templates: this.selectedTemplates,
|
||||
};
|
||||
|
||||
if (await api.backups.create(data)) {
|
||||
this.$emit("created");
|
||||
}
|
||||
this.loading = false;
|
||||
},
|
||||
appendTemplate(templateName) {
|
||||
if (this.selectedTemplates.includes(templateName)) {
|
||||
let index = this.selectedTemplates.indexOf(templateName);
|
||||
if (index !== -1) {
|
||||
this.selectedTemplates.splice(index, 1);
|
||||
}
|
||||
} else this.selectedTemplates.push(templateName);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
|
@ -3,14 +3,14 @@
|
|||
<slot name="open" v-bind="{ open }"> </slot>
|
||||
<v-dialog v-model="dialog" :width="modalWidth + 'px'" :content-class="top ? 'top-dialog' : undefined">
|
||||
<v-card class="pb-10" height="100%">
|
||||
<v-app-bar dark :color="color" class="mt-n1 mb-2">
|
||||
<v-app-bar dark :color="color" class="mt-n1 mb-0">
|
||||
<v-icon large left>
|
||||
{{ titleIcon }}
|
||||
</v-icon>
|
||||
<v-toolbar-title class="headline"> {{ title }} </v-toolbar-title>
|
||||
<v-spacer></v-spacer>
|
||||
</v-app-bar>
|
||||
<v-progress-linear v-if="loading" indeterminate color="primary"></v-progress-linear>
|
||||
<v-progress-linear class="mt-1" v-if="loading" indeterminate color="primary"></v-progress-linear>
|
||||
<slot> </slot>
|
||||
<v-card-actions>
|
||||
<slot name="card-actions">
|
||||
|
@ -19,7 +19,7 @@
|
|||
</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="success" @click="submitEvent">
|
||||
{{ $t("general.submit") }}
|
||||
{{ submitText }}
|
||||
</v-btn>
|
||||
</slot>
|
||||
</v-card-actions>
|
||||
|
@ -31,6 +31,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import i18n from "@/i18n";
|
||||
export default {
|
||||
props: {
|
||||
color: {
|
||||
|
@ -51,16 +52,34 @@ export default {
|
|||
top: {
|
||||
default: false,
|
||||
},
|
||||
submitText: {
|
||||
default: () => i18n.t("general.create"),
|
||||
},
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
dialog: false,
|
||||
submitted: false,
|
||||
};
|
||||
},
|
||||
computed: {
|
||||
determineClose() {
|
||||
return this.submitted && !this.loading;
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
determineClose() {
|
||||
this.submitted = false;
|
||||
this.dialog = false;
|
||||
},
|
||||
dialog(val) {
|
||||
if (val) this.submitted = false;
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
submitEvent() {
|
||||
this.$emit("submit");
|
||||
this.close();
|
||||
this.submitted = true;
|
||||
},
|
||||
open() {
|
||||
this.dialog = true;
|
||||
|
|
|
@ -48,7 +48,7 @@
|
|||
</template>
|
||||
|
||||
<script>
|
||||
import ImportOptions from "./ImportOptions";
|
||||
import ImportOptions from "@/components/FormHelpers/ImportOptions";
|
||||
import TheDownloadBtn from "@/components/UI/Buttons/TheDownloadBtn.vue";
|
||||
import { backupURLs } from "@/api/backup";
|
||||
export default {
|
|
@ -167,11 +167,6 @@ export default {
|
|||
to: "/admin/manage-users",
|
||||
title: this.$t("settings.manage-users"),
|
||||
},
|
||||
{
|
||||
icon: "mdi-backup-restore",
|
||||
to: "/admin/backups",
|
||||
title: this.$t("settings.backup-and-exports"),
|
||||
},
|
||||
{
|
||||
icon: "mdi-database-import",
|
||||
to: "/admin/migrations",
|
||||
|
|
|
@ -1,79 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<ImportDialog
|
||||
:name="selectedName"
|
||||
:date="selectedDate"
|
||||
ref="import_dialog"
|
||||
@import="importBackup"
|
||||
@delete="deleteBackup"
|
||||
/>
|
||||
<v-row>
|
||||
<v-col :cols="12" :sm="6" :md="6" :lg="4" :xl="4" v-for="backup in backups" :key="backup.name">
|
||||
<v-card hover outlined @click="openDialog(backup)">
|
||||
<v-card-text>
|
||||
<v-row align="center">
|
||||
<v-col cols="2">
|
||||
<v-icon large color="primary">mdi-backup-restore</v-icon>
|
||||
</v-col>
|
||||
<v-col cols="10">
|
||||
<div class="text-truncate">
|
||||
<strong>{{ backup.name }}</strong>
|
||||
</div>
|
||||
<div class="text-truncate">{{ $d(Date.parse(backup.date), "medium") }}</div>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ImportDialog from "./ImportDialog";
|
||||
import { api } from "@/api";
|
||||
export default {
|
||||
props: {
|
||||
backups: Array,
|
||||
},
|
||||
components: {
|
||||
ImportDialog,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
selectedName: "",
|
||||
selectedDate: "",
|
||||
loading: false,
|
||||
};
|
||||
},
|
||||
methods: {
|
||||
openDialog(backup) {
|
||||
this.selectedDate = backup.date;
|
||||
this.selectedName = backup.name;
|
||||
this.$refs.import_dialog.open();
|
||||
},
|
||||
async importBackup(data) {
|
||||
this.$emit("loading");
|
||||
const response = await api.backups.import(data.name, data);
|
||||
if (response) {
|
||||
let importData = response.data;
|
||||
this.$emit("finished", importData);
|
||||
} else {
|
||||
this.$emit("finished");
|
||||
}
|
||||
},
|
||||
async deleteBackup(data) {
|
||||
this.$emit("loading");
|
||||
|
||||
if (await api.backups.delete(data.name)) {
|
||||
this.selectedBackup = null;
|
||||
}
|
||||
this.backupLoading = false;
|
||||
|
||||
this.$emit("finished");
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
|
@ -1,113 +0,0 @@
|
|||
<template>
|
||||
<v-card :loading="loading">
|
||||
<v-card-title> {{ $t("settings.backup.create-heading") }} </v-card-title>
|
||||
<v-card-text class="mt-n3">
|
||||
<v-text-field dense :label="$t('settings.backup.backup-tag')" v-model="tag"></v-text-field>
|
||||
</v-card-text>
|
||||
<v-card-actions class="mt-n9 flex-wrap">
|
||||
<v-switch v-model="fullBackup" :label="switchLabel"></v-switch>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="success" text @click="createBackup()">
|
||||
{{ $t("general.create") }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
<v-expand-transition>
|
||||
<div v-if="!fullBackup">
|
||||
<v-card-text class="mt-n4">
|
||||
<v-row>
|
||||
<v-col sm="4">
|
||||
<p>{{ $t("general.options") }}</p>
|
||||
<ImportOptions @update-options="updateOptions" class="mt-5" />
|
||||
</v-col>
|
||||
<v-col>
|
||||
<p>{{ $t("general.templates") }}</p>
|
||||
<v-checkbox
|
||||
v-for="template in availableTemplates"
|
||||
:key="template"
|
||||
class="mb-n4 mt-n3"
|
||||
dense
|
||||
:label="template"
|
||||
@click="appendTemplate(template)"
|
||||
></v-checkbox>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
</div>
|
||||
</v-expand-transition>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import ImportOptions from "./ImportOptions";
|
||||
import { api } from "@/api";
|
||||
export default {
|
||||
components: { ImportOptions },
|
||||
data() {
|
||||
return {
|
||||
tag: null,
|
||||
fullBackup: true,
|
||||
loading: false,
|
||||
options: {
|
||||
recipes: true,
|
||||
settings: true,
|
||||
themes: true,
|
||||
users: true,
|
||||
groups: true,
|
||||
},
|
||||
availableTemplates: [],
|
||||
selectedTemplates: [],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getAvailableBackups();
|
||||
},
|
||||
computed: {
|
||||
switchLabel() {
|
||||
if (this.fullBackup) {
|
||||
return this.$t("settings.backup.full-backup");
|
||||
} else return this.$t("settings.backup.partial-backup");
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
updateOptions(options) {
|
||||
this.options = options;
|
||||
},
|
||||
async getAvailableBackups() {
|
||||
let response = await api.backups.requestAvailable();
|
||||
response.templates.forEach(element => {
|
||||
this.availableTemplates.push(element);
|
||||
});
|
||||
},
|
||||
async createBackup() {
|
||||
this.loading = true;
|
||||
|
||||
let data = {
|
||||
tag: this.tag,
|
||||
options: {
|
||||
recipes: this.options.recipes,
|
||||
settings: this.options.settings,
|
||||
themes: this.options.themes,
|
||||
users: this.options.users,
|
||||
groups: this.options.groups,
|
||||
},
|
||||
templates: this.selectedTemplates,
|
||||
};
|
||||
|
||||
if (await api.backups.create(data)) {
|
||||
this.$emit("created");
|
||||
}
|
||||
this.loading = false;
|
||||
},
|
||||
appendTemplate(templateName) {
|
||||
if (this.selectedTemplates.includes(templateName)) {
|
||||
let index = this.selectedTemplates.indexOf(templateName);
|
||||
if (index !== -1) {
|
||||
this.selectedTemplates.splice(index, 1);
|
||||
}
|
||||
} else this.selectedTemplates.push(templateName);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
|
@ -1,75 +0,0 @@
|
|||
<template>
|
||||
<v-card :loading="backupLoading" class="mt-3">
|
||||
<v-card-title class="headline">
|
||||
{{ $t("settings.backup-and-exports") }}
|
||||
</v-card-title>
|
||||
<v-divider></v-divider>
|
||||
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col cols="12" md="6" sm="12">
|
||||
<NewBackupCard @created="processFinished" />
|
||||
</v-col>
|
||||
<v-col cols="12" md="6" sm="12">
|
||||
<p>
|
||||
{{ $t("settings.backup-info") }}
|
||||
</p>
|
||||
</v-col>
|
||||
</v-row>
|
||||
<v-divider class="my-3"></v-divider>
|
||||
<v-card-title class="mt-n6">
|
||||
{{ $t("settings.available-backups") }}
|
||||
<span>
|
||||
<TheUploadBtn class="mt-1" url="/api/backups/upload" @uploaded="getAvailableBackups" />
|
||||
</span>
|
||||
<v-spacer></v-spacer>
|
||||
</v-card-title>
|
||||
<AvailableBackupCard @loading="backupLoading = true" @finished="processFinished" :backups="availableBackups" />
|
||||
|
||||
<ImportSummaryDialog ref="report" :import-data="importData" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { api } from "@/api";
|
||||
import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
|
||||
import ImportSummaryDialog from "@/components/ImportSummaryDialog";
|
||||
import AvailableBackupCard from "@/pages/Admin/Backup/AvailableBackupCard";
|
||||
import NewBackupCard from "@/pages/Admin/Backup/NewBackupCard";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
TheUploadBtn,
|
||||
AvailableBackupCard,
|
||||
NewBackupCard,
|
||||
ImportSummaryDialog,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
failedImports: [],
|
||||
successfulImports: [],
|
||||
backupLoading: false,
|
||||
availableBackups: [],
|
||||
importData: [],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
this.getAvailableBackups();
|
||||
},
|
||||
methods: {
|
||||
async getAvailableBackups() {
|
||||
let response = await api.backups.requestAvailable();
|
||||
this.availableBackups = response.imports;
|
||||
this.availableTemplates = response.templates;
|
||||
},
|
||||
processFinished(data) {
|
||||
this.getAvailableBackups();
|
||||
this.backupLoading = false;
|
||||
this.$refs.report.open(data);
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style></style>
|
|
@ -26,6 +26,8 @@
|
|||
</v-btn>
|
||||
</template>
|
||||
</TheUploadBtn>
|
||||
<BackupDialog :color="color" />
|
||||
|
||||
<v-btn :loading="loading" class="mx-2" small :color="color" @click="createBackup">
|
||||
<v-icon left> mdi-plus </v-icon> Create
|
||||
</v-btn>
|
||||
|
@ -36,7 +38,7 @@
|
|||
<v-list-item @click.prevent="openDialog(item)">
|
||||
<v-list-item-avatar>
|
||||
<v-icon large dark :color="color">
|
||||
mdi-backup-restore
|
||||
mdi-database
|
||||
</v-icon>
|
||||
</v-list-item-avatar>
|
||||
|
||||
|
@ -66,9 +68,10 @@ import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn";
|
|||
import ImportSummaryDialog from "@/components/ImportSummaryDialog";
|
||||
import { api } from "@/api";
|
||||
import StatCard from "./StatCard";
|
||||
import ImportDialog from "../Backup/ImportDialog";
|
||||
import BackupDialog from "@/components/UI/Dialogs/BackupDialog";
|
||||
import ImportDialog from "@/components/UI/Dialogs/ImportDialog";
|
||||
export default {
|
||||
components: { StatCard, ImportDialog, TheUploadBtn, ImportSummaryDialog },
|
||||
components: { StatCard, ImportDialog, TheUploadBtn, ImportSummaryDialog, BackupDialog },
|
||||
data() {
|
||||
return {
|
||||
color: "accent",
|
||||
|
@ -91,7 +94,6 @@ export default {
|
|||
async getAvailableBackups() {
|
||||
const response = await api.backups.requestAvailable();
|
||||
this.availableBackups = response.imports;
|
||||
console.log(this.availableBackups);
|
||||
},
|
||||
|
||||
async deleteBackup(name) {
|
||||
|
@ -106,6 +108,7 @@ export default {
|
|||
this.selectedName = backup.name;
|
||||
this.$refs.import_dialog.open();
|
||||
},
|
||||
|
||||
async importBackup(data) {
|
||||
this.loading = true;
|
||||
const response = await api.backups.import(data.name, data);
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import Admin from "@/pages/Admin";
|
||||
import Backup from "@/pages/Admin/Backup";
|
||||
import Theme from "@/pages/Admin/Theme";
|
||||
import MealPlanner from "@/pages/Admin/MealPlanner";
|
||||
import Migration from "@/pages/Admin/Migration";
|
||||
|
@ -31,14 +30,6 @@ export const adminRoutes = {
|
|||
title: "settings.profile",
|
||||
},
|
||||
},
|
||||
|
||||
{
|
||||
path: "backups",
|
||||
component: Backup,
|
||||
meta: {
|
||||
title: "settings.backup-and-exports",
|
||||
},
|
||||
},
|
||||
{
|
||||
path: "themes",
|
||||
component: Theme,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue