mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 14:33:33 -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 settings from "./api/settings";
|
||||||
import themes from "./api/themes";
|
import themes from "./api/themes";
|
||||||
import migration from "./api/migration";
|
import migration from "./api/migration";
|
||||||
|
import myUtils from "./api/upload";
|
||||||
// import api from "../api";
|
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
recipes: recipe,
|
recipes: recipe,
|
||||||
|
@ -14,4 +13,5 @@ export default {
|
||||||
settings: settings,
|
settings: settings,
|
||||||
themes: themes,
|
themes: themes,
|
||||||
migrations: migration,
|
migrations: migration,
|
||||||
|
utils: myUtils,
|
||||||
};
|
};
|
||||||
|
|
|
@ -10,6 +10,7 @@ const backupURLs = {
|
||||||
createBackup: `${backupBase}export/database/`,
|
createBackup: `${backupBase}export/database/`,
|
||||||
importBackup: (fileName) => `${backupBase}${fileName}/import/`,
|
importBackup: (fileName) => `${backupBase}${fileName}/import/`,
|
||||||
deleteBackup: (fileName) => `${backupBase}${fileName}/delete/`,
|
deleteBackup: (fileName) => `${backupBase}${fileName}/delete/`,
|
||||||
|
downloadBackup: (fileName) => `${backupBase}${fileName}/download/`,
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@ -32,4 +33,8 @@ export default {
|
||||||
let response = apiReq.post(backupURLs.createBackup, data);
|
let response = apiReq.post(backupURLs.createBackup, data);
|
||||||
return response;
|
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;
|
||||||
|
},
|
||||||
|
};
|
|
@ -65,15 +65,15 @@
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
|
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-btn disabled color="success" text @click="raiseEvent('download')">
|
<v-btn color="success" text :href="`/api/backups/${name}/download/`">
|
||||||
{{$t('general.download')}}
|
{{ $t("general.download") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn color="error" text @click="raiseEvent('delete')">
|
<v-btn color="error" text @click="raiseEvent('delete')">
|
||||||
{{$t('general.delete')}}
|
{{ $t("general.delete") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn color="success" text @click="raiseEvent('import')">
|
<v-btn color="success" text @click="raiseEvent('import')">
|
||||||
{{$t('general.import')}}
|
{{ $t("general.import") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
|
|
|
@ -21,10 +21,10 @@
|
||||||
Available Backups
|
Available Backups
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<span>
|
<span>
|
||||||
<v-btn color="success" text class="ma-2 white--text">
|
<UploadBtn
|
||||||
Upload
|
url="/api/backups/upload/"
|
||||||
<v-icon right dark> mdi-cloud-upload </v-icon>
|
@uploaded="getAvailableBackups"
|
||||||
</v-btn>
|
/>
|
||||||
</span>
|
</span>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<AvailableBackupCard
|
<AvailableBackupCard
|
||||||
|
@ -45,12 +45,14 @@
|
||||||
<script>
|
<script>
|
||||||
import api from "../../../api";
|
import api from "../../../api";
|
||||||
import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
|
import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
|
||||||
|
import UploadBtn from "../../UI/UploadBtn";
|
||||||
import AvailableBackupCard from "./AvailableBackupCard";
|
import AvailableBackupCard from "./AvailableBackupCard";
|
||||||
import NewBackupCard from "./NewBackupCard";
|
import NewBackupCard from "./NewBackupCard";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
SuccessFailureAlert,
|
SuccessFailureAlert,
|
||||||
|
UploadBtn,
|
||||||
AvailableBackupCard,
|
AvailableBackupCard,
|
||||||
NewBackupCard,
|
NewBackupCard,
|
||||||
},
|
},
|
||||||
|
@ -70,6 +72,7 @@ export default {
|
||||||
let response = await api.backups.requestAvailable();
|
let response = await api.backups.requestAvailable();
|
||||||
this.availableBackups = response.imports;
|
this.availableBackups = response.imports;
|
||||||
this.availableTemplates = response.templates;
|
this.availableTemplates = response.templates;
|
||||||
|
console.log(this.availableBackups);
|
||||||
},
|
},
|
||||||
deleteBackup() {
|
deleteBackup() {
|
||||||
if (this.$refs.form.validate()) {
|
if (this.$refs.form.validate()) {
|
||||||
|
|
|
@ -1,24 +1,62 @@
|
||||||
<template>
|
<template>
|
||||||
<v-form ref="file">
|
<v-form ref="file">
|
||||||
<v-file-input
|
<input ref="uploader" class="d-none" type="file" @change="onFileChanged" />
|
||||||
:loading="loading"
|
<v-btn
|
||||||
:label="$t('migration.upload-an-archive')"
|
:loading="isSelecting"
|
||||||
v-model="file"
|
@click="onButtonClick"
|
||||||
accept=".zip"
|
color="success"
|
||||||
@change="upload"
|
text
|
||||||
:prepend-icon="icon"
|
class="ma-2 white--text"
|
||||||
class="file-icon"
|
|
||||||
>
|
>
|
||||||
</v-file-input>
|
<v-icon left dark> mdi-cloud-upload </v-icon>
|
||||||
<v-btn color="success" text class="ma-2 white--text">
|
|
||||||
Upload
|
Upload
|
||||||
<v-icon right dark> mdi-cloud-upload </v-icon>
|
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-form>
|
</v-form>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<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>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -1,12 +1,14 @@
|
||||||
import operator
|
import operator
|
||||||
|
import shutil
|
||||||
|
|
||||||
from app_config import BACKUP_DIR, TEMPLATE_DIR
|
from app_config import BACKUP_DIR, TEMPLATE_DIR
|
||||||
from db.db_setup import generate_session
|
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 models.backup_models import BackupJob, ImportJob, Imports, LocalBackup
|
||||||
from services.backups.exports import backup_all
|
from services.backups.exports import backup_all
|
||||||
from services.backups.imports import ImportDatabase
|
from services.backups.imports import ImportDatabase
|
||||||
from sqlalchemy.orm.session import Session
|
from sqlalchemy.orm.session import Session
|
||||||
|
from starlette.responses import FileResponse
|
||||||
from utils.snackbar import SnackResponse
|
from utils.snackbar import SnackResponse
|
||||||
|
|
||||||
router = APIRouter(tags=["Import / Export"])
|
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)
|
@router.post("/api/backups/{file_name}/import/", status_code=200)
|
||||||
def import_database(
|
def import_database(
|
||||||
file_name: str, import_data: ImportJob, db: Session = Depends(generate_session)
|
file_name: str, import_data: ImportJob, db: Session = Depends(generate_session)
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue