mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 06:23:34 -07:00
unified upload button + download backups
This commit is contained in:
parent
51893e89cd
commit
eff9d6f559
8 changed files with 118 additions and 30 deletions
|
@ -4,8 +4,7 @@ import mealplan from "./api/mealplan";
|
|||
import settings from "./api/settings";
|
||||
import themes from "./api/themes";
|
||||
import migration from "./api/migration";
|
||||
|
||||
// import api from "../api";
|
||||
import myUtils from "./api/upload";
|
||||
|
||||
export default {
|
||||
recipes: recipe,
|
||||
|
@ -14,4 +13,5 @@ export default {
|
|||
settings: settings,
|
||||
themes: themes,
|
||||
migrations: migration,
|
||||
utils: myUtils,
|
||||
};
|
||||
|
|
|
@ -16,8 +16,8 @@ function processResponse(response) {
|
|||
}
|
||||
|
||||
const apiReq = {
|
||||
post: async function(url, data) {
|
||||
let response = await axios.post(url, data).catch(function(error) {
|
||||
post: async function (url, data) {
|
||||
let response = await axios.post(url, data).catch(function (error) {
|
||||
if (error.response) {
|
||||
processResponse(error.response);
|
||||
return error.response;
|
||||
|
@ -27,8 +27,8 @@ const apiReq = {
|
|||
return response;
|
||||
},
|
||||
|
||||
get: async function(url, data) {
|
||||
let response = await axios.get(url, data).catch(function(error) {
|
||||
get: async function (url, data) {
|
||||
let response = await axios.get(url, data).catch(function (error) {
|
||||
if (error.response) {
|
||||
processResponse(error.response);
|
||||
return response;
|
||||
|
@ -38,8 +38,8 @@ const apiReq = {
|
|||
return response;
|
||||
},
|
||||
|
||||
delete: async function(url, data) {
|
||||
let response = await axios.delete(url, data).catch(function(error) {
|
||||
delete: async function (url, data) {
|
||||
let response = await axios.delete(url, data).catch(function (error) {
|
||||
if (error.response) {
|
||||
processResponse(error.response);
|
||||
return response;
|
||||
|
|
|
@ -10,6 +10,7 @@ const backupURLs = {
|
|||
createBackup: `${backupBase}export/database/`,
|
||||
importBackup: (fileName) => `${backupBase}${fileName}/import/`,
|
||||
deleteBackup: (fileName) => `${backupBase}${fileName}/delete/`,
|
||||
downloadBackup: (fileName) => `${backupBase}${fileName}/download/`,
|
||||
};
|
||||
|
||||
export default {
|
||||
|
@ -32,4 +33,8 @@ export default {
|
|||
let response = apiReq.post(backupURLs.createBackup, data);
|
||||
return response;
|
||||
},
|
||||
async download(fileName) {
|
||||
let response = await apiReq.get(backupURLs.downloadBackup(fileName));
|
||||
return response.data;
|
||||
},
|
||||
};
|
||||
|
|
13
frontend/src/api/upload.js
Normal file
13
frontend/src/api/upload.js
Normal file
|
@ -0,0 +1,13 @@
|
|||
import { apiReq } from "./api-utils";
|
||||
|
||||
export default {
|
||||
// import api from "../api";
|
||||
async uploadFile(url, fileObject) {
|
||||
let response = await apiReq.post(url, fileObject, {
|
||||
headers: {
|
||||
"Content-Type": "multipart/form-data",
|
||||
},
|
||||
});
|
||||
return response.data;
|
||||
},
|
||||
};
|
|
@ -8,7 +8,7 @@
|
|||
|
||||
<v-card-text>
|
||||
<v-row>
|
||||
<v-col >
|
||||
<v-col>
|
||||
<v-checkbox
|
||||
class="mb-n4 mt-1"
|
||||
dense
|
||||
|
@ -65,15 +65,15 @@
|
|||
<v-divider></v-divider>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn disabled color="success" text @click="raiseEvent('download')">
|
||||
{{$t('general.download')}}
|
||||
<v-btn color="success" text :href="`/api/backups/${name}/download/`">
|
||||
{{ $t("general.download") }}
|
||||
</v-btn>
|
||||
<v-spacer></v-spacer>
|
||||
<v-btn color="error" text @click="raiseEvent('delete')">
|
||||
{{$t('general.delete')}}
|
||||
{{ $t("general.delete") }}
|
||||
</v-btn>
|
||||
<v-btn color="success" text @click="raiseEvent('import')">
|
||||
{{$t('general.import')}}
|
||||
{{ $t("general.import") }}
|
||||
</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
|
|
|
@ -21,10 +21,10 @@
|
|||
Available Backups
|
||||
<v-spacer></v-spacer>
|
||||
<span>
|
||||
<v-btn color="success" text class="ma-2 white--text">
|
||||
Upload
|
||||
<v-icon right dark> mdi-cloud-upload </v-icon>
|
||||
</v-btn>
|
||||
<UploadBtn
|
||||
url="/api/backups/upload/"
|
||||
@uploaded="getAvailableBackups"
|
||||
/>
|
||||
</span>
|
||||
</v-card-title>
|
||||
<AvailableBackupCard
|
||||
|
@ -45,12 +45,14 @@
|
|||
<script>
|
||||
import api from "../../../api";
|
||||
import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
|
||||
import UploadBtn from "../../UI/UploadBtn";
|
||||
import AvailableBackupCard from "./AvailableBackupCard";
|
||||
import NewBackupCard from "./NewBackupCard";
|
||||
|
||||
export default {
|
||||
components: {
|
||||
SuccessFailureAlert,
|
||||
UploadBtn,
|
||||
AvailableBackupCard,
|
||||
NewBackupCard,
|
||||
},
|
||||
|
@ -70,6 +72,7 @@ export default {
|
|||
let response = await api.backups.requestAvailable();
|
||||
this.availableBackups = response.imports;
|
||||
this.availableTemplates = response.templates;
|
||||
console.log(this.availableBackups);
|
||||
},
|
||||
deleteBackup() {
|
||||
if (this.$refs.form.validate()) {
|
||||
|
|
|
@ -1,24 +1,62 @@
|
|||
<template>
|
||||
<v-form ref="file">
|
||||
<v-file-input
|
||||
:loading="loading"
|
||||
:label="$t('migration.upload-an-archive')"
|
||||
v-model="file"
|
||||
accept=".zip"
|
||||
@change="upload"
|
||||
:prepend-icon="icon"
|
||||
class="file-icon"
|
||||
<input ref="uploader" class="d-none" type="file" @change="onFileChanged" />
|
||||
<v-btn
|
||||
:loading="isSelecting"
|
||||
@click="onButtonClick"
|
||||
color="success"
|
||||
text
|
||||
class="ma-2 white--text"
|
||||
>
|
||||
</v-file-input>
|
||||
<v-btn color="success" text class="ma-2 white--text">
|
||||
<v-icon left dark> mdi-cloud-upload </v-icon>
|
||||
Upload
|
||||
<v-icon right dark> mdi-cloud-upload </v-icon>
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {};
|
||||
import api from "../../api";
|
||||
export default {
|
||||
props: {
|
||||
url: String,
|
||||
},
|
||||
data: () => ({
|
||||
defaultButtonText: "Upload",
|
||||
file: null,
|
||||
isSelecting: false,
|
||||
}),
|
||||
|
||||
methods: {
|
||||
async upload() {
|
||||
if (this.file != null) {
|
||||
this.isSelecting = true;
|
||||
let formData = new FormData();
|
||||
formData.append("archive", this.file);
|
||||
|
||||
await api.utils.uploadFile(this.url, formData);
|
||||
|
||||
this.isSelecting = false;
|
||||
this.$emit("uploaded");
|
||||
}
|
||||
},
|
||||
onButtonClick() {
|
||||
this.isSelecting = true;
|
||||
window.addEventListener(
|
||||
"focus",
|
||||
() => {
|
||||
this.isSelecting = false;
|
||||
},
|
||||
{ once: true }
|
||||
);
|
||||
|
||||
this.$refs.uploader.click();
|
||||
},
|
||||
onFileChanged(e) {
|
||||
this.file = e.target.files[0];
|
||||
this.upload();
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
import operator
|
||||
import shutil
|
||||
|
||||
from app_config import BACKUP_DIR, TEMPLATE_DIR
|
||||
from db.db_setup import generate_session
|
||||
from fastapi import APIRouter, Depends, HTTPException
|
||||
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile
|
||||
from models.backup_models import BackupJob, ImportJob, Imports, LocalBackup
|
||||
from services.backups.exports import backup_all
|
||||
from services.backups.imports import ImportDatabase
|
||||
from sqlalchemy.orm.session import Session
|
||||
from starlette.responses import FileResponse
|
||||
from utils.snackbar import SnackResponse
|
||||
|
||||
router = APIRouter(tags=["Import / Export"])
|
||||
|
@ -49,6 +51,33 @@ def export_database(data: BackupJob, db: Session = Depends(generate_session)):
|
|||
)
|
||||
|
||||
|
||||
@router.post("/api/backups/upload/")
|
||||
def upload_backup_zipfile(archive: UploadFile = File(...)):
|
||||
""" Upload a .zip File to later be imported into Mealie """
|
||||
dest = BACKUP_DIR.joinpath(archive.filename)
|
||||
|
||||
with dest.open("wb") as buffer:
|
||||
shutil.copyfileobj(archive.file, buffer)
|
||||
|
||||
if dest.is_file:
|
||||
return SnackResponse.success("Backup uploaded")
|
||||
else:
|
||||
return SnackResponse.error("Failure uploading file")
|
||||
|
||||
|
||||
@router.get("/api/backups/{file_name}/download/")
|
||||
def upload_nextcloud_zipfile(file_name: str):
|
||||
""" Upload a .zip File to later be imported into Mealie """
|
||||
file = BACKUP_DIR.joinpath(file_name)
|
||||
|
||||
if file.is_file:
|
||||
return FileResponse(
|
||||
file, media_type="application/octet-stream", filename=file_name
|
||||
)
|
||||
else:
|
||||
return SnackResponse.error("No File Found")
|
||||
|
||||
|
||||
@router.post("/api/backups/{file_name}/import/", status_code=200)
|
||||
def import_database(
|
||||
file_name: str, import_data: ImportJob, db: Session = Depends(generate_session)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue