mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-24 15:25:23 -07:00
Upload component (#108)
* unified upload button + download backups * javascript toolings * fix vuetur config * fixed type check error * refactor: clean up bag javascript Co-authored-by: Hayden <hay-kot@pm.me>
This commit is contained in:
parent
51893e89cd
commit
f35e9c20d6
19 changed files with 239 additions and 105 deletions
3
frontend/jsconfig.json
Normal file
3
frontend/jsconfig.json
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
{
|
||||||
|
"include": ["./src/**/*"]
|
||||||
|
}
|
|
@ -54,5 +54,11 @@
|
||||||
"> 1%",
|
"> 1%",
|
||||||
"last 2 versions",
|
"last 2 versions",
|
||||||
"not dead"
|
"not dead"
|
||||||
]
|
],
|
||||||
|
"prettier": {
|
||||||
|
"trailingComma": "es5",
|
||||||
|
"tabWidth": 2,
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": false
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -34,11 +34,11 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import Menu from "./components/UI/Menu";
|
import Menu from "./components/UI/Menu"
|
||||||
import SearchBar from "./components/UI/SearchBar";
|
import SearchBar from "./components/UI/SearchBar"
|
||||||
import AddRecipeFab from "./components/UI/AddRecipeFab";
|
import AddRecipeFab from "./components/UI/AddRecipeFab"
|
||||||
import SnackBar from "./components/UI/SnackBar";
|
import SnackBar from "./components/UI/SnackBar"
|
||||||
import Vuetify from "./plugins/vuetify";
|
import Vuetify from "./plugins/vuetify"
|
||||||
export default {
|
export default {
|
||||||
name: "App",
|
name: "App",
|
||||||
|
|
||||||
|
@ -51,15 +51,15 @@ export default {
|
||||||
|
|
||||||
watch: {
|
watch: {
|
||||||
$route() {
|
$route() {
|
||||||
this.search = false;
|
this.search = false
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
mounted() {
|
mounted() {
|
||||||
this.$store.dispatch("initTheme");
|
this.$store.dispatch("initTheme")
|
||||||
this.$store.dispatch("requestRecentRecipes");
|
this.$store.dispatch("requestRecentRecipes")
|
||||||
this.darkModeSystemCheck();
|
this.darkModeSystemCheck()
|
||||||
this.darkModeAddEventListener();
|
this.darkModeAddEventListener()
|
||||||
},
|
},
|
||||||
|
|
||||||
data: () => ({
|
data: () => ({
|
||||||
|
@ -73,30 +73,30 @@ export default {
|
||||||
if (this.$store.getters.getDarkMode === "system")
|
if (this.$store.getters.getDarkMode === "system")
|
||||||
Vuetify.framework.theme.dark = window.matchMedia(
|
Vuetify.framework.theme.dark = window.matchMedia(
|
||||||
"(prefers-color-scheme: dark)"
|
"(prefers-color-scheme: dark)"
|
||||||
).matches;
|
).matches
|
||||||
},
|
},
|
||||||
/**
|
/**
|
||||||
* This will monitor the OS level darkmode and call to update dark mode.
|
* This will monitor the OS level darkmode and call to update dark mode.
|
||||||
*/
|
*/
|
||||||
darkModeAddEventListener() {
|
darkModeAddEventListener() {
|
||||||
const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)");
|
const darkMediaQuery = window.matchMedia("(prefers-color-scheme: dark)")
|
||||||
darkMediaQuery.addEventListener("change", () => {
|
darkMediaQuery.addEventListener("change", () => {
|
||||||
this.darkModeSystemCheck();
|
this.darkModeSystemCheck()
|
||||||
});
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
toggleSearch() {
|
toggleSearch() {
|
||||||
if (this.search === true) {
|
if (this.search === true) {
|
||||||
this.search = false;
|
this.search = false
|
||||||
} else {
|
} else {
|
||||||
this.search = true;
|
this.search = true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
navigateFromSearch(slug) {
|
navigateFromSearch(slug) {
|
||||||
this.$router.push(`/recipe/${slug}`);
|
this.$router.push(`/recipe/${slug}`)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
};
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style>
|
<style>
|
||||||
|
|
|
@ -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,
|
||||||
};
|
};
|
||||||
|
|
|
@ -16,8 +16,8 @@ function processResponse(response) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const apiReq = {
|
const apiReq = {
|
||||||
post: async function(url, data) {
|
post: async function (url, data) {
|
||||||
let response = await axios.post(url, data).catch(function(error) {
|
let response = await axios.post(url, data).catch(function (error) {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
processResponse(error.response);
|
processResponse(error.response);
|
||||||
return error.response;
|
return error.response;
|
||||||
|
@ -27,8 +27,8 @@ const apiReq = {
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
get: async function(url, data) {
|
get: async function (url, data) {
|
||||||
let response = await axios.get(url, data).catch(function(error) {
|
let response = await axios.get(url, data).catch(function (error) {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
processResponse(error.response);
|
processResponse(error.response);
|
||||||
return response;
|
return response;
|
||||||
|
@ -38,8 +38,8 @@ const apiReq = {
|
||||||
return response;
|
return response;
|
||||||
},
|
},
|
||||||
|
|
||||||
delete: async function(url, data) {
|
delete: async function (url, data) {
|
||||||
let response = await axios.delete(url, data).catch(function(error) {
|
let response = await axios.delete(url, data).catch(function (error) {
|
||||||
if (error.response) {
|
if (error.response) {
|
||||||
processResponse(error.response);
|
processResponse(error.response);
|
||||||
return response;
|
return response;
|
||||||
|
|
|
@ -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;
|
||||||
|
},
|
||||||
|
};
|
|
@ -1,13 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-title class="headline"> {{$t('meal-plan.edit-meal-plan')}} </v-card-title>
|
<v-card-title class="headline">
|
||||||
|
{{ $t("meal-plan.edit-meal-plan") }}
|
||||||
|
</v-card-title>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
|
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<MealPlanCard v-model="mealPlan.meals" />
|
<MealPlanCard v-model="mealPlan.meals" />
|
||||||
<v-row align="center" justify="end">
|
<v-row align="center" justify="end">
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-btn color="success" text @click="update"> {{$t('general.update')}} </v-btn>
|
<v-btn color="success" text @click="update">
|
||||||
|
{{ $t("general.update") }}
|
||||||
|
</v-btn>
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<template>
|
<template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-title class="headline">
|
<v-card-title class="headline">
|
||||||
{{$t('meal-plan.create-a-new-meal-plan')}}
|
{{ $t("meal-plan.create-a-new-meal-plan") }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-divider></v-divider>
|
<v-divider></v-divider>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
|
@ -71,9 +71,11 @@
|
||||||
<v-row align="center" justify="end">
|
<v-row align="center" justify="end">
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-btn color="success" @click="random" v-if="meals[1]" text>
|
<v-btn color="success" @click="random" v-if="meals[1]" text>
|
||||||
{{$t('general.random')}}
|
{{ $t("general.random") }}
|
||||||
|
</v-btn>
|
||||||
|
<v-btn color="success" @click="save" text>
|
||||||
|
{{ $t("general.save") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<v-btn color="success" @click="save" text> {{$t('general.save')}} </v-btn>
|
|
||||||
|
|
||||||
<v-spacer></v-spacer>
|
<v-spacer></v-spacer>
|
||||||
<v-btn icon @click="show = !show"> </v-btn>
|
<v-btn icon @click="show = !show"> </v-btn>
|
||||||
|
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<v-row>
|
<v-row>
|
||||||
<v-col >
|
<v-col>
|
||||||
<v-checkbox
|
<v-checkbox
|
||||||
class="mb-n4 mt-1"
|
class="mb-n4 mt-1"
|
||||||
dense
|
dense
|
||||||
|
@ -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,11 @@
|
||||||
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
|
class="mt-1"
|
||||||
<v-icon right dark> mdi-cloud-upload </v-icon>
|
url="/api/backups/upload/"
|
||||||
</v-btn>
|
@uploaded="getAvailableBackups"
|
||||||
|
/>
|
||||||
</span>
|
</span>
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<AvailableBackupCard
|
<AvailableBackupCard
|
||||||
|
@ -45,12 +46,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 +73,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,7 +1,11 @@
|
||||||
<template>
|
<template>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<p>
|
<p>
|
||||||
{{$t('migration.you-can-import-recipes-from-either-a-zip-file-or-a-directory-located-in-the-app-data-migraiton-folder-please-review-the-documentation-to-ensure-your-directory-structure-matches-what-is-expected')}}
|
{{
|
||||||
|
$t(
|
||||||
|
"migration.you-can-import-recipes-from-either-a-zip-file-or-a-directory-located-in-the-app-data-migraiton-folder-please-review-the-documentation-to-ensure-your-directory-structure-matches-what-is-expected"
|
||||||
|
)
|
||||||
|
}}
|
||||||
</p>
|
</p>
|
||||||
<v-form ref="form">
|
<v-form ref="form">
|
||||||
<v-row align="center">
|
<v-row align="center">
|
||||||
|
@ -13,27 +17,32 @@
|
||||||
:rules="[rules.required]"
|
:rules="[rules.required]"
|
||||||
></v-select>
|
></v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="2" sm="12">
|
<v-col md="1" sm="12">
|
||||||
<v-btn text color="info" @click="importRecipes"> {{$t('migration.migrate')}} </v-btn>
|
<v-btn-toggle group>
|
||||||
</v-col>
|
<v-btn text color="info" @click="importRecipes">
|
||||||
<v-col cols="12" md="1" sm="12">
|
{{ $t("migration.migrate") }}
|
||||||
<v-btn text color="error" @click="deleteImportValidation">
|
</v-btn>
|
||||||
{{$t('general.delete')}}
|
<v-btn text color="error" @click="deleteImportValidation">
|
||||||
</v-btn>
|
{{ $t("general.delete") }}
|
||||||
<Confirmation
|
</v-btn>
|
||||||
:title="$t('general.delete-data')"
|
<UploadBtn
|
||||||
:message="$t('migration.delete-confirmation')"
|
url="/api/migration/upload/"
|
||||||
color="error"
|
class="mt-1"
|
||||||
icon="mdi-alert-circle"
|
@uploaded="getAvaiableImports"
|
||||||
ref="deleteThemeConfirm"
|
/>
|
||||||
v-on:confirm="deleteImport()"
|
|
||||||
/>
|
<Confirmation
|
||||||
</v-col>
|
:title="$t('general.delete-data')"
|
||||||
</v-row>
|
:message="$t('migration.delete-confirmation')"
|
||||||
<v-row>
|
color="error"
|
||||||
<v-col cols="12" md="5" sm="12">
|
icon="mdi-alert-circle"
|
||||||
<UploadMigrationButton @uploaded="getAvaiableImports" />
|
ref="deleteThemeConfirm"
|
||||||
|
v-on:confirm="deleteImport()"
|
||||||
|
/>
|
||||||
|
</v-btn-toggle>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
|
||||||
|
<v-spacer></v-spacer>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-form>
|
</v-form>
|
||||||
<SuccessFailureAlert
|
<SuccessFailureAlert
|
||||||
|
@ -48,13 +57,13 @@
|
||||||
<script>
|
<script>
|
||||||
import api from "../../../api";
|
import api from "../../../api";
|
||||||
import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
|
import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
|
||||||
import UploadMigrationButton from "./UploadMigrationButton";
|
|
||||||
import Confirmation from "../../UI/Confirmation";
|
import Confirmation from "../../UI/Confirmation";
|
||||||
|
import UploadBtn from "../../UI/UploadBtn";
|
||||||
export default {
|
export default {
|
||||||
components: {
|
components: {
|
||||||
SuccessFailureAlert,
|
SuccessFailureAlert,
|
||||||
UploadMigrationButton,
|
|
||||||
Confirmation,
|
Confirmation,
|
||||||
|
UploadBtn,
|
||||||
},
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
|
@ -63,7 +72,7 @@ export default {
|
||||||
availableImports: [],
|
availableImports: [],
|
||||||
selectedImport: null,
|
selectedImport: null,
|
||||||
rules: {
|
rules: {
|
||||||
required: (v) => !!v || "Selection Required",
|
required: v => !!v || "Selection Required",
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
|
|
@ -43,7 +43,7 @@
|
||||||
|
|
||||||
<v-form ref="form" lazy-validation>
|
<v-form ref="form" lazy-validation>
|
||||||
<v-row dense align="center">
|
<v-row dense align="center">
|
||||||
<v-col cols="12" md="4" sm="3">
|
<v-col md="4" sm="3">
|
||||||
<v-select
|
<v-select
|
||||||
:label="$t('settings.theme.saved-color-theme')"
|
:label="$t('settings.theme.saved-color-theme')"
|
||||||
:items="availableThemes"
|
:items="availableThemes"
|
||||||
|
@ -51,18 +51,18 @@
|
||||||
return-object
|
return-object
|
||||||
v-model="selectedTheme"
|
v-model="selectedTheme"
|
||||||
@change="themeSelected"
|
@change="themeSelected"
|
||||||
:rules="[(v) => !!v || $t('settings.theme.theme-is-required')]"
|
:rules="[v => !!v || $t('settings.theme.theme-is-required')]"
|
||||||
required
|
required
|
||||||
>
|
>
|
||||||
</v-select>
|
</v-select>
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" sm="1">
|
<v-col>
|
||||||
<NewThemeDialog @new-theme="appendTheme" />
|
<v-btn-toggle group>
|
||||||
</v-col>
|
<NewThemeDialog @new-theme="appendTheme" class="mt-1" />
|
||||||
<v-col cols="12" sm="1">
|
<v-btn text color="error" @click="deleteSelectedThemeValidation">
|
||||||
<v-btn text color="error" @click="deleteSelectedThemeValidation">
|
Delete
|
||||||
Delete
|
</v-btn>
|
||||||
</v-btn>
|
</v-btn-toggle>
|
||||||
<Confirmation
|
<Confirmation
|
||||||
:title="$t('settings.theme.delete-theme')"
|
:title="$t('settings.theme.delete-theme')"
|
||||||
:message="
|
:message="
|
||||||
|
@ -74,6 +74,7 @@
|
||||||
v-on:confirm="deleteSelectedTheme()"
|
v-on:confirm="deleteSelectedTheme()"
|
||||||
/>
|
/>
|
||||||
</v-col>
|
</v-col>
|
||||||
|
<v-spacer></v-spacer>
|
||||||
</v-row>
|
</v-row>
|
||||||
</v-form>
|
</v-form>
|
||||||
<v-row dense align-content="center" v-if="selectedTheme.colors">
|
<v-row dense align-content="center" v-if="selectedTheme.colors">
|
||||||
|
@ -123,15 +124,10 @@
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
|
|
||||||
<v-card-actions>
|
<v-card-actions>
|
||||||
<v-row>
|
<v-spacer></v-spacer>
|
||||||
<v-col> </v-col>
|
<v-btn color="success" @click="saveThemes" class="mr-2">
|
||||||
<v-col></v-col>
|
{{ $t("general.save") }}
|
||||||
<v-col align="end">
|
</v-btn>
|
||||||
<v-btn text color="success" @click="saveThemes">
|
|
||||||
{{ $t("settings.theme.save-colors-and-apply-theme") }}
|
|
||||||
</v-btn>
|
|
||||||
</v-col>
|
|
||||||
</v-row>
|
|
||||||
</v-card-actions>
|
</v-card-actions>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
|
@ -187,7 +183,7 @@ export default {
|
||||||
//Change to default if deleting current theme.
|
//Change to default if deleting current theme.
|
||||||
if (
|
if (
|
||||||
!this.availableThemes.some(
|
!this.availableThemes.some(
|
||||||
(theme) => theme.name === this.selectedTheme.name
|
theme => theme.name === this.selectedTheme.name
|
||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
await this.$store.dispatch("resetTheme");
|
await this.$store.dispatch("resetTheme");
|
||||||
|
|
|
@ -1,10 +1,17 @@
|
||||||
<template>
|
<template>
|
||||||
<v-card>
|
<v-card>
|
||||||
<v-card-title class="headline">
|
<v-card-title class="headline">
|
||||||
{{$t('settings.webhooks.meal-planner-webhooks')}}
|
{{ $t("settings.webhooks.meal-planner-webhooks") }}
|
||||||
</v-card-title>
|
</v-card-title>
|
||||||
<v-card-text>
|
<v-card-text>
|
||||||
<p v-html="$t('settings.webhooks.the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at', {time: time})"></p>
|
<p
|
||||||
|
v-html="
|
||||||
|
$t(
|
||||||
|
'settings.webhooks.the-urls-listed-below-will-recieve-webhooks-containing-the-recipe-data-for-the-meal-plan-on-its-scheduled-day-currently-webhooks-will-execute-at',
|
||||||
|
{ time: time }
|
||||||
|
)
|
||||||
|
"
|
||||||
|
></p>
|
||||||
|
|
||||||
<v-row dense align="center">
|
<v-row dense align="center">
|
||||||
<v-col cols="12" md="2" sm="5">
|
<v-col cols="12" md="2" sm="5">
|
||||||
|
@ -19,7 +26,9 @@
|
||||||
<TimePickerDialog @save-time="saveTime" />
|
<TimePickerDialog @save-time="saveTime" />
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col cols="12" md="4" sm="5">
|
<v-col cols="12" md="4" sm="5">
|
||||||
<v-btn text color="info" @click="testWebhooks"> {{$t('settings.webhooks.test-webhooks')}} </v-btn>
|
<v-btn text color="info" @click="testWebhooks">
|
||||||
|
{{ $t("settings.webhooks.test-webhooks") }}
|
||||||
|
</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
||||||
|
@ -46,8 +55,8 @@
|
||||||
</v-col>
|
</v-col>
|
||||||
<v-col> </v-col>
|
<v-col> </v-col>
|
||||||
<v-col align="end">
|
<v-col align="end">
|
||||||
<v-btn text color="success" @click="saveWebhooks">
|
<v-btn color="success" @click="saveWebhooks" class="mr-2 mb-1">
|
||||||
{{$t('settings.webhooks.save-webhooks')}}
|
{{ $t("settings.webhooks.save-webhooks") }}
|
||||||
</v-btn>
|
</v-btn>
|
||||||
</v-col>
|
</v-col>
|
||||||
</v-row>
|
</v-row>
|
||||||
|
|
|
@ -1,24 +1,61 @@
|
||||||
<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="file-icon"
|
|
||||||
>
|
>
|
||||||
</v-file-input>
|
<v-icon left > 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>
|
||||||
|
|
|
@ -11,6 +11,7 @@
|
||||||
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Paste in your recipe data. Each line will be treated as an item in a list"
|
"paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Paste in your recipe data. Each line will be treated as an item in a list"
|
||||||
},
|
},
|
||||||
"general": {
|
"general": {
|
||||||
|
"upload": "Upload",
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"settings": "Settings",
|
"settings": "Settings",
|
||||||
|
|
|
@ -109,7 +109,7 @@ export default {
|
||||||
},
|
},
|
||||||
|
|
||||||
editPlan(id) {
|
editPlan(id) {
|
||||||
this.plannedMeals.forEach((element) => {
|
this.plannedMeals.forEach(element => {
|
||||||
if (element.uid === id) {
|
if (element.uid === id) {
|
||||||
this.editMealPlan = element;
|
this.editMealPlan = element;
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
16
vetur.config.js
Normal file
16
vetur.config.js
Normal file
|
@ -0,0 +1,16 @@
|
||||||
|
// vetur.config.js
|
||||||
|
/** @type {import('vls').VeturConfig} */
|
||||||
|
module.exports = {
|
||||||
|
settings: {
|
||||||
|
"vetur.useWorkspaceDependencies": true,
|
||||||
|
"vetur.experimental.templateInterpolationService": true,
|
||||||
|
"vetur.validation.interpolation": false,
|
||||||
|
},
|
||||||
|
projects: [
|
||||||
|
{
|
||||||
|
root: "./frontend",
|
||||||
|
package: "package.json",
|
||||||
|
globalComponents: ["./src/components/**/*.vue"],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
Loading…
Add table
Add a link
Reference in a new issue