Merge branch 'dev' of https://github.com/hay-kot/mealie into dev

This commit is contained in:
hay-kot 2021-05-03 20:40:42 -08:00
commit 2fc44018ec
118 changed files with 1770 additions and 421 deletions

View File

@ -10,8 +10,8 @@
encode gzip
uri strip_suffix /
handle_path /api/recipes/image/* {
root * /app/data/img/
handle_path /api/recipes/media/* {
root * /app/data/recipes/
file_server
}

View File

@ -57,14 +57,16 @@ Mealie is a self hosted recipe manager and meal planner with a RestAPI backend a
## Key Features
- 🔍 Fuzzy search
- 🏷️ Tag recipes with categories or tags to flexible sorting
- 🏷️ Tag recipes with categories or tags for flexible sorting
- 🕸 Import recipes from around the web by URL
- 💪 Powerful bulk Category/Tag assignment
- 📱 Beautiful Mobile Views
- 📆 Create Meal Plans
- 🛒 Generate shopping lists
- 🐳 Easy setup with Docker
- 🎨 Customize your interface with color themes layouts
- 💾 Export all your data in any format with Jinja2 Templates, with easy data restoration from the user interface.
- 🎨 Customize your interface with color themes
- 💾 Export all your data in any format with Jinja2 Templates
- 🔒 Keep your data safe with automated backup and easy restore options
- 🌍 localized in many languages
- Plus tons more!
- Flexible API

View File

@ -11,6 +11,9 @@
#### Database
Database version has been bumped from v0.4.x -> v0.5.0. You will need to export and import your data.
#### Image Directory
the /data/img directory has been depreciated. All images are now stored in the /recipes/{slug}/image directory. Images should be migrated automatically, but you may experience issues related to this change.
## Bug Fixes
- Fixed #332 - Language settings are saved for one browser
- Fixes #281 - Slow Handling of Large Sets of Recipes

59
frontend/src/api/about.js Normal file
View File

@ -0,0 +1,59 @@
import { baseURL } from "./api-utils";
import { apiReq } from "./api-utils";
const prefix = baseURL + "about";
const aboutURLs = {
version: `${prefix}/version`,
debug: `${prefix}`,
lastRecipe: `${prefix}/last-recipe-json`,
demo: `${prefix}/is-demo`,
log: num => `${prefix}/log/${num}`,
statistics: `${prefix}/statistics`,
events: `${prefix}/events`,
event: id => `${prefix}/events/${id}`,
};
export const aboutAPI = {
async getEvents() {
const resposne = await apiReq.get(aboutURLs.events);
return resposne.data;
},
async deleteEvent(id) {
const resposne = await apiReq.delete(aboutURLs.event(id));
return resposne.data;
},
async deleteAllEvents() {
const resposne = await apiReq.delete(aboutURLs.events);
return resposne.data;
},
// async getAppInfo() {
// const response = await apiReq.get(aboutURLs.version);
// return response.data;
// },
// async getDebugInfo() {
// const response = await apiReq.get(aboutURLs.debug);
// return response.data;
// },
// async getLogText(num) {
// const response = await apiReq.get(aboutURLs.log(num));
// return response.data;
// },
// async getLastJson() {
// const response = await apiReq.get(aboutURLs.lastRecipe);
// return response.data;
// },
// async getIsDemo() {
// const response = await apiReq.get(aboutURLs.demo);
// return response.data;
// },
// async getStatistics() {
// const response = await apiReq.get(aboutURLs.statistics);
// return response.data;
// },
};

View File

@ -11,6 +11,7 @@ import { userAPI } from "./users";
import { signupAPI } from "./signUps";
import { groupAPI } from "./groups";
import { siteSettingsAPI } from "./siteSettings";
import { aboutAPI } from "./about";
/**
* The main object namespace for interacting with the backend database
@ -30,4 +31,5 @@ export const api = {
users: userAPI,
signUps: signupAPI,
groups: groupAPI,
about: aboutAPI,
};

View File

@ -8,11 +8,13 @@ const debugURLs = {
debug: `${prefix}`,
lastRecipe: `${prefix}/last-recipe-json`,
demo: `${prefix}/is-demo`,
log: num => `${prefix}/log/${num}`,
statistics: `${prefix}/statistics`,
};
export const metaAPI = {
async getAppInfo() {
let response = await apiReq.get(debugURLs.version);
const response = await apiReq.get(debugURLs.version);
return response.data;
},
@ -21,13 +23,23 @@ export const metaAPI = {
return response.data;
},
async getLogText(num) {
const response = await apiReq.get(debugURLs.log(num));
return response.data;
},
async getLastJson() {
let response = await apiReq.get(debugURLs.lastRecipe);
const response = await apiReq.get(debugURLs.lastRecipe);
return response.data;
},
async getIsDemo() {
let response = await apiReq.get(debugURLs.demo);
const response = await apiReq.get(debugURLs.demo);
return response.data;
},
async getStatistics() {
const response = await apiReq.get(debugURLs.statistics);
return response.data;
},
};

View File

@ -14,9 +14,9 @@ const recipeURLs = {
recipe: slug => prefix + slug,
update: slug => prefix + slug,
delete: slug => prefix + slug,
createAsset: slug => `${prefix}media/${slug}/assets`,
recipeImage: slug => `${prefix}${slug}/image`,
updateImage: slug => `${prefix}${slug}/image`,
createAsset: slug => `${prefix}${slug}/asset`,
};
export const recipeAPI = {
@ -84,7 +84,7 @@ export const recipeAPI = {
fd.append("extension", fileObject.name.split(".").pop());
fd.append("name", name);
fd.append("icon", icon);
let response = apiReq.post(recipeURLs.createAsset(recipeSlug), fd);
const response = apiReq.post(recipeURLs.createAsset(recipeSlug), fd);
return response;
},
@ -135,14 +135,14 @@ export const recipeAPI = {
},
recipeImage(recipeSlug) {
return `/api/recipes/image/${recipeSlug}/original.webp`;
return `/api/recipes/media/${recipeSlug}/image/original.webp`;
},
recipeSmallImage(recipeSlug) {
return `/api/recipes/image/${recipeSlug}/min-original.webp`;
return `/api/recipes/media/${recipeSlug}/image/min-original.webp`;
},
recipeTinyImage(recipeSlug) {
return `/api/recipes/image/${recipeSlug}/tiny-original.webp`;
return `/api/recipes/media/${recipeSlug}/image/tiny-original.webp`;
},
};

View File

@ -18,15 +18,20 @@
v-if="!edit"
color="primary"
icon
:href="`/api/recipes/${slug}/asset?file_name=${item.fileName}`"
:href="`/api/recipes/media/${slug}/assets/${item.fileName}`"
target="_blank"
top
>
<v-icon> mdi-download</v-icon>
</v-btn>
<v-btn v-else color="error" icon @click="deleteAsset(i)" top>
<v-icon>mdi-delete</v-icon>
</v-btn>
<div v-else>
<v-btn color="error" icon @click="deleteAsset(i)" top>
<v-icon>mdi-delete</v-icon>
</v-btn>
<v-btn color="primary" icon @click="copyLink(item.name, item.fileName)" top>
<v-icon>mdi-content-copy</v-icon>
</v-btn>
</div>
</v-list-item-action>
</v-list-item>
</v-list>
@ -107,6 +112,11 @@ export default {
],
};
},
computed: {
baseURL() {
return window.location.origin;
},
},
methods: {
setFileObject(obj) {
this.fileObject = obj;
@ -124,6 +134,13 @@ export default {
deleteAsset(index) {
this.value.splice(index, 1);
},
copyLink(name, fileName) {
const copyText = `![${name}](${this.baseURL}/api/recipes/media/${this.slug}/assets/${fileName})`;
navigator.clipboard.writeText(copyText).then(
() => console.log("Copied", copyText),
() => console.log("Copied Failed", copyText)
);
},
},
};
</script>

View File

@ -27,23 +27,36 @@
<v-row>
<v-col cols="12" sm="12" md="4" lg="4">
<Ingredients :edit="true" v-model="value.recipeIngredient" />
<v-card class="mt-6">
<v-card-title class="py-2">
{{ $t("recipe.categories") }}
</v-card-title>
<v-divider class="mx-2"></v-divider>
<v-card-text>
<CategoryTagSelector
:return-object="false"
v-model="value.recipeCategory"
:show-add="true"
:show-label="false"
/>
</v-card-text>
</v-card>
<h2 class="mt-6">{{ $t("recipe.categories") }}</h2>
<CategoryTagSelector
:return-object="false"
v-model="value.recipeCategory"
:show-add="true"
:show-label="false"
/>
<h2 class="mt-4">{{ $t("tag.tags") }}</h2>
<CategoryTagSelector
:return-object="false"
v-model="value.tags"
:show-add="true"
:tag-selector="true"
:show-label="false"
/>
<v-card class="mt-2">
<v-card-title class="py-2">
{{ $t("tag.tags") }}
</v-card-title>
<v-divider class="mx-2"></v-divider>
<v-card-text>
<CategoryTagSelector
:return-object="false"
v-model="value.tags"
:show-add="true"
:tag-selector="true"
:show-label="false"
/>
</v-card-text>
</v-card>
<Nutrition v-model="value.nutrition" :edit="true" />
<Assets v-model="value.assets" :edit="true" :slug="value.slug" />
<ExtrasEditor :extras="value.extras" @save="saveExtras" />

View File

@ -1,14 +1,14 @@
<template>
<div>
<v-card-title class="headline">
{{ name }}
{{ recipe.name }}
</v-card-title>
<v-card-text>
<vue-markdown :source="description"> </vue-markdown>
<vue-markdown :source="recipe.description"> </vue-markdown>
<v-row dense disabled>
<v-col>
<v-btn
v-if="yields"
v-if="recipe.yields"
dense
small
:hover="false"
@ -21,59 +21,59 @@
{{ yields }}
</v-btn>
</v-col>
<Rating :value="rating" :name="name" :slug="slug" />
<Rating :value="recipe.rating" :name="recipe.name" :slug="recipe.slug" />
</v-row>
<v-row>
<v-col cols="12" sm="12" md="4" lg="4">
<Ingredients :value="ingredients" :edit="false" />
<Ingredients :value="recipe.recipeIngredient" :edit="false" />
<div v-if="medium">
<v-card class="mt-2" v-if="categories.length > 0">
<v-card class="mt-2" v-if="recipe.recipeCategory.length > 0">
<v-card-title class="py-2">
{{ $t("recipe.categories") }}
</v-card-title>
<v-divider class="mx-2"></v-divider>
<v-card-text>
<RecipeChips :items="categories" />
<RecipeChips :items="recipe.recipeCategory" />
</v-card-text>
</v-card>
<v-card class="mt-2" v-if="tags.length > 0">
<v-card class="mt-2" v-if="recipe.tags.length > 0">
<v-card-title class="py-2">
{{ $t("tag.tags") }}
</v-card-title>
<v-divider class="mx-2"></v-divider>
<v-card-text>
<RecipeChips :items="tags" :isCategory="false" />
<RecipeChips :items="recipe.tags" :isCategory="false" />
</v-card-text>
</v-card>
<Nutrition :value="nutrition" :edit="false" />
<Assets :value="assets" :edit="false" :slug="slug" />
<Nutrition v-if="recipe.settings.showNutrition" :value="recipe.nutrition" :edit="false" />
<Assets v-if="recipe.settings.showAssets" :value="recipe.assets" :edit="false" :slug="recipe.slug" />
</div>
</v-col>
<v-divider v-if="medium" class="my-divider" :vertical="true"></v-divider>
<v-col cols="12" sm="12" md="8" lg="8">
<Instructions :value="instructions" :edit="false" />
<Notes :value="notes" :edit="false" />
<Instructions :value="recipe.recipeInstructions" :edit="false" />
<Notes :value="recipe.notes" :edit="false" />
</v-col>
</v-row>
<div v-if="!medium">
<RecipeChips :title="$t('recipe.categories')" :items="categories" />
<RecipeChips :title="$t('tag.tags')" :items="tags" />
<Nutrition :value="nutrition" :edit="false" />
<Assets :value="assets" :edit="false" :slug="slug" />
<RecipeChips :title="$t('recipe.categories')" :items="recipe.recipeCategory" />
<RecipeChips :title="$t('tag.tags')" :items="recipe.tags" />
<Nutrition v-if="recipe.settings.showNutrition" :value="recipe.nutrition" :edit="false" />
<Assets v-if="recipe.settings.showAssets" :value="recipe.assets" :edit="false" :slug="recipe.slug" />
</div>
<v-row class="mt-2 mb-1">
<v-col></v-col>
<v-btn
v-if="orgURL"
v-if="recipe.orgURL"
dense
small
:hover="false"
type="label"
:ripple="false"
elevation="0"
:href="orgURL"
:href="recipe.orgURL"
color="secondary darken-1"
target="_blank"
class="rounded-sm mr-4"
@ -107,19 +107,7 @@ export default {
Rating,
},
props: {
name: String,
slug: String,
description: String,
ingredients: Array,
instructions: Array,
categories: Array,
tags: Array,
notes: Array,
rating: Number,
yields: String,
orgURL: String,
nutrition: Object,
assets: Array,
recipe: Object,
},
data() {
return {

View File

@ -1,7 +1,11 @@
<template>
<v-btn color="accent" text :loading="downloading" @click="downloadFile">
{{ showButtonText }}
</v-btn>
<div>
<slot v-bind="{ downloading, downloadFile }">
<v-btn color="accent" text :loading="downloading" @click="downloadFile">
{{ showButtonText }}
</v-btn>
</slot>
</div>
</template>
<script>

View File

@ -1,10 +1,12 @@
<template>
<v-form ref="file">
<input ref="uploader" class="d-none" type="file" @change="onFileChanged" />
<v-btn :loading="isSelecting" @click="onButtonClick" color="accent" :text="textBtn">
<v-icon left> {{ icon }}</v-icon>
{{ text ? text : defaultText }}
</v-btn>
<slot v-bind="{ isSelecting, onButtonClick }">
<v-btn :loading="isSelecting" @click="onButtonClick" color="accent" :text="textBtn">
<v-icon left> {{ icon }}</v-icon>
{{ text ? text : defaultText }}
</v-btn>
</slot>
</v-form>
</template>
@ -25,7 +27,7 @@ export default {
default: true,
},
},
data: () => ({
data: () => ({
file: null,
isSelecting: false,
}),

View File

@ -0,0 +1,81 @@
<template>
<div class="mt-2">
<v-card>
<v-card-title class="headline">
Log
<v-spacer></v-spacer>
<v-text-field
class="ml-auto shrink mb-n7"
solo
label="Log Lines"
type="number"
append-icon="mdi-refresh-circle"
v-model="lines"
@click:append="getLogText"
suffix="lines"
single-line
>
</v-text-field>
<TheDownloadBtn :button-text="$t('about.download-log')" download-url="/api/debug/log">
<template v-slot:default="{ downloadFile }">
<v-btn bottom right relative fab icon color="primary" @click="downloadFile">
<v-icon> mdi-download </v-icon>
</v-btn>
</template>
</TheDownloadBtn>
</v-card-title>
<v-divider></v-divider>
<v-card-text>
<div v-for="(item, index) in splitText" :key="index" :class="getClass(item)">
{{ item }}
</div>
</v-card-text>
</v-card>
</div>
</template>
<script>
import TheDownloadBtn from "@/components/UI/Buttons/TheDownloadBtn";
import { api } from "@/api";
export default {
components: { TheDownloadBtn },
data() {
return {
lines: 200,
text: "",
};
},
mounted() {
this.getLogText();
},
computed: {
splitText() {
return this.text.split("/n");
},
},
methods: {
async getLogText() {
this.text = await api.meta.getLogText(this.lines);
},
getClass(text) {
const isError = text.includes("ERROR:");
if (isError) {
return "log--error";
}
},
},
};
</script>
<style scoped>
.log-text {
background-color: #e0e0e077;
}
.log--error {
color: #ef5350;
}
.line-number {
color: black;
font-weight: bold;
}
</style>

View File

@ -147,6 +147,11 @@ export default {
},
adminLinks() {
return [
{
icon: "mdi-view-dashboard",
to: "/admin/dashboard",
title: this.$t("general.dashboard"),
},
{
icon: "mdi-cog",
to: "/admin/settings",

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -9,5 +9,14 @@
"day": "numeric",
"weekday": "long",
"year": "numeric"
},
"long": {
"year": "numeric",
"month": "long",
"day": "numeric",
"weekday": "long",
"hour": "numeric",
"minute": "numeric",
"hour12": true
}
}

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Anvend",
@ -36,6 +37,7 @@
"confirm": "Bekræft",
"create": "Opret",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Slet",
"disabled": "Disabled",
"download": "Hent",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Importere",
"keyword": "Keyword",
"link": "Link",
"monday": "Mandag",
"name": "Navn",
"no": "Nej",

View File

@ -14,10 +14,10 @@
"demo-status": "Demostatus",
"development": "Entwicklung",
"download-log": "Protokoll herunterladen",
"download-recipe-json": "Rezept JSON herunterladen",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Keine Demo",
"production": "Production",
"sqlite-file": "SQLite Datei",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Entfernen der Kategorie fehlgeschlagen",
"category-filter": "Kategoriefilter",
"category-update-failed": "Aktualisieren der Kategorie fehlgeschlagen",
"category-updated": "Kategorie aktualisiert"
"category-updated": "Kategorie aktualisiert",
"category": "Category"
},
"general": {
"apply": "Anwenden",
@ -36,6 +37,7 @@
"confirm": "Bestätigen",
"create": "Erstellen",
"current-parenthesis": "(Neueste)",
"dashboard": "Dashboard",
"delete": "Löschen",
"disabled": "Deaktiviert",
"download": "Herunterladen",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Importieren",
"keyword": "Keyword",
"link": "Link",
"monday": "Montag",
"name": "Name",
"no": "Nein",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",

View File

@ -1,60 +1,63 @@
{
"404": {
"page-not-found": "404 Page Not Found",
"take-me-home": "Take me Home"
"page-not-found": "404 Página No Encontrada",
"take-me-home": "Ir a Inicio"
},
"about": {
"about-mealie": "About Mealie",
"api-docs": "API Docs",
"api-port": "API Port",
"application-mode": "Application Mode",
"database-type": "Database Type",
"default-group": "Default Group",
"demo": "Demo",
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"version": "Version"
"about-mealie": "Acerca de Mealie",
"api-docs": "Documentación API",
"api-port": "Puerto API",
"application-mode": "Modo de Aplicación",
"database-type": "Tipo de base de datos",
"default-group": "Grupo Predeterminado",
"demo": "Versión Demo",
"demo-status": "Estado Demo",
"development": "Desarrollo",
"download-log": "Descargar Log",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "No Demo",
"production": "Producción",
"database-url": "Database URL",
"version": "Versión"
},
"category": {
"category-created": "Category created",
"category-creation-failed": "Category creation failed",
"category-deleted": "Category Deleted",
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-created": "Categoría creada",
"category-creation-failed": "Error al crear categoría",
"category-deleted": "Categoría Eliminada",
"category-deletion-failed": "Error al eliminar categoría",
"category-filter": "Filtros de Categorías",
"category-update-failed": "Error al actualizar categoría",
"category-updated": "Categoría actualizada",
"category": "Category"
},
"general": {
"apply": "Apply",
"cancel": "Cancel",
"close": "Close",
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
"edit": "Edit",
"enabled": "Enabled",
"exception": "Exception",
"failed-count": "Failed: {count}",
"failure-uploading-file": "Failure uploading file",
"field-required": "Field Required",
"file-folder-not-found": "File/folder not found",
"file-uploaded": "File uploaded",
"filter": "Filter",
"friday": "Friday",
"get": "Get",
"groups": "Groups",
"image": "Image",
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"apply": "Aplicar",
"cancel": "Cancelar",
"close": "Cerrar",
"confirm": "Confirmar",
"create": "Crear",
"current-parenthesis": "(Actual)",
"dashboard": "Dashboard",
"delete": "Eliminar",
"disabled": "Deshabilitado",
"download": "Descargar",
"edit": "Editar",
"enabled": "Habilitado",
"exception": "Excepción",
"failed-count": "Error: {count}",
"failure-uploading-file": "Error al cargar el archivo",
"field-required": "Campo Requerido",
"file-folder-not-found": "No se ha encontrado el archivo o la carpeta",
"file-uploaded": "Archivo cargado",
"filter": "Filtro",
"friday": "Viernes",
"get": "Obtener",
"groups": "Grupos",
"image": "Imagen",
"image-upload-failed": "Error al subir la imagen",
"import": "Importar",
"keyword": "Etiqueta",
"link": "Enlace",
"monday": "Monday",
"name": "Name",
"no": "No",
@ -84,23 +87,23 @@
"url": "URL",
"users": "Users",
"wednesday": "Wednesday",
"yes": "Yes"
"yes": "Si"
},
"group": {
"are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete <b>{groupName}<b/>?",
"cannot-delete-default-group": "Cannot delete default group",
"cannot-delete-group-with-users": "Cannot delete group with users",
"confirm-group-deletion": "Confirm Group Deletion",
"create-group": "Create Group",
"error-updating-group": "Error updating group",
"group": "Group",
"group-deleted": "Group deleted",
"group-deletion-failed": "Group deletion failed",
"group-id-with-value": "Group ID: {groupID}",
"group-name": "Group Name",
"group-not-found": "Group not found",
"groups": "Groups",
"groups-can-only-be-set-by-administrators": "Groups can only be set by administrators",
"are-you-sure-you-want-to-delete-the-group": "Por favor, confirma que deseas eliminar <b>{groupName}<b/>",
"cannot-delete-default-group": "No se puede eliminar el grupo predeterminado",
"cannot-delete-group-with-users": "No se puede eliminar el grupo con usuarios",
"confirm-group-deletion": "Confirmar Eliminación de Grupo",
"create-group": "Crear Grupo",
"error-updating-group": "Error al actualizar grupo",
"group": "Grupo",
"group-deleted": "Grupo eliminado",
"group-deletion-failed": "Error al eliminar grupo",
"group-id-with-value": "ID del Grupo: {groupID}",
"group-name": "Nombre del Grupo",
"group-not-found": "Grupo no encontrado",
"groups": "Grupos",
"groups-can-only-be-set-by-administrators": "Los grupos sólo pueden ser establecidos por administradores",
"user-group": "User Group",
"user-group-created": "User Group Created",
"user-group-creation-failed": "User Group Creation Failed"
@ -109,13 +112,13 @@
"create-a-new-meal-plan": "Create a New Meal Plan",
"dinner-this-week": "Dinner This Week",
"dinner-today": "Dinner Today",
"edit-meal-plan": "Edit Meal Plan",
"end-date": "End Date",
"group": "Group (Beta)",
"meal-planner": "Meal Planner",
"meal-plans": "Meal Plans",
"mealplan-created": "Mealplan created",
"mealplan-creation-failed": "Mealplan creation failed",
"edit-meal-plan": "Editar Plan de Comida",
"end-date": "Fecha de Finalización",
"group": "Grupo (Beta)",
"meal-planner": "Plan de Comidas",
"meal-plans": "Planes de Comidas",
"mealplan-created": "Plan de Comida creado",
"mealplan-creation-failed": "Error al crear Plan de Comida",
"mealplan-deleted": "Mealplan Deleted",
"mealplan-deletion-failed": "Mealplan deletion failed",
"mealplan-update-failed": "Mealplan update failed",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Mode démo",
"development": "Développement",
"download-log": "Télécharger les logs",
"download-recipe-json": "Télécharger le JSON de la recette",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Non",
"production": "Production",
"sqlite-file": "Fichier SQLite",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "La suppression de la catégorie a échoué",
"category-filter": "Filtre par catégories",
"category-update-failed": "La mise à jour de la catégorie a échoué",
"category-updated": "Catégorie mise à jour"
"category-updated": "Catégorie mise à jour",
"category": "Category"
},
"general": {
"apply": "Appliquer",
@ -36,6 +37,7 @@
"confirm": "Confirmer",
"create": "Créer",
"current-parenthesis": "(Actuel)",
"dashboard": "Dashboard",
"delete": "Supprimer",
"disabled": "Désactivé",
"download": "Télécharger",
@ -55,6 +57,7 @@
"image-upload-failed": "Le téléchargement de l'image a échoué",
"import": "Importer",
"keyword": "Mot-clé",
"link": "Lien",
"monday": "Lundi",
"name": "Nom",
"no": "Non",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo status",
"development": "Versies in ontwikkeling",
"download-log": "Logbestand downloaden",
"download-recipe-json": "Download Recept JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Niet Demo",
"production": "Productie",
"sqlite-file": "SQLite bestand",
"database-url": "Database URL",
"version": "Versie"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Categorie verwijderen mislukt",
"category-filter": "Categorie Filter",
"category-update-failed": "Categorie bijwerken mislukt",
"category-updated": "Categorie bijgewerkt"
"category-updated": "Categorie bijgewerkt",
"category": "Category"
},
"general": {
"apply": "Toepassen",
@ -36,6 +37,7 @@
"confirm": "Bevestigen",
"create": "Maak",
"current-parenthesis": "(Huidig)",
"dashboard": "Dashboard",
"delete": "Verwijderen",
"disabled": "Uitgeschakeld",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Afbeelding uploaden mislukt",
"import": "Importeren",
"keyword": "Trefwoord",
"link": "Link",
"monday": "Maandag",
"name": "Naam",
"no": "Nee",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Status demo",
"development": "Wersja testowa",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Nie demo",
"production": "Produkcyjna",
"sqlite-file": "Plik SQLite'a",
"database-url": "Database URL",
"version": "Wersja"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Zastosuj",
@ -36,6 +37,7 @@
"confirm": "Potwierdź",
"create": "Utwórz",
"current-parenthesis": "(Bieżący)",
"dashboard": "Dashboard",
"delete": "Usuń",
"disabled": "Wyłączone",
"download": "Pobierz",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Importuj",
"keyword": "Keyword",
"link": "Link",
"monday": "Poniedziałek",
"name": "Nazwa",
"no": "Nie",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirmar",
"create": "Criar",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Eliminar",
"disabled": "Disabled",
"download": "Transferir",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Importar",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Nome",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Skapa",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Ta bort",
"disabled": "Disabled",
"download": "Ladda ner",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Importera",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Namn",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "Confirm",
"create": "Create",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "Delete",
"disabled": "Disabled",
"download": "Download",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "Import",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "Name",
"no": "No",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "应用",
@ -36,6 +37,7 @@
"confirm": "确定",
"create": "创建",
"current-parenthesis": "(当前)",
"dashboard": "Dashboard",
"delete": "删除",
"disabled": "Disabled",
"download": "下载",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "导入",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "名称",
"no": "否",

View File

@ -14,10 +14,10 @@
"demo-status": "Demo Status",
"development": "Development",
"download-log": "Download Log",
"download-recipe-json": "Download Recipe JSON",
"download-recipe-json": "Last Scraped JSON",
"not-demo": "Not Demo",
"production": "Production",
"sqlite-file": "SQLite File",
"database-url": "Database URL",
"version": "Version"
},
"category": {
@ -27,7 +27,8 @@
"category-deletion-failed": "Category deletion failed",
"category-filter": "Category Filter",
"category-update-failed": "Category update failed",
"category-updated": "Category updated"
"category-updated": "Category updated",
"category": "Category"
},
"general": {
"apply": "Apply",
@ -36,6 +37,7 @@
"confirm": "確定",
"create": "創建",
"current-parenthesis": "(Current)",
"dashboard": "Dashboard",
"delete": "删除",
"disabled": "Disabled",
"download": "下载",
@ -55,6 +57,7 @@
"image-upload-failed": "Image upload failed",
"import": "導入",
"keyword": "Keyword",
"link": "Link",
"monday": "Monday",
"name": "名稱",
"no": "No",

View File

@ -22,19 +22,26 @@
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<TheDownloadBtn :button-text="$t('about.download-recipe-json')" download-url="/api/debug/last-recipe-json" />
<TheDownloadBtn :button-text="$t('about.download-log')" download-url="/api/debug/log" />
<TheDownloadBtn download-url="/api/debug/last-recipe-json">
<template v-slot:default="{ downloadFile }">
<v-btn color="primary" @click="downloadFile">
<v-icon left> mdi-code-braces </v-icon> {{ $t("about.download-recipe-json") }}
</v-btn>
</template>
</TheDownloadBtn>
</v-card-actions>
<v-divider></v-divider>
</v-card>
<LogCard />
</div>
</template>
<script>
import { api } from "@/api";
import TheDownloadBtn from "@/components/UI/Buttons/TheDownloadBtn";
import LogCard from "@/components/UI/LogCard.vue";
export default {
components: { TheDownloadBtn },
components: { TheDownloadBtn, LogCard },
data() {
return {
prettyInfo: [],
@ -79,9 +86,9 @@ export default {
value: debugInfo.dbType,
},
{
name: this.$t("about.sqlite-file"),
name: this.$t("about.database-url"),
icon: "mdi-file-cabinet",
value: debugInfo.sqliteFile,
value: debugInfo.dbUrl,
},
{
name: this.$t("about.default-group"),
@ -93,5 +100,3 @@ export default {
},
};
</script>
<style lang="scss" scoped></style>

View File

@ -19,7 +19,7 @@
<div class="text-truncate">
<strong>{{ backup.name }}</strong>
</div>
<div class="text-truncate">{{ $d(new Date(backup.date), "medium") }}</div>
<div class="text-truncate">{{ $d(Date.parse(backup.date), "medium") }}</div>
</v-col>
</v-row>
</v-card-text>

View File

@ -15,7 +15,7 @@
</v-toolbar-items>
</v-toolbar>
<v-card-title> {{ name }} </v-card-title>
<v-card-subtitle class="mb-n3"> {{ $d(new Date(date), "medium") }} </v-card-subtitle>
<v-card-subtitle class="mb-n3" v-if="date"> {{ $d(new Date(date), "medium") }} </v-card-subtitle>
<v-divider></v-divider>
<v-card-text>

View File

@ -0,0 +1,144 @@
<template>
<div>
<ImportSummaryDialog ref="report" />
<ImportDialog
:name="selectedName"
:date="selectedDate"
ref="import_dialog"
@import="importBackup"
@delete="deleteBackup"
/>
<StatCard icon="mdi-backup-restore" :color="color">
<template v-slot:after-heading>
<div class="ml-auto text-right">
<div class="body-3 grey--text font-weight-light" v-text="'Backups'" />
<h3 class="display-2 font-weight-light text--primary">
<small> {{ total }}</small>
</h3>
</div>
</template>
<div class="d-flex row py-3 justify-end">
<TheUploadBtn url="/api/backups/upload" @uploaded="getAvailableBackups">
<template v-slot="{ isSelecting, onButtonClick }">
<v-btn :loading="isSelecting" class="mx-2" small :color="color" @click="onButtonClick">
<v-icon left> mdi-cloud-upload </v-icon> Upload
</v-btn>
</template>
</TheUploadBtn>
<v-btn :loading="loading" class="mx-2" small :color="color" @click="createBackup">
<v-icon left> mdi-plus </v-icon> Create
</v-btn>
</div>
<template v-slot:bottom>
<v-virtual-scroll height="290" item-height="70" :items="availableBackups">
<template v-slot:default="{ item }">
<v-list-item @click.prevent="openDialog(item)">
<v-list-item-avatar>
<v-icon large dark :color="color">
mdi-backup-restore
</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="item.name"></v-list-item-title>
<v-list-item-subtitle>
{{ $d(Date.parse(item.date), "medium") }}
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action class="ml-auto">
<v-btn large icon @click.stop="deleteBackup(item.name)">
<v-icon color="error">mdi-delete</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</template>
</v-virtual-scroll>
</template>
</StatCard>
</div>
</template>
<script>
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";
export default {
components: { StatCard, ImportDialog, TheUploadBtn, ImportSummaryDialog },
data() {
return {
color: "secondary",
selectedName: "",
selectedDate: "",
loading: false,
events: [],
availableBackups: [],
};
},
computed: {
total() {
return this.availableBackups.length;
},
},
mounted() {
this.getAvailableBackups();
},
methods: {
async getAvailableBackups() {
const response = await api.backups.requestAvailable();
this.availableBackups = response.imports;
console.log(this.availableBackups);
},
async deleteBackup(name) {
this.loading = true;
await api.backups.delete(name);
this.loading = false;
this.getAvailableBackups();
},
openDialog(backup) {
this.selectedDate = backup.date;
this.selectedName = backup.name;
this.$refs.import_dialog.open();
},
async importBackup(data) {
this.loading = true;
const response = await api.backups.import(data.name, data);
if (response) {
const importData = response.data;
this.$refs.report.open(importData);
}
this.loading = false;
},
async createBackup() {
this.loading = true;
let data = {
tag: this.tag,
options: {
recipes: true,
settings: true,
themes: true,
users: true,
groups: true,
},
templates: [],
};
if (await api.backups.create(data)) {
this.getAvailableBackups();
}
this.loading = false;
},
},
};
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,110 @@
<template>
<div>
<StatCard icon="mdi-bell-ring" :color="color">
<template v-slot:after-heading>
<div class="ml-auto text-right">
<div class="body-3 grey--text font-weight-light" v-text="'Events'" />
<h3 class="display-2 font-weight-light text--primary">
<small> {{ total }} </small>
</h3>
</div>
</template>
<div class="d-flex row py-3 justify-end">
<v-btn class="mx-2" small :color="color" @click="deleteAll">
<v-icon left> mdi-notification-clear-all </v-icon> Clear
</v-btn>
</div>
<template v-slot:bottom>
<v-virtual-scroll height="290" item-height="70" :items="events">
<template v-slot:default="{ item }">
<v-list-item>
<v-list-item-avatar>
<v-icon large dark :color="icons[item.category].color">
{{ icons[item.category].icon }}
</v-icon>
</v-list-item-avatar>
<v-list-item-content>
<v-list-item-title v-text="item.title"></v-list-item-title>
<v-list-item-subtitle v-text="item.text"></v-list-item-subtitle>
<v-list-item-subtitle>
{{ $d(Date.parse(item.timeStamp), "long") }}
</v-list-item-subtitle>
</v-list-item-content>
<v-list-item-action class="ml-auto">
<v-btn large icon @click="deleteEvent(item.id)">
<v-icon color="error">mdi-delete</v-icon>
</v-btn>
</v-list-item-action>
</v-list-item>
</template>
</v-virtual-scroll>
</template>
</StatCard>
</div>
</template>
<script>
import { api } from "@/api";
import StatCard from "./StatCard";
export default {
components: { StatCard },
data() {
return {
color: "secondary",
total: 0,
events: [],
icons: {
general: {
icon: "mdi-information",
color: "info",
},
recipe: {
icon: "mdi-silverware-fork-knife",
color: "primary",
},
backup: {
icon: "mdi-backup-restore",
color: "primary",
},
schedule: {
icon: "mdi-calendar-clock",
color: "primary",
},
migration: {
icon: "mdi-database-import",
color: "primary",
},
signup: {
icon: "mdi-account",
color: "primary",
},
},
};
},
mounted() {
this.getEvents();
},
methods: {
async getEvents() {
const events = await api.about.getEvents();
this.events = events.events;
this.total = events.total;
},
async deleteEvent(id) {
await api.about.deleteEvent(id);
this.getEvents();
},
async deleteAll() {
await api.about.deleteAllEvents();
this.getEvents();
},
},
};
</script>
<style lang="scss" scoped>
</style>

View File

@ -0,0 +1,100 @@
w<template>
<v-card v-bind="$attrs" :class="classes" class="v-card--material pa-3">
<div class="d-flex grow flex-wrap">
<v-sheet
:color="color"
:max-height="icon ? 90 : undefined"
:width="icon ? 'auto' : '100%'"
elevation="6"
class="text-start v-card--material__heading mb-n6 mt-n10 pa-7"
dark
>
<v-icon v-if="icon" size="40" v-text="icon" />
<div v-if="text" class="headline font-weight-thin" v-text="text" />
</v-sheet>
<div v-if="$slots['after-heading']" class="ml-auto">
<slot name="after-heading" />
</div>
</div>
<slot />
<template v-if="$slots.actions">
<v-divider class="mt-2" />
<v-card-actions class="pb-0">
<slot name="actions" />
</v-card-actions>
</template>
<template v-if="$slots.bottom">
<v-divider class="mt-2" />
<div class="pb-0">
<slot name="bottom" />
</div>
</template>
</v-card>
</template>
<script>
export default {
name: "MaterialCard",
props: {
avatar: {
type: String,
default: "",
},
color: {
type: String,
default: "primary",
},
icon: {
type: String,
default: undefined,
},
image: {
type: Boolean,
default: false,
},
text: {
type: String,
default: "",
},
title: {
type: String,
default: "",
},
},
computed: {
classes() {
return {
"v-card--material--has-heading": this.hasHeading,
};
},
hasHeading() {
return false;
},
hasAltHeading() {
return false;
},
},
};
</script>
<style lang="sass">
.v-card--material
&__avatar
position: relative
top: -64px
margin-bottom: -32px
&__heading
position: relative
top: -40px
transition: .3s ease
z-index: 1
</style>

View File

@ -0,0 +1,119 @@
<template>
<div class="mt-10">
<v-row>
<v-col cols="12" sm="12" md="4">
<StatCard icon="mdi-silverware-fork-knife">
<template v-slot:after-heading>
<div class="ml-auto text-right">
<div class="body-3 grey--text font-weight-light" v-text="'Recipes'" />
<h3 class="display-2 font-weight-light text--primary">
<small> {{ statistics.totalRecipes }}</small>
</h3>
</div>
</template>
<template v-slot:actions>
<div class="d-flex row py-3 justify-space-around">
<v-btn small color="primary" :to="{ path: '/admin/toolbox/', query: { tab: 'organize', filter: 'tag' } }">
<v-icon left> mdi-tag </v-icon> Untagged {{ statistics.untaggedRecipes }}
</v-btn>
<v-btn
small
color="primary"
:to="{ path: '/admin/toolbox/', query: { tab: 'organize', filter: 'category' } }"
>
<v-icon left> mdi-tag </v-icon> Uncategorized {{ statistics.uncategorizedRecipes }}
</v-btn>
</div>
</template>
</StatCard>
</v-col>
<v-col cols="12" sm="12" md="4">
<StatCard icon="mdi-account">
<template v-slot:after-heading>
<div class="ml-auto text-right">
<div class="body-3 grey--text font-weight-light" v-text="'Users'" />
<h3 class="display-2 font-weight-light text--primary">
<small> {{ statistics.totalUsers }}</small>
</h3>
</div>
</template>
<template v-slot:actions>
<div class="ml-auto">
<v-btn color="primary" small to="/admin/manage-users?tab=users">
<v-icon left>mdi-account</v-icon>
Manage Users
</v-btn>
</div>
</template>
</StatCard>
</v-col>
<v-col cols="12" sm="12" md="4">
<StatCard icon="mdi-account-group">
<template v-slot:after-heading>
<div class="ml-auto text-right">
<div class="body-3 grey--text font-weight-light" v-text="'Groups'" />
<h3 class="display-2 font-weight-light text--primary">
<small> {{ statistics.totalGroups }}</small>
</h3>
</div>
</template>
<template v-slot:actions>
<div class="ml-auto">
<v-btn color="primary" small to="/admin/manage-users?tab=groups">
<v-icon left>mdi-account-group</v-icon>
Manage Groups
</v-btn>
</div>
</template>
</StatCard>
</v-col>
</v-row>
<v-row class="mt-10">
<v-col cols="12" sm="12" lg="6">
<EventViewer />
</v-col>
<v-col cols="12" sm="12" lg="6"> <BackupViewer /> </v-col>
</v-row>
</div>
</template>
<script>
import { api } from "@/api";
import StatCard from "./StatCard";
import EventViewer from "./EventViewer";
import BackupViewer from "./BackupViewer";
export default {
components: { StatCard, EventViewer, BackupViewer },
data() {
return {
statistics: {
totalGroups: 0,
totalRecipes: 0,
totalUsers: 0,
uncategorizedRecipes: 0,
untaggedRecipes: 0,
},
};
},
mounted() {
this.getStatistics();
},
methods: {
async getStatistics() {
this.statistics = await api.meta.getStatistics();
},
},
};
</script>
<style>
.grid-style {
flex-grow: inherit;
display: inline-flex;
flex-wrap: wrap;
gap: 10px;
}
</style>

View File

@ -160,10 +160,10 @@ export default {
methods: {
updateClipboard(newClip) {
navigator.clipboard.writeText(newClip).then(
function() {
() => {
console.log("Copied", newClip);
},
function() {
() => {
console.log("Copy Failed", newClip);
}
);

View File

@ -4,30 +4,30 @@
<v-tabs v-model="tab" background-color="primary" centered dark icons-and-text>
<v-tabs-slider></v-tabs-slider>
<v-tab>
<v-tab href="#users">
{{ $t("user.users") }}
<v-icon>mdi-account</v-icon>
</v-tab>
<v-tab>
<v-tab href="#sign-ups">
{{ $t("signup.sign-up-links") }}
<v-icon>mdi-account-plus-outline</v-icon>
</v-tab>
<v-tab>
<v-tab href="#groups">
{{ $t("group.groups") }}
<v-icon>mdi-account-group</v-icon>
</v-tab>
</v-tabs>
<v-tabs-items v-model="tab">
<v-tab-item>
<v-tab-item value="users">
<TheUserTable />
</v-tab-item>
<v-tab-item>
<v-tab-item value="sign-ups">
<TheSignUpTable />
</v-tab-item>
<v-tab-item>
<v-tab-item value="groups">
<GroupDashboard />
</v-tab-item>
</v-tabs-items>
@ -42,9 +42,17 @@ import TheSignUpTable from "./TheSignUpTable";
export default {
components: { TheUserTable, GroupDashboard, TheSignUpTable },
data() {
return {
tab: 0,
};
return {};
},
computed: {
tab: {
set(tab) {
this.$router.replace({ query: { ...this.$route.query, tab } });
},
get() {
return this.$route.query.tab;
},
},
},
mounted() {
this.$store.dispatch("requestAllGroups");

View File

@ -0,0 +1,72 @@
<template>
<v-card outlined class="mt-n1">
<div class="d-flex justify-center align-center pa-2 flex-wrap">
<v-btn-toggle v-model="filter" mandatory color="primary">
<v-btn small value="category">
<v-icon>mdi-tag-multiple</v-icon>
{{ $t("category.category") }}
</v-btn>
<v-btn small value="tag">
<v-icon>mdi-tag-multiple</v-icon>
{{ $t("tag.tags") }}
</v-btn>
</v-btn-toggle>
<v-spacer v-if="!isMobile"> </v-spacer>
<FuseSearchBar :raw-data="allItems" @results="filterItems" :search="searchString">
<v-text-field
v-model="searchString"
clearable
solo
dense
class="mx-2"
hide-details
single-line
:placeholder="$t('search.search')"
prepend-inner-icon="mdi-magnify"
>
</v-text-field>
</FuseSearchBar>
</div>
</v-card>
</template>
<script>
import FuseSearchBar from "@/components/UI/Search/FuseSearchBar";
export default {
components: { FuseSearchBar },
data() {
return {
buttonToggle: 0,
allItems: [],
searchString: "",
searchResults: [],
};
},
computed: {
isMobile() {
return this.$vuetify.breakpoint.name === "xs";
},
isCategory() {
return this.buttonToggle === 0;
},
filter: {
set(filter) {
this.$router.replace({ query: { ...this.$route.query, filter } });
},
get() {
return this.$route.query.filter;
},
},
},
methods: {
filterItems(val) {
this.searchResults = val.map(x => x.item);
},
},
};
</script>
<style lang="scss" scoped>
</style>

View File

@ -4,20 +4,25 @@
<v-tabs v-model="tab" background-color="primary" centered dark icons-and-text>
<v-tabs-slider></v-tabs-slider>
<v-tab>
<v-tab href="#category-editor">
{{ $t("recipe.categories") }}
<v-icon>mdi-tag-multiple-outline</v-icon>
</v-tab>
<v-tab>
<v-tab href="#tag-editor">
{{ $t("tag.tags") }}
<v-icon>mdi-tag-multiple-outline</v-icon>
</v-tab>
<v-tab href="#organize">
Organize
<v-icon>mdi-broom</v-icon>
</v-tab>
</v-tabs>
<v-tabs-items v-model="tab">
<v-tab-item><CategoryTagEditor :is-tags="false"/></v-tab-item>
<v-tab-item><CategoryTagEditor :is-tags="true" /> </v-tab-item>
<v-tab-item value="category-editor"> <CategoryTagEditor :is-tags="false"/></v-tab-item>
<v-tab-item value="tag-editor"> <CategoryTagEditor :is-tags="true" /> </v-tab-item>
<v-tab-item value="organize"> <RecipeOrganizer :is-tags="true" /> </v-tab-item>
</v-tabs-items>
</v-card>
</div>
@ -25,14 +30,24 @@
<script>
import CategoryTagEditor from "./CategoryTagEditor";
import RecipeOrganizer from "./RecipeOrganizer";
export default {
components: {
CategoryTagEditor,
RecipeOrganizer,
},
computed: {
tab: {
set(tab) {
this.$router.replace({ query: { ...this.$route.query, tab } });
},
get() {
return this.$route.query.tab;
},
},
},
data() {
return {
tab: 0,
};
return {};
},
};
</script>

View File

@ -37,7 +37,6 @@ export default {
return this.$store.getters.getSiteSettings;
},
recentRecipes() {
console.log("Recent Recipes");
return this.$store.getters.getRecentRecipes;
},
},

View File

@ -25,22 +25,7 @@
class="sticky"
/>
<RecipeViewer
v-if="!form"
:name="recipeDetails.name"
:ingredients="recipeDetails.recipeIngredient"
:description="recipeDetails.description"
:instructions="recipeDetails.recipeInstructions"
:tags="recipeDetails.tags"
:categories="recipeDetails.recipeCategory"
:notes="recipeDetails.notes"
:rating="recipeDetails.rating"
:yields="recipeDetails.recipeYield"
:orgURL="recipeDetails.orgURL"
:nutrition="recipeDetails.nutrition"
:assets="recipeDetails.assets"
:slug="recipeDetails.slug"
/>
<RecipeViewer v-if="!form" :recipe="recipeDetails" />
<VJsoneditor
@error="logError()"
class="mt-10"

View File

@ -8,6 +8,7 @@ import ManageUsers from "@/pages/Admin/ManageUsers";
import Settings from "@/pages/Admin/Settings";
import About from "@/pages/Admin/About";
import ToolBox from "@/pages/Admin/ToolBox";
import Dashboard from "@/pages/Admin/Dashboard";
import { store } from "../store";
export const adminRoutes = {
@ -87,5 +88,12 @@ export const adminRoutes = {
title: "general.about",
},
},
{
path: "dashboard",
component: Dashboard,
meta: {
title: "general.dashboard",
},
},
],
};

View File

@ -4,11 +4,13 @@ from fastapi import FastAPI
from mealie.core import root_logger
from mealie.core.config import APP_VERSION, settings
from mealie.routes import backup_routes, debug_routes, migration_routes, theme_routes, utility_routes
from mealie.routes.about import about_router
from mealie.routes.groups import groups
from mealie.routes.mealplans import mealplans
from mealie.routes.recipe import router as recipe_router
from mealie.routes.site_settings import all_settings
from mealie.routes.users import users
from mealie.services.events import create_general_event
logger = root_logger.get_logger()
@ -31,6 +33,7 @@ def api_routers():
app.include_router(groups.router)
# Recipes
app.include_router(recipe_router)
app.include_router(about_router)
# Meal Routes
app.include_router(mealplans.router)
# Settings Routes
@ -53,6 +56,7 @@ def system_startup():
logger.info("-----SYSTEM STARTUP----- \n")
logger.info("------APP SETTINGS------")
logger.info(settings.json(indent=4, exclude={"SECRET", "DEFAULT_PASSWORD", "SFTP_PASSWORD", "SFTP_USERNAME"}))
create_general_event("Application Startup", f"Mealie API started on port {settings.API_PORT}")
def main():

View File

@ -4,8 +4,8 @@ import sys
from mealie.core.config import DATA_DIR
LOGGER_FILE = DATA_DIR.joinpath("mealie.log")
LOGGER_FORMAT = "%(levelname)s: \t%(message)s"
DATE_FORMAT = "%d-%b-%y %H:%M:%S"
LOGGER_FORMAT = "%(levelname)s: %(asctime)s \t%(message)s"
logging.basicConfig(level=logging.INFO, format=LOGGER_FORMAT, datefmt="%d-%b-%y %H:%M:%S")
@ -30,6 +30,9 @@ def logger_init() -> logging.Logger:
return logger
root_logger = logger_init()
def get_logger(module=None) -> logging.Logger:
""" Returns a child logger for mealie """
global root_logger
@ -38,6 +41,3 @@ def get_logger(module=None) -> logging.Logger:
return root_logger
return root_logger.getChild(module)
root_logger = logger_init()

View File

@ -1,6 +1,7 @@
from logging import getLogger
from mealie.db.db_base import BaseDocument
from mealie.db.models.event import Event
from mealie.db.models.group import Group
from mealie.db.models.mealplan import MealPlanModel
from mealie.db.models.recipe.recipe import Category, RecipeModel, Tag
@ -9,6 +10,7 @@ from mealie.db.models.sign_up import SignUp
from mealie.db.models.theme import SiteThemeModel
from mealie.db.models.users import User
from mealie.schema.category import RecipeCategoryResponse, RecipeTagResponse
from mealie.schema.events import Event as EventSchema
from mealie.schema.meal import MealPlanInDB
from mealie.schema.recipe import Recipe
from mealie.schema.settings import CustomPageOut
@ -18,7 +20,6 @@ from mealie.schema.theme import SiteTheme
from mealie.schema.user import GroupInDB, UserInDB
from sqlalchemy.orm.session import Session
logger = getLogger()
@ -35,6 +36,26 @@ class _Recipes(BaseDocument):
return f"{slug}.{extension}"
def count_uncategorized(self, session: Session, count=True, override_schema=None) -> int:
eff_schema = override_schema or self.schema
if count:
return session.query(self.sql_model).filter(RecipeModel.recipe_category == None).count() # noqa: 711
else:
return [
eff_schema.from_orm(x)
for x in session.query(self.sql_model).filter(RecipeModel.tags == None).all() # noqa: 711
]
def count_untagged(self, session: Session, count=True, override_schema=None) -> int:
eff_schema = override_schema or self.schema
if count:
return session.query(self.sql_model).filter(RecipeModel.tags == None).count() # noqa: 711
else:
return [
eff_schema.from_orm(x)
for x in session.query(self.sql_model).filter(RecipeModel.tags == None).all() # noqa: 711
]
class _Categories(BaseDocument):
def __init__(self) -> None:
@ -110,8 +131,6 @@ class _Groups(BaseDocument):
"""
group: GroupInDB = session.query(self.sql_model).filter_by(**{match_key: match_value}).one_or_none()
# Potentially not needed? column is sorted by SqlAlchemy based on startDate
# return sorted(group.mealplans, key=lambda mealplan: mealplan.startDate)
return group.mealplans
@ -129,6 +148,13 @@ class _CustomPages(BaseDocument):
self.schema = CustomPageOut
class _Events(BaseDocument):
def __init__(self) -> None:
self.primary_key = "id"
self.sql_model = Event
self.schema = EventSchema
class Database:
def __init__(self) -> None:
self.recipes = _Recipes()
@ -141,6 +167,7 @@ class Database:
self.sign_ups = _SignUps()
self.groups = _Groups()
self.custom_pages = _CustomPages()
self.events = _Events()
db = Database()

View File

@ -23,6 +23,14 @@ class BaseDocument:
) -> List[dict]:
eff_schema = override_schema or self.schema
if order_by:
order_attr = getattr(self.sql_model, str(order_by))
return [
eff_schema.from_orm(x)
for x in session.query(self.sql_model).order_by(order_attr.desc()).offset(start).limit(limit).all()
]
return [eff_schema.from_orm(x) for x in session.query(self.sql_model).offset(start).limit(limit).all()]
def get_all_limit_columns(self, session: Session, fields: List[str], limit: int = None) -> List[SqlAlchemyBase]:
@ -154,3 +162,14 @@ class BaseDocument:
session.commit()
return results_as_model
def delete_all(self, session: Session) -> None:
session.query(self.sql_model).delete()
session.commit()
def count_all(self, session: Session, match_key=None, match_value=None) -> int:
if None in [match_key, match_value]:
return session.query(self.sql_model).count()
else:
return session.query(self.sql_model).filter_by(**{match_key: match_value}).count()

View File

@ -5,6 +5,7 @@ from mealie.db.database import db
from mealie.db.db_setup import create_session
from mealie.schema.settings import SiteSettings
from mealie.schema.theme import SiteTheme
from mealie.services.events import create_general_event
from sqlalchemy.orm import Session
logger = root_logger.get_logger("init_db")
@ -58,6 +59,7 @@ def main():
else:
print("Database Doesn't Exists, Initializing...")
init_db()
create_general_event("Initialize Database", "Initialize database with default values", session)
if __name__ == "__main__":

View File

@ -1,7 +1,8 @@
from mealie.db.models.event import *
from mealie.db.models.group import *
from mealie.db.models.mealplan import *
from mealie.db.models.recipe.recipe import *
from mealie.db.models.settings import *
from mealie.db.models.sign_up import *
from mealie.db.models.theme import *
from mealie.db.models.users import *
from mealie.db.models.sign_up import *
from mealie.db.models.group import *

17
mealie/db/models/event.py Normal file
View File

@ -0,0 +1,17 @@
import sqlalchemy as sa
from mealie.db.models.model_base import BaseMixins, SqlAlchemyBase
class Event(SqlAlchemyBase, BaseMixins):
__tablename__ = "events"
id = sa.Column(sa.Integer, primary_key=True)
title = sa.Column(sa.String)
text = sa.Column(sa.String)
time_stamp = sa.Column(sa.DateTime)
category = sa.Column(sa.String)
def __init__(self, title, text, time_stamp, category, *args, **kwargs) -> None:
self.title = title
self.text = text
self.time_stamp = time_stamp
self.category = category

View File

@ -4,5 +4,5 @@ SqlAlchemyBase = dec.declarative_base()
class BaseMixins:
def _pass_on_me():
pass
def update(self, *args, **kwarg):
self.__init__(*args, **kwarg)

View File

@ -0,0 +1,7 @@
from fastapi import APIRouter
from .events import router as events_router
about_router = APIRouter(prefix="/api/about")
about_router.include_router(events_router)

View File

@ -0,0 +1,28 @@
from fastapi import APIRouter, Depends
from mealie.db.database import db
from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.events import EventsOut
from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/events", tags=["App Events"])
@router.get("", response_model=EventsOut)
async def get_events(session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
""" Get event from the Database """
# Get Item
return EventsOut(total=db.events.count_all(session), events=db.events.get_all(session, order_by="time_stamp"))
@router.delete("")
async def delete_events(session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
""" Get event from the Database """
# Get Item
return db.events.delete_all(session)
@router.delete("/{id}")
async def delete_event(id: int, session: Session = Depends(generate_session), current_user=Depends(get_current_user)):
""" Delete event from the Database """
return db.events.delete(session, id)

View File

@ -1,17 +1,21 @@
import operator
import shutil
from pathlib import Path
from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status
from mealie.core.config import app_dirs
from mealie.core.root_logger import get_logger
from mealie.core.security import create_file_token
from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.backup import BackupJob, ImportJob, Imports, LocalBackup
from mealie.services.backups import imports
from mealie.services.backups.exports import backup_all
from mealie.services.events import create_backup_event
from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/api/backups", tags=["Backups"], dependencies=[Depends(get_current_user)])
logger = get_logger()
@router.get("/available", response_model=Imports)
@ -43,8 +47,10 @@ def export_database(data: BackupJob, session: Session = Depends(generate_session
export_users=data.options.users,
export_groups=data.options.groups,
)
create_backup_event("Database Backup", f"Manual Backup Created '{Path(export_path).name}'", session)
return {"export_path": export_path}
except Exception:
except Exception as e:
logger.error(e)
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR)
@ -72,7 +78,7 @@ async def download_backup_file(file_name: str):
def import_database(file_name: str, import_data: ImportJob, session: Session = Depends(generate_session)):
""" Import a database backup file generated from Mealie. """
return imports.import_database(
db_import = imports.import_database(
session=session,
archive=import_data.name,
import_recipes=import_data.recipes,
@ -84,6 +90,8 @@ def import_database(file_name: str, import_data: ImportJob, session: Session = D
force_import=import_data.force,
rebase=import_data.rebase,
)
create_backup_event("Database Restore", f"Restored Database File {file_name}", session)
return db_import
@router.delete("/{file_name}/delete", status_code=status.HTTP_200_OK)

View File

@ -2,8 +2,11 @@ from fastapi import APIRouter, Depends
from mealie.core.config import APP_VERSION, app_dirs, settings
from mealie.core.root_logger import LOGGER_FILE
from mealie.core.security import create_file_token
from mealie.db.database import db
from mealie.db.db_setup import generate_session
from mealie.routes.deps import get_current_user
from mealie.schema.debug import AppInfo, DebugInfo
from mealie.schema.about import AppInfo, AppStatistics, DebugInfo
from sqlalchemy.orm.session import Session
router = APIRouter(prefix="/api/debug", tags=["Debug"])
@ -18,11 +21,23 @@ async def get_debug_info(current_user=Depends(get_current_user)):
demo_status=settings.IS_DEMO,
api_port=settings.API_PORT,
api_docs=settings.API_DOCS,
db_type=settings.DB_ENGINE,
db_url=settings.DB_URL,
default_group=settings.DEFAULT_GROUP,
)
@router.get("/statistics")
async def get_app_statistics(session: Session = Depends(generate_session)):
return AppStatistics(
total_recipes=db.recipes.count_all(session),
uncategorized_recipes=db.recipes.count_uncategorized(session),
untagged_recipes=db.recipes.count_untagged(session),
total_users=db.users.count_all(session),
total_groups=db.groups.count_all(session),
)
@router.get("/version")
async def get_mealie_version():
""" Returns the current version of mealie"""

Some files were not shown because too many files have changed in this diff Show More