From f2d2b79a5709657e0530e595530a8739ce910604 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 3 May 2021 18:23:41 -0800 Subject: [PATCH 01/10] New Crowdin updates (#385) * New translations en-US.json (Spanish) * New translations en-US.json (Spanish) * New translations en-US.json (French) --- frontend/src/locales/messages/es-ES.json | 144 +++++++++++------------ frontend/src/locales/messages/fr-FR.json | 2 +- 2 files changed, 73 insertions(+), 73 deletions(-) diff --git a/frontend/src/locales/messages/es-ES.json b/frontend/src/locales/messages/es-ES.json index 0ca827e7f..1fd0f815c 100644 --- a/frontend/src/locales/messages/es-ES.json +++ b/frontend/src/locales/messages/es-ES.json @@ -1,61 +1,61 @@ { "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": "Descargar Receta JSON", + "not-demo": "No Demo", + "production": "Producción", + "sqlite-file": "Archivo SQLite", + "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" }, "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", - "link": "Link", + "apply": "Aplicar", + "cancel": "Cancelar", + "close": "Cerrar", + "confirm": "Confirmar", + "create": "Crear", + "current-parenthesis": "(Actual)", + "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", @@ -85,23 +85,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 {groupName}?", - "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 {groupName}", + "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" @@ -110,13 +110,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", diff --git a/frontend/src/locales/messages/fr-FR.json b/frontend/src/locales/messages/fr-FR.json index c90cfab6b..17f27227a 100644 --- a/frontend/src/locales/messages/fr-FR.json +++ b/frontend/src/locales/messages/fr-FR.json @@ -55,7 +55,7 @@ "image-upload-failed": "Le téléchargement de l'image a échoué", "import": "Importer", "keyword": "Mot-clé", - "link": "Link", + "link": "Lien", "monday": "Lundi", "name": "Nom", "no": "Non", From 5580d177c3f5fdc8985994d8caacc9bf5d86e8ad Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 3 May 2021 19:32:37 -0800 Subject: [PATCH 02/10] feature/finish-recipe-assets (#384) * add features to readme * Copy markdown reference * prop as whole recipe * parameter as url instead of query * add card styling to editor * move images to /recipes/{slug}/images * add image to breaking changes * fix delete and import errors * fix debug/about response * logger updates * dashboard ui * add server side events * unorganized routes * default slot * add backup viewer to dashboard * format * add dialog to backup imports * initial event support * delete assets when removed Co-authored-by: hay-kot --- Caddyfile | 4 +- README.md | 8 +- docs/docs/changelog/v0.5.0.md | 3 + frontend/src/api/about.js | 59 +++++++ frontend/src/api/index.js | 2 + frontend/src/api/meta.js | 18 ++- frontend/src/api/recipe.js | 10 +- .../src/components/Recipe/Parts/Assets.vue | 25 ++- .../components/Recipe/RecipeEditor/index.vue | 45 ++++-- .../components/Recipe/RecipeViewer/index.vue | 52 +++---- .../components/UI/Buttons/TheDownloadBtn.vue | 10 +- .../components/UI/Buttons/TheUploadBtn.vue | 12 +- frontend/src/components/UI/LogCard.vue | 81 ++++++++++ frontend/src/components/UI/TheSidebar.vue | 5 + .../src/locales/dateTimeFormats/en-US.json | 9 ++ .../src/locales/dateTimeFormats/es-ES.json | 9 ++ frontend/src/locales/messages/en-US.json | 8 +- frontend/src/pages/Admin/About/index.vue | 19 ++- .../Admin/Backup/AvailableBackupCard.vue | 2 +- .../src/pages/Admin/Backup/ImportDialog.vue | 2 +- .../pages/Admin/Dashboard/BackupViewer.vue | 144 ++++++++++++++++++ .../src/pages/Admin/Dashboard/EventViewer.vue | 110 +++++++++++++ .../src/pages/Admin/Dashboard/StatCard.vue | 100 ++++++++++++ frontend/src/pages/Admin/Dashboard/index.vue | 119 +++++++++++++++ .../Admin/ManageUsers/TheSignUpTable.vue | 4 +- .../src/pages/Admin/ManageUsers/index.vue | 26 ++-- .../pages/Admin/ToolBox/RecipeOrganizer.vue | 72 +++++++++ frontend/src/pages/Admin/ToolBox/index.vue | 29 +++- frontend/src/pages/HomePage.vue | 1 - frontend/src/pages/Recipe/ViewRecipe.vue | 17 +-- frontend/src/routes/admin.js | 8 + mealie/app.py | 4 + mealie/core/root_logger.py | 8 +- mealie/db/database.py | 33 +++- mealie/db/db_base.py | 19 +++ mealie/db/init_db.py | 2 + mealie/db/models/_all_models.py | 5 +- mealie/db/models/event.py | 17 +++ mealie/db/models/model_base.py | 4 +- mealie/routes/about/__init__.py | 7 + mealie/routes/about/events.py | 28 ++++ mealie/routes/backup_routes.py | 12 +- mealie/routes/debug_routes.py | 17 ++- mealie/routes/mealplans/crud.py | 2 +- mealie/routes/recipe/__init__.py | 4 +- mealie/routes/recipe/all_recipe_routes.py | 12 +- mealie/routes/recipe/recipe_crud_routes.py | 22 +-- .../{recipe_assets.py => recipe_media.py} | 25 +-- mealie/routes/users/crud.py | 5 +- mealie/routes/users/sign_up.py | 10 +- mealie/schema/{debug.py => about.py} | 10 ++ mealie/schema/events.py | 31 ++++ mealie/schema/recipe.py | 30 +++- mealie/services/backups/exports.py | 23 +-- mealie/services/backups/imports.py | 34 +++-- mealie/services/events.py | 40 +++++ mealie/services/image/image.py | 50 +----- mealie/services/image/minify.py | 38 +---- mealie/services/recipe/__init__.py | 0 mealie/services/recipe/media.py | 34 +++++ mealie/utils/post_webhooks.py | 3 + 61 files changed, 1276 insertions(+), 266 deletions(-) create mode 100644 frontend/src/api/about.js create mode 100644 frontend/src/components/UI/LogCard.vue create mode 100644 frontend/src/pages/Admin/Dashboard/BackupViewer.vue create mode 100644 frontend/src/pages/Admin/Dashboard/EventViewer.vue create mode 100644 frontend/src/pages/Admin/Dashboard/StatCard.vue create mode 100644 frontend/src/pages/Admin/Dashboard/index.vue create mode 100644 frontend/src/pages/Admin/ToolBox/RecipeOrganizer.vue create mode 100644 mealie/db/models/event.py create mode 100644 mealie/routes/about/__init__.py create mode 100644 mealie/routes/about/events.py rename mealie/routes/recipe/{recipe_assets.py => recipe_media.py} (73%) rename mealie/schema/{debug.py => about.py} (59%) create mode 100644 mealie/schema/events.py create mode 100644 mealie/services/events.py create mode 100644 mealie/services/recipe/__init__.py create mode 100644 mealie/services/recipe/media.py diff --git a/Caddyfile b/Caddyfile index ba5d2e454..0faf9fe7d 100644 --- a/Caddyfile +++ b/Caddyfile @@ -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 } diff --git a/README.md b/README.md index 72a9a3176..b8b0833a0 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/docs/changelog/v0.5.0.md b/docs/docs/changelog/v0.5.0.md index 369df1124..e8e665d35 100644 --- a/docs/docs/changelog/v0.5.0.md +++ b/docs/docs/changelog/v0.5.0.md @@ -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 diff --git a/frontend/src/api/about.js b/frontend/src/api/about.js new file mode 100644 index 000000000..f04322939 --- /dev/null +++ b/frontend/src/api/about.js @@ -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; + // }, +}; diff --git a/frontend/src/api/index.js b/frontend/src/api/index.js index aa1c612fc..53d22e8ac 100644 --- a/frontend/src/api/index.js +++ b/frontend/src/api/index.js @@ -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, }; diff --git a/frontend/src/api/meta.js b/frontend/src/api/meta.js index 59183c0c5..16f7477b7 100644 --- a/frontend/src/api/meta.js +++ b/frontend/src/api/meta.js @@ -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; }, }; diff --git a/frontend/src/api/recipe.js b/frontend/src/api/recipe.js index fe755f6f7..d35c4d1bc 100644 --- a/frontend/src/api/recipe.js +++ b/frontend/src/api/recipe.js @@ -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`; }, }; diff --git a/frontend/src/components/Recipe/Parts/Assets.vue b/frontend/src/components/Recipe/Parts/Assets.vue index bb8417805..62119ba21 100644 --- a/frontend/src/components/Recipe/Parts/Assets.vue +++ b/frontend/src/components/Recipe/Parts/Assets.vue @@ -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 > mdi-download - - mdi-delete - +
+ + mdi-delete + + + mdi-content-copy + +
@@ -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) + ); + }, }, }; diff --git a/frontend/src/components/Recipe/RecipeEditor/index.vue b/frontend/src/components/Recipe/RecipeEditor/index.vue index ef735d13d..bdc153ff8 100644 --- a/frontend/src/components/Recipe/RecipeEditor/index.vue +++ b/frontend/src/components/Recipe/RecipeEditor/index.vue @@ -27,23 +27,36 @@ + + + {{ $t("recipe.categories") }} + + + + + + -

{{ $t("recipe.categories") }}

- - -

{{ $t("tag.tags") }}

- + + + {{ $t("tag.tags") }} + + + + + + diff --git a/frontend/src/components/Recipe/RecipeViewer/index.vue b/frontend/src/components/Recipe/RecipeViewer/index.vue index aef80568c..d4a4fac9a 100644 --- a/frontend/src/components/Recipe/RecipeViewer/index.vue +++ b/frontend/src/components/Recipe/RecipeViewer/index.vue @@ -1,14 +1,14 @@ + + \ No newline at end of file diff --git a/frontend/src/components/UI/TheSidebar.vue b/frontend/src/components/UI/TheSidebar.vue index 834c60d97..69cfad552 100644 --- a/frontend/src/components/UI/TheSidebar.vue +++ b/frontend/src/components/UI/TheSidebar.vue @@ -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", diff --git a/frontend/src/locales/dateTimeFormats/en-US.json b/frontend/src/locales/dateTimeFormats/en-US.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/en-US.json +++ b/frontend/src/locales/dateTimeFormats/en-US.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/es-ES.json b/frontend/src/locales/dateTimeFormats/es-ES.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/es-ES.json +++ b/frontend/src/locales/dateTimeFormats/es-ES.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/messages/en-US.json b/frontend/src/locales/messages/en-US.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/en-US.json +++ b/frontend/src/locales/messages/en-US.json @@ -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", diff --git a/frontend/src/pages/Admin/About/index.vue b/frontend/src/pages/Admin/About/index.vue index efbb5b843..b410b3bfe 100644 --- a/frontend/src/pages/Admin/About/index.vue +++ b/frontend/src/pages/Admin/About/index.vue @@ -22,19 +22,26 @@ - - + + + + - - diff --git a/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue b/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue index 591b31b82..b4e333c9c 100644 --- a/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue +++ b/frontend/src/pages/Admin/Backup/AvailableBackupCard.vue @@ -19,7 +19,7 @@
{{ backup.name }}
-
{{ $d(new Date(backup.date), "medium") }}
+
{{ $d(Date.parse(backup.date), "medium") }}
diff --git a/frontend/src/pages/Admin/Backup/ImportDialog.vue b/frontend/src/pages/Admin/Backup/ImportDialog.vue index 9d4aceef3..0aed4c0e2 100644 --- a/frontend/src/pages/Admin/Backup/ImportDialog.vue +++ b/frontend/src/pages/Admin/Backup/ImportDialog.vue @@ -15,7 +15,7 @@ {{ name }} - {{ $d(new Date(date), "medium") }} + {{ $d(new Date(date), "medium") }} diff --git a/frontend/src/pages/Admin/Dashboard/BackupViewer.vue b/frontend/src/pages/Admin/Dashboard/BackupViewer.vue new file mode 100644 index 000000000..5ef125968 --- /dev/null +++ b/frontend/src/pages/Admin/Dashboard/BackupViewer.vue @@ -0,0 +1,144 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/pages/Admin/Dashboard/EventViewer.vue b/frontend/src/pages/Admin/Dashboard/EventViewer.vue new file mode 100644 index 000000000..5070c677b --- /dev/null +++ b/frontend/src/pages/Admin/Dashboard/EventViewer.vue @@ -0,0 +1,110 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/pages/Admin/Dashboard/StatCard.vue b/frontend/src/pages/Admin/Dashboard/StatCard.vue new file mode 100644 index 000000000..f79cc8289 --- /dev/null +++ b/frontend/src/pages/Admin/Dashboard/StatCard.vue @@ -0,0 +1,100 @@ +w + + + + diff --git a/frontend/src/pages/Admin/Dashboard/index.vue b/frontend/src/pages/Admin/Dashboard/index.vue new file mode 100644 index 000000000..771cc4da0 --- /dev/null +++ b/frontend/src/pages/Admin/Dashboard/index.vue @@ -0,0 +1,119 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue b/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue index 6deac3ce1..21138a520 100644 --- a/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue +++ b/frontend/src/pages/Admin/ManageUsers/TheSignUpTable.vue @@ -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); } ); diff --git a/frontend/src/pages/Admin/ManageUsers/index.vue b/frontend/src/pages/Admin/ManageUsers/index.vue index fbbb1aef5..74aa8bd27 100644 --- a/frontend/src/pages/Admin/ManageUsers/index.vue +++ b/frontend/src/pages/Admin/ManageUsers/index.vue @@ -4,30 +4,30 @@ - + {{ $t("user.users") }} mdi-account - + {{ $t("signup.sign-up-links") }} mdi-account-plus-outline - + {{ $t("group.groups") }} mdi-account-group - + - + - + @@ -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"); diff --git a/frontend/src/pages/Admin/ToolBox/RecipeOrganizer.vue b/frontend/src/pages/Admin/ToolBox/RecipeOrganizer.vue new file mode 100644 index 000000000..b83cdfcde --- /dev/null +++ b/frontend/src/pages/Admin/ToolBox/RecipeOrganizer.vue @@ -0,0 +1,72 @@ + + + + + \ No newline at end of file diff --git a/frontend/src/pages/Admin/ToolBox/index.vue b/frontend/src/pages/Admin/ToolBox/index.vue index 50333cd6d..d5d029bf2 100644 --- a/frontend/src/pages/Admin/ToolBox/index.vue +++ b/frontend/src/pages/Admin/ToolBox/index.vue @@ -4,20 +4,25 @@ - + {{ $t("recipe.categories") }} mdi-tag-multiple-outline - + {{ $t("tag.tags") }} mdi-tag-multiple-outline + + Organize + mdi-broom + - - + + + @@ -25,14 +30,24 @@ diff --git a/frontend/src/pages/HomePage.vue b/frontend/src/pages/HomePage.vue index 3b2bc4536..abc1f7d52 100644 --- a/frontend/src/pages/HomePage.vue +++ b/frontend/src/pages/HomePage.vue @@ -37,7 +37,6 @@ export default { return this.$store.getters.getSiteSettings; }, recentRecipes() { - console.log("Recent Recipes"); return this.$store.getters.getRecentRecipes; }, }, diff --git a/frontend/src/pages/Recipe/ViewRecipe.vue b/frontend/src/pages/Recipe/ViewRecipe.vue index ea97cb941..8ee5c0293 100644 --- a/frontend/src/pages/Recipe/ViewRecipe.vue +++ b/frontend/src/pages/Recipe/ViewRecipe.vue @@ -25,22 +25,7 @@ class="sticky" /> - + 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() diff --git a/mealie/db/database.py b/mealie/db/database.py index 31aad381a..0881d29bd 100644 --- a/mealie/db/database.py +++ b/mealie/db/database.py @@ -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() diff --git a/mealie/db/db_base.py b/mealie/db/db_base.py index b2c239ef8..9d178adf4 100644 --- a/mealie/db/db_base.py +++ b/mealie/db/db_base.py @@ -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() diff --git a/mealie/db/init_db.py b/mealie/db/init_db.py index f5cfa746f..a9a243a9f 100644 --- a/mealie/db/init_db.py +++ b/mealie/db/init_db.py @@ -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__": diff --git a/mealie/db/models/_all_models.py b/mealie/db/models/_all_models.py index 8034475ca..0e5daca17 100644 --- a/mealie/db/models/_all_models.py +++ b/mealie/db/models/_all_models.py @@ -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 * diff --git a/mealie/db/models/event.py b/mealie/db/models/event.py new file mode 100644 index 000000000..1ee63b120 --- /dev/null +++ b/mealie/db/models/event.py @@ -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 diff --git a/mealie/db/models/model_base.py b/mealie/db/models/model_base.py index 57662ab53..2eb385f8f 100644 --- a/mealie/db/models/model_base.py +++ b/mealie/db/models/model_base.py @@ -4,5 +4,5 @@ SqlAlchemyBase = dec.declarative_base() class BaseMixins: - def _pass_on_me(): - pass + def update(self, *args, **kwarg): + self.__init__(*args, **kwarg) diff --git a/mealie/routes/about/__init__.py b/mealie/routes/about/__init__.py new file mode 100644 index 000000000..e36affaa9 --- /dev/null +++ b/mealie/routes/about/__init__.py @@ -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) diff --git a/mealie/routes/about/events.py b/mealie/routes/about/events.py new file mode 100644 index 000000000..3eed7690f --- /dev/null +++ b/mealie/routes/about/events.py @@ -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) diff --git a/mealie/routes/backup_routes.py b/mealie/routes/backup_routes.py index 864a1380a..d4fef6c35 100644 --- a/mealie/routes/backup_routes.py +++ b/mealie/routes/backup_routes.py @@ -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) diff --git a/mealie/routes/debug_routes.py b/mealie/routes/debug_routes.py index 3e5d6c4b8..04f1516bb 100644 --- a/mealie/routes/debug_routes.py +++ b/mealie/routes/debug_routes.py @@ -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""" diff --git a/mealie/routes/mealplans/crud.py b/mealie/routes/mealplans/crud.py index 7b58853d7..bb2f17672 100644 --- a/mealie/routes/mealplans/crud.py +++ b/mealie/routes/mealplans/crud.py @@ -88,7 +88,7 @@ def get_todays_image(session: Session = Depends(generate_session), group_name: s recipe = get_todays_meal(session, group_in_db) if recipe: - recipe_image = image.read_image(recipe.slug, image_type=image.IMG_OPTIONS.ORIGINAL_IMAGE) + recipe_image = recipe.image_dir.joinpath(image.ImageOptions.ORIGINAL_IMAGE) else: raise HTTPException(status.HTTP_404_NOT_FOUND) if recipe_image: diff --git a/mealie/routes/recipe/__init__.py b/mealie/routes/recipe/__init__.py index 8af92586a..1d54034f0 100644 --- a/mealie/routes/recipe/__init__.py +++ b/mealie/routes/recipe/__init__.py @@ -1,10 +1,10 @@ from fastapi import APIRouter -from mealie.routes.recipe import all_recipe_routes, category_routes, recipe_assets, recipe_crud_routes, tag_routes +from mealie.routes.recipe import all_recipe_routes, category_routes, recipe_crud_routes, recipe_media, tag_routes router = APIRouter() router.include_router(all_recipe_routes.router) router.include_router(recipe_crud_routes.router) -router.include_router(recipe_assets.router) +router.include_router(recipe_media.router) router.include_router(category_routes.router) router.include_router(tag_routes.router) diff --git a/mealie/routes/recipe/all_recipe_routes.py b/mealie/routes/recipe/all_recipe_routes.py index 014815774..9a61751c9 100644 --- a/mealie/routes/recipe/all_recipe_routes.py +++ b/mealie/routes/recipe/all_recipe_routes.py @@ -8,7 +8,7 @@ from sqlalchemy.orm.session import Session router = APIRouter(tags=["Query All Recipes"]) -@router.get("/api/recipes/summary") +@router.get("/api/recipes/summary", response_model=list[RecipeSummary]) async def get_recipe_summary( start=0, limit=9999, @@ -29,6 +29,16 @@ async def get_recipe_summary( return db.recipes.get_all(session, limit=limit, start=start, override_schema=RecipeSummary) +@router.get("/api/recipes/summary/untagged", response_model=list[RecipeSummary]) +async def get_untagged_recipes(session: Session = Depends(generate_session)): + return db.recipes.count_untagged(session, False, override_schema=RecipeSummary) + + +@router.get("/api/recipes/summary/uncategorized", response_model=list[RecipeSummary]) +async def get_uncategorized_recipes(session: Session = Depends(generate_session)): + return db.recipes.count_uncategorized(session, False, override_schema=RecipeSummary) + + @router.post("/api/recipes/category") def filter_by_category(categories: list, session: Session = Depends(generate_session)): """ pass a list of categories and get a list of recipes associated with those categories """ diff --git a/mealie/routes/recipe/recipe_crud_routes.py b/mealie/routes/recipe/recipe_crud_routes.py index 8547aa09f..6604b84e6 100644 --- a/mealie/routes/recipe/recipe_crud_routes.py +++ b/mealie/routes/recipe/recipe_crud_routes.py @@ -4,7 +4,9 @@ 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.recipe import Recipe, RecipeURLIn -from mealie.services.image.image import delete_image, rename_image, scrape_image, write_image +from mealie.services.events import create_recipe_event +from mealie.services.image.image import scrape_image, write_image +from mealie.services.recipe.media import check_assets, delete_assets from mealie.services.scraper.scraper import create_from_url from sqlalchemy.orm.session import Session @@ -21,6 +23,8 @@ def create_from_json( """ Takes in a JSON string and loads data into the database as a new entry""" recipe: Recipe = db.recipes.create(session, data.dict()) + create_recipe_event("Recipe Created", f"Recipe '{recipe.name}' created", session=session) + return recipe.slug @@ -34,6 +38,7 @@ def parse_recipe_url( recipe = create_from_url(url.url) recipe: Recipe = db.recipes.create(session, recipe.dict()) + create_recipe_event("Recipe Created (URL)", f"'{recipe.name}' by {current_user.full_name}", session=session) return recipe.slug @@ -57,8 +62,7 @@ def update_recipe( recipe: Recipe = db.recipes.update(session, recipe_slug, data.dict()) print(recipe.assets) - if recipe_slug != recipe.slug: - rename_image(original_slug=recipe_slug, new_slug=recipe.slug) + check_assets(original_slug=recipe_slug, recipe=recipe) return recipe @@ -75,8 +79,8 @@ def patch_recipe( recipe: Recipe = db.recipes.patch( session, recipe_slug, new_data=data.dict(exclude_unset=True, exclude_defaults=True) ) - if recipe_slug != recipe.slug: - rename_image(original_slug=recipe_slug, new_slug=recipe.slug) + + check_assets(original_slug=recipe_slug, recipe=recipe) return recipe @@ -90,10 +94,10 @@ def delete_recipe( """ Deletes a recipe by slug """ try: - delete_data = db.recipes.delete(session, recipe_slug) - delete_image(recipe_slug) - - return delete_data + recipe: Recipe = db.recipes.delete(session, recipe_slug) + delete_assets(recipe_slug=recipe_slug) + create_recipe_event("Recipe Deleted", f"'{recipe.name}' deleted by {current_user.full_name}", session=session) + return recipe except Exception: raise HTTPException(status.HTTP_400_BAD_REQUEST) diff --git a/mealie/routes/recipe/recipe_assets.py b/mealie/routes/recipe/recipe_media.py similarity index 73% rename from mealie/routes/recipe/recipe_assets.py rename to mealie/routes/recipe/recipe_media.py index ebab8d246..b89605c72 100644 --- a/mealie/routes/recipe/recipe_assets.py +++ b/mealie/routes/recipe/recipe_media.py @@ -3,7 +3,6 @@ from enum import Enum from fastapi import APIRouter, Depends, File, Form, HTTPException, status from fastapi.datastructures import UploadFile -from mealie.core.config import app_dirs from mealie.db.database import db from mealie.db.db_setup import generate_session from mealie.routes.deps import get_current_user @@ -12,7 +11,7 @@ from slugify import slugify from sqlalchemy.orm.session import Session from starlette.responses import FileResponse -router = APIRouter(prefix="/api/recipes", tags=["Recipe Media"]) +router = APIRouter(prefix="/api/recipes/media", tags=["Recipe Media"]) class ImageType(str, Enum): @@ -21,25 +20,30 @@ class ImageType(str, Enum): tiny = "tiny-original.webp" -@router.get("/image/{recipe_slug}/{file_name}") +@router.get("/{recipe_slug}/image/{file_name}") async def get_recipe_img(recipe_slug: str, file_name: ImageType = ImageType.original): """Takes in a recipe slug, returns the static image. This route is proxied in the docker image and should not hit the API in production""" - recipe_image = app_dirs.IMG_DIR.joinpath(recipe_slug, file_name.value) + recipe_image = Recipe(slug=recipe_slug).image_dir.joinpath(file_name.value) + if recipe_image: return FileResponse(recipe_image) else: raise HTTPException(status.HTTP_404_NOT_FOUND) -@router.get("/{recipe_slug}/asset") -async def get_recipe_asset(recipe_slug, file_name: str): +@router.get("/{recipe_slug}/assets/{file_name}") +async def get_recipe_asset(recipe_slug: str, file_name: str): """ Returns a recipe asset """ - file = app_dirs.RECIPE_DATA_DIR.joinpath(recipe_slug, file_name) - return FileResponse(file) + file = Recipe(slug=recipe_slug).asset_dir.joinpath(file_name) + + try: + return FileResponse(file) + except Exception: + raise HTTPException(status.HTTP_404_NOT_FOUND) -@router.post("/{recipe_slug}/asset", response_model=RecipeAsset) +@router.post("/{recipe_slug}/assets", response_model=RecipeAsset) def upload_recipe_asset( recipe_slug: str, name: str = Form(...), @@ -52,8 +56,7 @@ def upload_recipe_asset( """ Upload a file to store as a recipe asset """ file_name = slugify(name) + "." + extension asset_in = RecipeAsset(name=name, icon=icon, file_name=file_name) - dest = app_dirs.RECIPE_DATA_DIR.joinpath(recipe_slug, file_name) - dest.parent.mkdir(exist_ok=True, parents=True) + dest = Recipe(slug=recipe_slug).asset_dir.joinpath(file_name) with dest.open("wb") as buffer: shutil.copyfileobj(file.file, buffer) diff --git a/mealie/routes/users/crud.py b/mealie/routes/users/crud.py index 3398545d8..63d11dbdb 100644 --- a/mealie/routes/users/crud.py +++ b/mealie/routes/users/crud.py @@ -1,6 +1,6 @@ import shutil -from fastapi import APIRouter, Depends, File, UploadFile, status, HTTPException +from fastapi import APIRouter, Depends, File, HTTPException, UploadFile, status from fastapi.responses import FileResponse from mealie.core import security from mealie.core.config import app_dirs, settings @@ -9,6 +9,7 @@ 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.user import ChangePassword, UserBase, UserIn, UserInDB, UserOut +from mealie.services.events import create_sign_up_event from sqlalchemy.orm.session import Session router = APIRouter(prefix="/api/users", tags=["Users"]) @@ -22,7 +23,7 @@ async def create_user( ): new_user.password = get_password_hash(new_user.password) - + create_sign_up_event("User Created", f"Created by {current_user.full_name}", session=session) return db.users.create(session, new_user.dict()) diff --git a/mealie/routes/users/sign_up.py b/mealie/routes/users/sign_up.py index c62d4f204..9d6bdaf79 100644 --- a/mealie/routes/users/sign_up.py +++ b/mealie/routes/users/sign_up.py @@ -1,14 +1,14 @@ import uuid +from fastapi import APIRouter, Depends, HTTPException, status from mealie.core.security import get_password_hash from mealie.db.database import db from mealie.db.db_setup import generate_session -from fastapi import APIRouter, Depends from mealie.routes.deps import get_current_user from mealie.schema.sign_up import SignUpIn, SignUpOut, SignUpToken from mealie.schema.user import UserIn, UserInDB +from mealie.services.events import create_sign_up_event from sqlalchemy.orm.session import Session -from fastapi import HTTPException, status router = APIRouter(prefix="/api/users/sign-ups", tags=["User Signup"]) @@ -20,9 +20,7 @@ async def get_all_open_sign_ups( ): """ Returns a list of open sign up links """ - all_sign_ups = db.sign_ups.get_all(session) - - return all_sign_ups + return db.sign_ups.get_all(session) @router.post("", response_model=SignUpToken) @@ -41,6 +39,7 @@ async def create_user_sign_up_key( "name": key_data.name, "admin": key_data.admin, } + create_sign_up_event("Sign-up Token Created", f"Created by {current_user.full_name}", session=session) return db.sign_ups.create(session, sign_up) @@ -63,6 +62,7 @@ async def create_user_with_token( db.users.create(session, new_user.dict()) # DeleteToken + create_sign_up_event("Sign-up Token Used", f"New User {new_user.full_name}", session=session) db.sign_ups.delete(session, token) diff --git a/mealie/schema/debug.py b/mealie/schema/about.py similarity index 59% rename from mealie/schema/debug.py rename to mealie/schema/about.py index d00c17719..202264a97 100644 --- a/mealie/schema/debug.py +++ b/mealie/schema/about.py @@ -1,7 +1,16 @@ from pathlib import Path + from fastapi_camelcase import CamelModel +class AppStatistics(CamelModel): + total_recipes: int + total_users: int + total_groups: int + uncategorized_recipes: int + untagged_recipes: int + + class AppInfo(CamelModel): production: bool version: str @@ -11,5 +20,6 @@ class AppInfo(CamelModel): class DebugInfo(AppInfo): api_port: int api_docs: bool + db_type: str db_url: Path default_group: str diff --git a/mealie/schema/events.py b/mealie/schema/events.py new file mode 100644 index 000000000..5ee4e8a77 --- /dev/null +++ b/mealie/schema/events.py @@ -0,0 +1,31 @@ +from datetime import datetime +from enum import Enum +from typing import Optional + +from fastapi_camelcase import CamelModel +from pydantic import Field + + +class EventCategory(str, Enum): + general = "general" + recipe = "recipe" + backup = "backup" + scheduled = "scheduled" + migration = "migration" + sign_up = "signup" + + +class Event(CamelModel): + id: Optional[int] + title: str + text: str + time_stamp: datetime = Field(default_factory=datetime.now) + category: EventCategory = EventCategory.general + + class Config: + orm_mode = True + + +class EventsOut(CamelModel): + total: int + events: list[Event] diff --git a/mealie/schema/recipe.py b/mealie/schema/recipe.py index 1ff2aabf7..debdec1d5 100644 --- a/mealie/schema/recipe.py +++ b/mealie/schema/recipe.py @@ -1,7 +1,9 @@ import datetime +from pathlib import Path from typing import Any, Optional from fastapi_camelcase import CamelModel +from mealie.core.config import app_dirs from mealie.db.models.recipe.recipe import RecipeModel from pydantic import BaseModel, Field, validator from pydantic.utils import GetterDict @@ -58,8 +60,8 @@ class Nutrition(CamelModel): class RecipeSummary(CamelModel): id: Optional[int] - name: str - slug: Optional[str] = "" + name: Optional[str] + slug: str = "" image: Optional[Any] description: Optional[str] @@ -98,6 +100,28 @@ class Recipe(RecipeSummary): org_url: Optional[str] = Field(None, alias="orgURL") extras: Optional[dict] = {} + @staticmethod + def directory_from_slug(slug) -> Path: + return app_dirs.RECIPE_DATA_DIR.joinpath(slug) + + @property + def directory(self) -> Path: + dir = app_dirs.RECIPE_DATA_DIR.joinpath(self.slug) + dir.mkdir(exist_ok=True, parents=True) + return dir + + @property + def asset_dir(self) -> Path: + dir = self.directory.joinpath("assets") + dir.mkdir(exist_ok=True, parents=True) + return dir + + @property + def image_dir(self) -> Path: + dir = self.directory.joinpath("images") + dir.mkdir(exist_ok=True, parents=True) + return dir + class Config: orm_mode = True @@ -140,6 +164,8 @@ class Recipe(RecipeSummary): @validator("slug", always=True, pre=True) def validate_slug(slug: str, values): + if not values["name"]: + return slug name: str = values["name"] calc_slug: str = slugify(name) diff --git a/mealie/services/backups/exports.py b/mealie/services/backups/exports.py index afd9a735e..321dd9e04 100644 --- a/mealie/services/backups/exports.py +++ b/mealie/services/backups/exports.py @@ -9,6 +9,7 @@ from mealie.core import root_logger from mealie.core.config import app_dirs from mealie.db.database import db from mealie.db.db_setup import create_session +from mealie.services.events import create_backup_event from pathvalidate import sanitize_filename from pydantic.main import BaseModel @@ -32,7 +33,7 @@ class ExportDatabase: export_tag = datetime.now().strftime("%Y-%b-%d") self.main_dir = app_dirs.TEMP_DIR.joinpath(export_tag) - self.img_dir = self.main_dir.joinpath("images") + self.recipes = self.main_dir.joinpath("recipes") self.templates_dir = self.main_dir.joinpath("templates") try: @@ -43,7 +44,7 @@ class ExportDatabase: required_dirs = [ self.main_dir, - self.img_dir, + self.recipes, self.templates_dir, ] @@ -67,10 +68,10 @@ class ExportDatabase: with open(out_file, "w") as f: f.write(content) - def export_images(self): - shutil.copytree(app_dirs.IMG_DIR, self.img_dir, dirs_exist_ok=True) + def export_recipe_dirs(self): + shutil.copytree(app_dirs.RECIPE_DATA_DIR, self.recipes, dirs_exist_ok=True) - def export_items(self, items: list[BaseModel], folder_name: str, export_list=True): + def export_items(self, items: list[BaseModel], folder_name: str, export_list=True, slug_folder=False): items = [x.dict() for x in items] out_dir = self.main_dir.joinpath(folder_name) out_dir.mkdir(parents=True, exist_ok=True) @@ -79,8 +80,10 @@ class ExportDatabase: ExportDatabase._write_json_file(items, out_dir.joinpath(f"{folder_name}.json")) else: for item in items: - filename = sanitize_filename(f"{item.get('name')}.json") - ExportDatabase._write_json_file(item, out_dir.joinpath(filename)) + final_dest = out_dir if not slug_folder else out_dir.joinpath(item.get("slug")) + final_dest.mkdir(exist_ok=True) + filename = sanitize_filename(f"{item.get('slug')}.json") + ExportDatabase._write_json_file(item, final_dest.joinpath(filename)) @staticmethod def _write_json_file(data: Union[dict, list], out_file: Path): @@ -121,9 +124,9 @@ def backup_all( if export_recipes: all_recipes = db.recipes.get_all(session) - db_export.export_items(all_recipes, "recipes", export_list=False) + db_export.export_recipe_dirs() + db_export.export_items(all_recipes, "recipes", export_list=False, slug_folder=True) db_export.export_templates(all_recipes) - db_export.export_images() if export_settings: all_settings = db.settings.get_all(session) @@ -148,3 +151,5 @@ def auto_backup_job(): session = create_session() backup_all(session=session, tag="Auto", templates=templates) logger.info("Auto Backup Called") + create_backup_event("Automated Backup", "Automated backup created", session) + session.close() diff --git a/mealie/services/backups/imports.py b/mealie/services/backups/imports.py index b561752bd..864a21fb4 100644 --- a/mealie/services/backups/imports.py +++ b/mealie/services/backups/imports.py @@ -2,7 +2,7 @@ import json import shutil import zipfile from pathlib import Path -from typing import Callable, List +from typing import Callable from mealie.core.config import app_dirs from mealie.db.database import db @@ -49,7 +49,7 @@ class ImportDatabase: def import_recipes(self): recipe_dir: Path = self.import_dir.joinpath("recipes") imports = [] - successful_imports = [] + successful_imports = {} recipes = ImportDatabase.read_models_file( file_path=recipe_dir, model=Recipe, single_file=False, migrate=ImportDatabase._recipe_migration @@ -68,7 +68,7 @@ class ImportDatabase: ) if import_status.status: - successful_imports.append(recipe.slug) + successful_imports.update({recipe.slug: recipe}) imports.append(import_status) @@ -105,15 +105,25 @@ class ImportDatabase: return recipe_dict - def _import_images(self, successful_imports: List[str]): + def _import_images(self, successful_imports: list[Recipe]): image_dir = self.import_dir.joinpath("images") - for image in image_dir.iterdir(): - if image.stem in successful_imports: - if image.is_dir(): - dest = app_dirs.IMG_DIR.joinpath(image.stem) - shutil.copytree(image, dest, dirs_exist_ok=True) - if image.is_file(): - shutil.copy(image, app_dirs.IMG_DIR) + + if image_dir.exists(): # Migrate from before v0.5.0 + for image in image_dir.iterdir(): + item: Recipe = successful_imports.get(image.stem) + + if item: + dest_dir = item.image_dir + + if image.is_dir(): + shutil.copytree(image, dest_dir, dirs_exist_ok=True) + + if image.is_file(): + shutil.copy(image, dest_dir) + + else: + recipe_dir = self.import_dir.joinpath("recipes") + shutil.copytree(recipe_dir, app_dirs.RECIPE_DATA_DIR, dirs_exist_ok=True) minify.migrate_images() @@ -227,7 +237,7 @@ class ImportDatabase: return [model(**g) for g in file_data] all_models = [] - for file in file_path.glob("*.json"): + for file in file_path.glob("**/*.json"): with open(file, "r") as f: file_data = json.loads(f.read()) diff --git a/mealie/services/events.py b/mealie/services/events.py new file mode 100644 index 000000000..b47edf5dc --- /dev/null +++ b/mealie/services/events.py @@ -0,0 +1,40 @@ +from mealie.db.database import db +from mealie.db.db_setup import create_session +from mealie.schema.events import Event, EventCategory +from sqlalchemy.orm.session import Session + + +def save_event(title, text, category, session: Session): + event = Event(title=title, text=text, category=category) + session = session or create_session() + db.events.create(session, event.dict()) + + +def create_general_event(title, text, session=None): + category = EventCategory.general + save_event(title=title, text=text, category=category, session=session) + + +def create_recipe_event(title, text, session=None): + category = EventCategory.recipe + save_event(title=title, text=text, category=category, session=session) + + +def create_backup_event(title, text, session=None): + category = EventCategory.backup + save_event(title=title, text=text, category=category, session=session) + + +def create_scheduled_event(title, text, session=None): + category = EventCategory.scheduled + save_event(title=title, text=text, category=category, session=session) + + +def create_migration_event(title, text, session=None): + category = EventCategory.migration + save_event(title=title, text=text, category=category, session=session) + + +def create_sign_up_event(title, text, session=None): + category = EventCategory.sign_up + save_event(title=title, text=text, category=category, session=session) diff --git a/mealie/services/image/image.py b/mealie/services/image/image.py index ed5f90ab0..229b2d08d 100644 --- a/mealie/services/image/image.py +++ b/mealie/services/image/image.py @@ -4,7 +4,7 @@ from pathlib import Path import requests from mealie.core import root_logger -from mealie.core.config import app_dirs +from mealie.schema.recipe import Recipe from mealie.services.image import minify logger = root_logger.get_logger() @@ -20,47 +20,11 @@ class ImageOptions: IMG_OPTIONS = ImageOptions() -def read_image(recipe_slug: str, image_type: str = "original") -> Path: - """returns the path to the image file for the recipe base of image_type - - Args: - recipe_slug (str): Recipe Slug - image_type (str, optional): Glob Style Matcher "original*" | "min-original* | "tiny-original*" - - Returns: - Path: [description] - """ - recipe_slug = recipe_slug.split(".")[0] # Incase of File Name - recipe_image_dir = app_dirs.IMG_DIR.joinpath(recipe_slug) - - for file in recipe_image_dir.glob(image_type): - return file - - return None - - -def rename_image(original_slug, new_slug) -> Path: - current_path = app_dirs.IMG_DIR.joinpath(original_slug) - new_path = app_dirs.IMG_DIR.joinpath(new_slug) - - try: - new_path = current_path.rename(new_path) - except FileNotFoundError: - logger.error(f"Image Directory {original_slug} Doesn't Exist") - - return new_path - - def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path: - try: - delete_image(recipe_slug) - except Exception: - pass - - image_dir = Path(app_dirs.IMG_DIR.joinpath(f"{recipe_slug}")) - image_dir.mkdir(exist_ok=True, parents=True) + image_dir = Recipe(slug=recipe_slug).image_dir extension = extension.replace(".", "") image_path = image_dir.joinpath(f"original.{extension}") + image_path.unlink(missing_ok=True) if isinstance(file_data, Path): shutil.copy2(file_data, image_path) @@ -77,12 +41,6 @@ def write_image(recipe_slug: str, file_data: bytes, extension: str) -> Path: return image_path -def delete_image(recipe_slug: str) -> str: - recipe_slug = recipe_slug.split(".")[0] - for file in app_dirs.IMG_DIR.glob(f"{recipe_slug}*"): - return shutil.rmtree(file) - - def scrape_image(image_url: str, slug: str) -> Path: if isinstance(image_url, str): # Handles String Types image_url = image_url @@ -96,7 +54,7 @@ def scrape_image(image_url: str, slug: str) -> Path: image_url = image_url.get("url") filename = slug + "." + image_url.split(".")[-1] - filename = app_dirs.IMG_DIR.joinpath(filename) + filename = Recipe(slug=slug).image_dir.joinpath(filename) try: r = requests.get(image_url, stream=True) diff --git a/mealie/services/image/minify.py b/mealie/services/image/minify.py index e33bbb38e..9dddcb63b 100644 --- a/mealie/services/image/minify.py +++ b/mealie/services/image/minify.py @@ -4,10 +4,8 @@ from pathlib import Path from mealie.core import root_logger from mealie.core.config import app_dirs -from mealie.db.database import db -from mealie.db.db_setup import create_session +from mealie.schema.recipe import Recipe from PIL import Image -from sqlalchemy.orm.session import Session logger = root_logger.get_logger() @@ -20,11 +18,7 @@ class ImageSizes: def get_image_sizes(org_img: Path, min_img: Path, tiny_img: Path) -> ImageSizes: - return ImageSizes( - org=sizeof_fmt(org_img), - min=sizeof_fmt(min_img), - tiny=sizeof_fmt(tiny_img), - ) + return ImageSizes(org=sizeof_fmt(org_img), min=sizeof_fmt(min_img), tiny=sizeof_fmt(tiny_img)) def minify_image(image_file: Path) -> ImageSizes: @@ -110,28 +104,9 @@ def move_all_images(): if new_file.is_file(): new_file.unlink() image_file.rename(new_file) - - -def validate_slugs_in_database(session: Session = None): - def check_image_path(image_name: str, slug_path: str) -> bool: - existing_path: Path = app_dirs.IMG_DIR.joinpath(image_name) - slug_path: Path = app_dirs.IMG_DIR.joinpath(slug_path) - - if existing_path.is_dir(): - slug_path.rename(existing_path) - else: - logger.info("No Image Found") - - session = session or create_session() - all_recipes = db.recipes.get_all(session) - - slugs_and_images = [(x.slug, x.image) for x in all_recipes] - - for slug, image in slugs_and_images: - image_slug = image.split(".")[0] # Remove Extension - if slug != image_slug: - logger.info(f"{slug}, Doesn't Match '{image_slug}'") - check_image_path(image, slug) + if image_file.is_dir(): + slug = image_file.name + image_file.rename(Recipe(slug=slug).image_dir) def migrate_images(): @@ -139,7 +114,7 @@ def migrate_images(): move_all_images() - for image in app_dirs.IMG_DIR.glob("*/original.*"): + for image in app_dirs.RECIPE_DATA_DIR.glob("**/original.*"): minify_image(image) @@ -148,4 +123,3 @@ def migrate_images(): if __name__ == "__main__": migrate_images() - validate_slugs_in_database() diff --git a/mealie/services/recipe/__init__.py b/mealie/services/recipe/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/mealie/services/recipe/media.py b/mealie/services/recipe/media.py new file mode 100644 index 000000000..9be892143 --- /dev/null +++ b/mealie/services/recipe/media.py @@ -0,0 +1,34 @@ +from pathlib import Path +from shutil import copytree, rmtree + +from mealie.core.config import app_dirs +from mealie.core.root_logger import get_logger +from mealie.schema.recipe import Recipe + +logger = get_logger() + + +def check_assets(original_slug, recipe: Recipe) -> None: + if original_slug != recipe.slug: + current_dir = app_dirs.RECIPE_DATA_DIR.joinpath(original_slug) + + try: + copytree(current_dir, recipe.directory, dirs_exist_ok=True) + + except FileNotFoundError: + logger.error(f"Recipe Directory not Found: {original_slug}") + logger.info(f"Renaming Recipe Directory: {original_slug} -> {recipe.slug}") + + all_asset_files = [x.file_name for x in recipe.assets] + for file in recipe.asset_dir.iterdir(): + file: Path + if file.is_dir(): + continue + if file.name not in all_asset_files: + file.unlink() + + +def delete_assets(recipe_slug): + recipe_dir = Recipe(slug=recipe_slug).directory + rmtree(recipe_dir, ignore_errors=True) + logger.info(f"Recipe Directory Removed: {recipe_slug}") diff --git a/mealie/utils/post_webhooks.py b/mealie/utils/post_webhooks.py index 2ccaa28c0..a9884c024 100644 --- a/mealie/utils/post_webhooks.py +++ b/mealie/utils/post_webhooks.py @@ -2,6 +2,7 @@ import requests from mealie.db.database import db from mealie.db.db_setup import create_session from mealie.schema.user import GroupInDB +from mealie.services.events import create_scheduled_event from mealie.services.meal_services import get_todays_meal from sqlalchemy.orm.session import Session @@ -21,4 +22,6 @@ def post_webhooks(group: int, session: Session = None): for url in group_settings.webhook_urls: requests.post(url, json=todays_recipe.json()) + create_scheduled_event("Meal Plan Webhook", f"Meal plan webhook executed for group '{group}'") + session.close() From 4e3d09ac7af1c3cc2b725a567a0fb3942287dae1 Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Mon, 3 May 2021 20:26:13 -0800 Subject: [PATCH 03/10] New Crowdin updates (#386) * New translations en-US.json (Romanian) * New translations en-US.json (Italian) * New translations en-US.json (Romanian) * New translations en-US.json (Spanish) * New translations en-US.json (Afrikaans) * New translations en-US.json (Arabic) * New translations en-US.json (Catalan) * New translations en-US.json (Czech) * New translations en-US.json (Danish) * New translations en-US.json (Greek) * New translations en-US.json (Finnish) * New translations en-US.json (Hebrew) * New translations en-US.json (Hungarian) * New translations en-US.json (Japanese) * New translations en-US.json (French) * New translations en-US.json (Korean) * New translations en-US.json (Dutch) * New translations en-US.json (Norwegian) * New translations en-US.json (Polish) * New translations en-US.json (Portuguese) * New translations en-US.json (Russian) * New translations en-US.json (Serbian (Cyrillic)) * New translations en-US.json (Swedish) * New translations en-US.json (Turkish) * New translations en-US.json (Ukrainian) * New translations en-US.json (Chinese Traditional) * New translations en-US.json (Vietnamese) * New translations en-US.json (Chinese Simplified) * New translations en-US.json (Portuguese, Brazilian) * New translations en-US.json (French) * New translations en-US.json (Italian) * New translations en-US.json (Afrikaans) * New translations en-US.json (Arabic) * New translations en-US.json (Catalan) * New translations en-US.json (Czech) * New translations en-US.json (Danish) * New translations en-US.json (German) * New translations en-US.json (German) * New translations en-US.json (Greek) * New translations en-US.json (Finnish) * New translations en-US.json (Hebrew) * New translations en-US.json (Hungarian) * New translations en-US.json (Japanese) * New translations en-US.json (Vietnamese) * New translations en-US.json (Korean) * New translations en-US.json (Dutch) * New translations en-US.json (Norwegian) * New translations en-US.json (Polish) * New translations en-US.json (Portuguese) * New translations en-US.json (Russian) * New translations en-US.json (Serbian (Cyrillic)) * New translations en-US.json (Swedish) * New translations en-US.json (Turkish) * New translations en-US.json (Ukrainian) * New translations en-US.json (Chinese Simplified) * New translations en-US.json (Chinese Traditional) * New translations en-US.json (Portuguese, Brazilian) --- frontend/src/locales/dateTimeFormats/af-ZA.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/ar-SA.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/ca-ES.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/cs-CZ.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/da-DK.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/de-DE.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/el-GR.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/fi-FI.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/fr-FR.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/he-IL.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/hu-HU.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/it-IT.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/ja-JP.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/ko-KR.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/nl-NL.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/no-NO.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/pl-PL.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/pt-BR.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/pt-PT.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/ro-RO.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/ru-RU.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/sr-SP.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/sv-SE.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/tr-TR.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/uk-UA.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/vi-VN.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/zh-CN.json | 9 +++++++++ frontend/src/locales/dateTimeFormats/zh-TW.json | 9 +++++++++ frontend/src/locales/messages/af-ZA.json | 8 +++++--- frontend/src/locales/messages/ar-SA.json | 8 +++++--- frontend/src/locales/messages/ca-ES.json | 8 +++++--- frontend/src/locales/messages/cs-CZ.json | 8 +++++--- frontend/src/locales/messages/da-DK.json | 8 +++++--- frontend/src/locales/messages/de-DE.json | 8 +++++--- frontend/src/locales/messages/el-GR.json | 8 +++++--- frontend/src/locales/messages/es-ES.json | 8 +++++--- frontend/src/locales/messages/fi-FI.json | 8 +++++--- frontend/src/locales/messages/fr-FR.json | 8 +++++--- frontend/src/locales/messages/he-IL.json | 8 +++++--- frontend/src/locales/messages/hu-HU.json | 8 +++++--- frontend/src/locales/messages/it-IT.json | 8 +++++--- frontend/src/locales/messages/ja-JP.json | 8 +++++--- frontend/src/locales/messages/ko-KR.json | 8 +++++--- frontend/src/locales/messages/nl-NL.json | 8 +++++--- frontend/src/locales/messages/no-NO.json | 8 +++++--- frontend/src/locales/messages/pl-PL.json | 8 +++++--- frontend/src/locales/messages/pt-BR.json | 8 +++++--- frontend/src/locales/messages/pt-PT.json | 8 +++++--- frontend/src/locales/messages/ro-RO.json | 8 +++++--- frontend/src/locales/messages/ru-RU.json | 8 +++++--- frontend/src/locales/messages/sr-SP.json | 8 +++++--- frontend/src/locales/messages/sv-SE.json | 8 +++++--- frontend/src/locales/messages/tr-TR.json | 8 +++++--- frontend/src/locales/messages/uk-UA.json | 8 +++++--- frontend/src/locales/messages/vi-VN.json | 8 +++++--- frontend/src/locales/messages/zh-CN.json | 8 +++++--- frontend/src/locales/messages/zh-TW.json | 8 +++++--- 57 files changed, 397 insertions(+), 87 deletions(-) diff --git a/frontend/src/locales/dateTimeFormats/af-ZA.json b/frontend/src/locales/dateTimeFormats/af-ZA.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/af-ZA.json +++ b/frontend/src/locales/dateTimeFormats/af-ZA.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/ar-SA.json b/frontend/src/locales/dateTimeFormats/ar-SA.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/ar-SA.json +++ b/frontend/src/locales/dateTimeFormats/ar-SA.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/ca-ES.json b/frontend/src/locales/dateTimeFormats/ca-ES.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/ca-ES.json +++ b/frontend/src/locales/dateTimeFormats/ca-ES.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/cs-CZ.json b/frontend/src/locales/dateTimeFormats/cs-CZ.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/cs-CZ.json +++ b/frontend/src/locales/dateTimeFormats/cs-CZ.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/da-DK.json b/frontend/src/locales/dateTimeFormats/da-DK.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/da-DK.json +++ b/frontend/src/locales/dateTimeFormats/da-DK.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/de-DE.json b/frontend/src/locales/dateTimeFormats/de-DE.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/de-DE.json +++ b/frontend/src/locales/dateTimeFormats/de-DE.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/el-GR.json b/frontend/src/locales/dateTimeFormats/el-GR.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/el-GR.json +++ b/frontend/src/locales/dateTimeFormats/el-GR.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/fi-FI.json b/frontend/src/locales/dateTimeFormats/fi-FI.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/fi-FI.json +++ b/frontend/src/locales/dateTimeFormats/fi-FI.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/fr-FR.json b/frontend/src/locales/dateTimeFormats/fr-FR.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/fr-FR.json +++ b/frontend/src/locales/dateTimeFormats/fr-FR.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/he-IL.json b/frontend/src/locales/dateTimeFormats/he-IL.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/he-IL.json +++ b/frontend/src/locales/dateTimeFormats/he-IL.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/hu-HU.json b/frontend/src/locales/dateTimeFormats/hu-HU.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/hu-HU.json +++ b/frontend/src/locales/dateTimeFormats/hu-HU.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/it-IT.json b/frontend/src/locales/dateTimeFormats/it-IT.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/it-IT.json +++ b/frontend/src/locales/dateTimeFormats/it-IT.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/ja-JP.json b/frontend/src/locales/dateTimeFormats/ja-JP.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/ja-JP.json +++ b/frontend/src/locales/dateTimeFormats/ja-JP.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/ko-KR.json b/frontend/src/locales/dateTimeFormats/ko-KR.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/ko-KR.json +++ b/frontend/src/locales/dateTimeFormats/ko-KR.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/nl-NL.json b/frontend/src/locales/dateTimeFormats/nl-NL.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/nl-NL.json +++ b/frontend/src/locales/dateTimeFormats/nl-NL.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/no-NO.json b/frontend/src/locales/dateTimeFormats/no-NO.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/no-NO.json +++ b/frontend/src/locales/dateTimeFormats/no-NO.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/pl-PL.json b/frontend/src/locales/dateTimeFormats/pl-PL.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/pl-PL.json +++ b/frontend/src/locales/dateTimeFormats/pl-PL.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/pt-BR.json b/frontend/src/locales/dateTimeFormats/pt-BR.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/pt-BR.json +++ b/frontend/src/locales/dateTimeFormats/pt-BR.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/pt-PT.json b/frontend/src/locales/dateTimeFormats/pt-PT.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/pt-PT.json +++ b/frontend/src/locales/dateTimeFormats/pt-PT.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/ro-RO.json b/frontend/src/locales/dateTimeFormats/ro-RO.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/ro-RO.json +++ b/frontend/src/locales/dateTimeFormats/ro-RO.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/ru-RU.json b/frontend/src/locales/dateTimeFormats/ru-RU.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/ru-RU.json +++ b/frontend/src/locales/dateTimeFormats/ru-RU.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/sr-SP.json b/frontend/src/locales/dateTimeFormats/sr-SP.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/sr-SP.json +++ b/frontend/src/locales/dateTimeFormats/sr-SP.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/sv-SE.json b/frontend/src/locales/dateTimeFormats/sv-SE.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/sv-SE.json +++ b/frontend/src/locales/dateTimeFormats/sv-SE.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/tr-TR.json b/frontend/src/locales/dateTimeFormats/tr-TR.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/tr-TR.json +++ b/frontend/src/locales/dateTimeFormats/tr-TR.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/uk-UA.json b/frontend/src/locales/dateTimeFormats/uk-UA.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/uk-UA.json +++ b/frontend/src/locales/dateTimeFormats/uk-UA.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/vi-VN.json b/frontend/src/locales/dateTimeFormats/vi-VN.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/vi-VN.json +++ b/frontend/src/locales/dateTimeFormats/vi-VN.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/zh-CN.json b/frontend/src/locales/dateTimeFormats/zh-CN.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/zh-CN.json +++ b/frontend/src/locales/dateTimeFormats/zh-CN.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/dateTimeFormats/zh-TW.json b/frontend/src/locales/dateTimeFormats/zh-TW.json index 485c132d5..9e0240f38 100644 --- a/frontend/src/locales/dateTimeFormats/zh-TW.json +++ b/frontend/src/locales/dateTimeFormats/zh-TW.json @@ -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 } } \ No newline at end of file diff --git a/frontend/src/locales/messages/af-ZA.json b/frontend/src/locales/messages/af-ZA.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/af-ZA.json +++ b/frontend/src/locales/messages/af-ZA.json @@ -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", diff --git a/frontend/src/locales/messages/ar-SA.json b/frontend/src/locales/messages/ar-SA.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/ar-SA.json +++ b/frontend/src/locales/messages/ar-SA.json @@ -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", diff --git a/frontend/src/locales/messages/ca-ES.json b/frontend/src/locales/messages/ca-ES.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/ca-ES.json +++ b/frontend/src/locales/messages/ca-ES.json @@ -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", diff --git a/frontend/src/locales/messages/cs-CZ.json b/frontend/src/locales/messages/cs-CZ.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/cs-CZ.json +++ b/frontend/src/locales/messages/cs-CZ.json @@ -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", diff --git a/frontend/src/locales/messages/da-DK.json b/frontend/src/locales/messages/da-DK.json index b63b87998..399693982 100644 --- a/frontend/src/locales/messages/da-DK.json +++ b/frontend/src/locales/messages/da-DK.json @@ -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", diff --git a/frontend/src/locales/messages/de-DE.json b/frontend/src/locales/messages/de-DE.json index acd1f0370..0712e2bf5 100644 --- a/frontend/src/locales/messages/de-DE.json +++ b/frontend/src/locales/messages/de-DE.json @@ -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", diff --git a/frontend/src/locales/messages/el-GR.json b/frontend/src/locales/messages/el-GR.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/el-GR.json +++ b/frontend/src/locales/messages/el-GR.json @@ -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", diff --git a/frontend/src/locales/messages/es-ES.json b/frontend/src/locales/messages/es-ES.json index 1fd0f815c..2d19e72ad 100644 --- a/frontend/src/locales/messages/es-ES.json +++ b/frontend/src/locales/messages/es-ES.json @@ -14,10 +14,10 @@ "demo-status": "Estado Demo", "development": "Desarrollo", "download-log": "Descargar Log", - "download-recipe-json": "Descargar Receta JSON", + "download-recipe-json": "Last Scraped JSON", "not-demo": "No Demo", "production": "Producción", - "sqlite-file": "Archivo SQLite", + "database-url": "Database URL", "version": "Versión" }, "category": { @@ -27,7 +27,8 @@ "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-updated": "Categoría actualizada", + "category": "Category" }, "general": { "apply": "Aplicar", @@ -36,6 +37,7 @@ "confirm": "Confirmar", "create": "Crear", "current-parenthesis": "(Actual)", + "dashboard": "Dashboard", "delete": "Eliminar", "disabled": "Deshabilitado", "download": "Descargar", diff --git a/frontend/src/locales/messages/fi-FI.json b/frontend/src/locales/messages/fi-FI.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/fi-FI.json +++ b/frontend/src/locales/messages/fi-FI.json @@ -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", diff --git a/frontend/src/locales/messages/fr-FR.json b/frontend/src/locales/messages/fr-FR.json index 17f27227a..0a617f196 100644 --- a/frontend/src/locales/messages/fr-FR.json +++ b/frontend/src/locales/messages/fr-FR.json @@ -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", diff --git a/frontend/src/locales/messages/he-IL.json b/frontend/src/locales/messages/he-IL.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/he-IL.json +++ b/frontend/src/locales/messages/he-IL.json @@ -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", diff --git a/frontend/src/locales/messages/hu-HU.json b/frontend/src/locales/messages/hu-HU.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/hu-HU.json +++ b/frontend/src/locales/messages/hu-HU.json @@ -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", diff --git a/frontend/src/locales/messages/it-IT.json b/frontend/src/locales/messages/it-IT.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/it-IT.json +++ b/frontend/src/locales/messages/it-IT.json @@ -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", diff --git a/frontend/src/locales/messages/ja-JP.json b/frontend/src/locales/messages/ja-JP.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/ja-JP.json +++ b/frontend/src/locales/messages/ja-JP.json @@ -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", diff --git a/frontend/src/locales/messages/ko-KR.json b/frontend/src/locales/messages/ko-KR.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/ko-KR.json +++ b/frontend/src/locales/messages/ko-KR.json @@ -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", diff --git a/frontend/src/locales/messages/nl-NL.json b/frontend/src/locales/messages/nl-NL.json index 18e258ace..f19426757 100644 --- a/frontend/src/locales/messages/nl-NL.json +++ b/frontend/src/locales/messages/nl-NL.json @@ -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", diff --git a/frontend/src/locales/messages/no-NO.json b/frontend/src/locales/messages/no-NO.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/no-NO.json +++ b/frontend/src/locales/messages/no-NO.json @@ -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", diff --git a/frontend/src/locales/messages/pl-PL.json b/frontend/src/locales/messages/pl-PL.json index 23fc17fb9..73479c31f 100644 --- a/frontend/src/locales/messages/pl-PL.json +++ b/frontend/src/locales/messages/pl-PL.json @@ -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", diff --git a/frontend/src/locales/messages/pt-BR.json b/frontend/src/locales/messages/pt-BR.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/pt-BR.json +++ b/frontend/src/locales/messages/pt-BR.json @@ -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", diff --git a/frontend/src/locales/messages/pt-PT.json b/frontend/src/locales/messages/pt-PT.json index 37a42cd90..68f5be09d 100644 --- a/frontend/src/locales/messages/pt-PT.json +++ b/frontend/src/locales/messages/pt-PT.json @@ -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", diff --git a/frontend/src/locales/messages/ro-RO.json b/frontend/src/locales/messages/ro-RO.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/ro-RO.json +++ b/frontend/src/locales/messages/ro-RO.json @@ -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", diff --git a/frontend/src/locales/messages/ru-RU.json b/frontend/src/locales/messages/ru-RU.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/ru-RU.json +++ b/frontend/src/locales/messages/ru-RU.json @@ -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", diff --git a/frontend/src/locales/messages/sr-SP.json b/frontend/src/locales/messages/sr-SP.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/sr-SP.json +++ b/frontend/src/locales/messages/sr-SP.json @@ -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", diff --git a/frontend/src/locales/messages/sv-SE.json b/frontend/src/locales/messages/sv-SE.json index 54534e5aa..7c5bd7aa4 100644 --- a/frontend/src/locales/messages/sv-SE.json +++ b/frontend/src/locales/messages/sv-SE.json @@ -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", diff --git a/frontend/src/locales/messages/tr-TR.json b/frontend/src/locales/messages/tr-TR.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/tr-TR.json +++ b/frontend/src/locales/messages/tr-TR.json @@ -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", diff --git a/frontend/src/locales/messages/uk-UA.json b/frontend/src/locales/messages/uk-UA.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/uk-UA.json +++ b/frontend/src/locales/messages/uk-UA.json @@ -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", diff --git a/frontend/src/locales/messages/vi-VN.json b/frontend/src/locales/messages/vi-VN.json index 0ca827e7f..5aab6900f 100644 --- a/frontend/src/locales/messages/vi-VN.json +++ b/frontend/src/locales/messages/vi-VN.json @@ -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", diff --git a/frontend/src/locales/messages/zh-CN.json b/frontend/src/locales/messages/zh-CN.json index 827cc157a..863e86463 100644 --- a/frontend/src/locales/messages/zh-CN.json +++ b/frontend/src/locales/messages/zh-CN.json @@ -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": "下载", diff --git a/frontend/src/locales/messages/zh-TW.json b/frontend/src/locales/messages/zh-TW.json index facd4c1e8..6553dc6b2 100644 --- a/frontend/src/locales/messages/zh-TW.json +++ b/frontend/src/locales/messages/zh-TW.json @@ -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": "下载", From 117c1d20ea0b2b9929404cd1b9beb6d58dc36f82 Mon Sep 17 00:00:00 2001 From: hay-kot Date: Mon, 3 May 2021 20:40:39 -0800 Subject: [PATCH 04/10] dashboard notes --- docs/docs/changelog/v0.5.0.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/docs/changelog/v0.5.0.md b/docs/docs/changelog/v0.5.0.md index 369df1124..72b8dec26 100644 --- a/docs/docs/changelog/v0.5.0.md +++ b/docs/docs/changelog/v0.5.0.md @@ -33,6 +33,11 @@ - Download (As Json) - Copy Link - Rating can be updated without entering the editor - Closes #25 +- New Admin Dashboard! 🎉 + - Now you can get some insight on your application with application statics and events. + - See uncategorized/untagged recipes and organize them! + - Backup/Restore right from your dashboard + - See server side events. Now you can know who deleted your favorite recipe! ### Performance - Images are now served up by the Caddy increase performance and offloading some loads from the API server From e13d203524e8ba4a441e784359858f8f3b858889 Mon Sep 17 00:00:00 2001 From: hay-kot Date: Mon, 3 May 2021 20:52:33 -0800 Subject: [PATCH 05/10] fix image display --- frontend/src/api/recipe.js | 6 +++--- mealie/routes/recipe/recipe_media.py | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/frontend/src/api/recipe.js b/frontend/src/api/recipe.js index d35c4d1bc..4410b23a1 100644 --- a/frontend/src/api/recipe.js +++ b/frontend/src/api/recipe.js @@ -135,14 +135,14 @@ export const recipeAPI = { }, recipeImage(recipeSlug) { - return `/api/recipes/media/${recipeSlug}/image/original.webp`; + return `/api/recipes/media/${recipeSlug}/images/original.webp`; }, recipeSmallImage(recipeSlug) { - return `/api/recipes/media/${recipeSlug}/image/min-original.webp`; + return `/api/recipes/media/${recipeSlug}/images/min-original.webp`; }, recipeTinyImage(recipeSlug) { - return `/api/recipes/media/${recipeSlug}/image/tiny-original.webp`; + return `/api/recipes/media/${recipeSlug}/images/tiny-original.webp`; }, }; diff --git a/mealie/routes/recipe/recipe_media.py b/mealie/routes/recipe/recipe_media.py index b89605c72..e7c3402a5 100644 --- a/mealie/routes/recipe/recipe_media.py +++ b/mealie/routes/recipe/recipe_media.py @@ -20,7 +20,7 @@ class ImageType(str, Enum): tiny = "tiny-original.webp" -@router.get("/{recipe_slug}/image/{file_name}") +@router.get("/{recipe_slug}/images/{file_name}") async def get_recipe_img(recipe_slug: str, file_name: ImageType = ImageType.original): """Takes in a recipe slug, returns the static image. This route is proxied in the docker image and should not hit the API in production""" From be5ac7a17a0e4b7f4031b30ff588be98de26a5fa Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Tue, 4 May 2021 07:09:21 -0800 Subject: [PATCH 06/10] New Crowdin updates (#387) * New translations en-US.json (Italian) * New translations en-US.json (Italian) * New translations en-US.json (Italian) --- frontend/src/locales/messages/it-IT.json | 648 +++++++++++------------ 1 file changed, 324 insertions(+), 324 deletions(-) diff --git a/frontend/src/locales/messages/it-IT.json b/frontend/src/locales/messages/it-IT.json index 5aab6900f..38748a32c 100644 --- a/frontend/src/locales/messages/it-IT.json +++ b/frontend/src/locales/messages/it-IT.json @@ -1,391 +1,391 @@ { "404": { - "page-not-found": "404 Page Not Found", - "take-me-home": "Take me Home" + "page-not-found": "404 Pagina Non Trovata", + "take-me-home": "Vai alla Home" }, "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", + "about-mealie": "Info su Mealie", + "api-docs": "Documentazione API", + "api-port": "Porta API", + "application-mode": "Modalità", + "database-type": "Tipo Database", + "default-group": "Gruppo Predefinito", "demo": "Demo", - "demo-status": "Demo Status", - "development": "Development", - "download-log": "Download Log", - "download-recipe-json": "Last Scraped JSON", - "not-demo": "Not Demo", - "production": "Production", - "database-url": "Database URL", - "version": "Version" + "demo-status": "Stato Demo", + "development": "In Sviluppo", + "download-log": "Scarica Log", + "download-recipe-json": "Ultimo JSON", + "not-demo": "Non Demo", + "production": "Produzione", + "database-url": "URL Database", + "version": "Versione" }, "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": "Category" + "category-created": "Categoria creata", + "category-creation-failed": "Creazione categoria fallita", + "category-deleted": "Categoria Eliminata", + "category-deletion-failed": "Eliminazione categoria fallita", + "category-filter": "Filtro Categoria", + "category-update-failed": "Aggiornamento categoria fallito", + "category-updated": "Categoria aggiornata", + "category": "Categoria" }, "general": { - "apply": "Apply", - "cancel": "Cancel", - "close": "Close", - "confirm": "Confirm", - "create": "Create", - "current-parenthesis": "(Current)", - "dashboard": "Dashboard", - "delete": "Delete", - "disabled": "Disabled", + "apply": "Applica", + "cancel": "Cancella", + "close": "Chiudi", + "confirm": "Conferma", + "create": "Crea", + "current-parenthesis": "(Corrente)", + "dashboard": "Pannello di controllo", + "delete": "Elimina", + "disabled": "Disabilitato", "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", + "edit": "Modifica", + "enabled": "Abilitato", + "exception": "Eccezione", + "failed-count": "Fallito: {count}", + "failure-uploading-file": "Caricamento file fallito", + "field-required": "Campo obbligatorio", + "file-folder-not-found": "Cartella/File non trovato", + "file-uploaded": "File caricato", + "filter": "Filtro", + "friday": "Venerdì", + "get": "Ottieni", + "groups": "Gruppi", + "image": "Immagine", + "image-upload-failed": "Caricamento immagine fallito", + "import": "Importa", + "keyword": "Parola chiave", "link": "Link", - "monday": "Monday", - "name": "Name", + "monday": "Lunedì", + "name": "Nome", "no": "No", "ok": "OK", - "options": "Options:", - "random": "Random", - "recent": "Recent", - "recipes": "Recipes", - "rename-object": "Rename {0}", - "reset": "Reset", - "saturday": "Saturday", - "save": "Save", - "settings": "Settings", - "sort": "Sort", + "options": "Opzioni:", + "random": "Casuale", + "recent": "Recente", + "recipes": "Ricette", + "rename-object": "Rinomina {0}", + "reset": "Reimposta", + "saturday": "Sabato", + "save": "Salva", + "settings": "Impostazioni", + "sort": "Ordina", "sort-alphabetically": "A-Z", - "status": "Status", - "submit": "Submit", - "success-count": "Success: {count}", - "sunday": "Sunday", - "templates": "Templates:", - "themes": "Themes", - "thursday": "Thursday", + "status": "Stato", + "submit": "Invia", + "success-count": "Successo: {count}", + "sunday": "Domenica", + "templates": "Modelli:", + "themes": "Temi", + "thursday": "Giovedì", "token": "Token", - "tuesday": "Tuesday", - "update": "Update", - "upload": "Upload", + "tuesday": "Martedì", + "update": "Aggiorna", + "upload": "Carica", "url": "URL", - "users": "Users", - "wednesday": "Wednesday", - "yes": "Yes" + "users": "Utenti", + "wednesday": "Mercoledì", + "yes": "Sì" }, "group": { - "are-you-sure-you-want-to-delete-the-group": "Are you sure you want to delete {groupName}?", - "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", - "user-group": "User Group", - "user-group-created": "User Group Created", - "user-group-creation-failed": "User Group Creation Failed" + "are-you-sure-you-want-to-delete-the-group": "Sei sicuro di volerlo eliminare {groupName}'?", + "cannot-delete-default-group": "Impossibile eliminare il gruppo predefinito", + "cannot-delete-group-with-users": "Impossibile eliminare il gruppo con utenti", + "confirm-group-deletion": "Conferma Eliminazione Gruppo", + "create-group": "Crea Gruppo", + "error-updating-group": "Errore aggiornamento gruppo", + "group": "Gruppo", + "group-deleted": "Gruppo eliminato", + "group-deletion-failed": "Eliminazione gruppo fallita", + "group-id-with-value": "ID Gruppo:{groupID}", + "group-name": "Nome Gruppo", + "group-not-found": "Gruppo non trovato", + "groups": "Gruppi", + "groups-can-only-be-set-by-administrators": "I gruppi possono essere impostati solo dagli amministratori", + "user-group": "Gruppo Utente", + "user-group-created": "Gruppo Utente Creato", + "user-group-creation-failed": "Creazione Gruppo Utente Fallita" }, "meal-plan": { - "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", - "mealplan-deleted": "Mealplan Deleted", - "mealplan-deletion-failed": "Mealplan deletion failed", - "mealplan-update-failed": "Mealplan update failed", - "mealplan-updated": "Mealplan Updated", - "no-meal-plan-defined-yet": "No meal plan defined yet", - "no-meal-planned-for-today": "No meal planned for today", - "only-recipes-with-these-categories-will-be-used-in-meal-plans": "Only recipes with these categories will be used in Meal Plans", - "planner": "Planner", - "quick-week": "Quick Week", - "shopping-list": "Shopping List", - "start-date": "Start Date" + "create-a-new-meal-plan": "Crea un Nuovo Piano Alimentare", + "dinner-this-week": "Cena Questa Settimana", + "dinner-today": "Cena Oggi", + "edit-meal-plan": "Modifica Piano Alimentare", + "end-date": "Data Fine", + "group": "Gruppo (Beta)", + "meal-planner": "Piano Alimentare", + "meal-plans": "Piani Alimentari", + "mealplan-created": "Piano allimentare creato", + "mealplan-creation-failed": "Creazione piano alimentare fallita", + "mealplan-deleted": "Piano Alimentare Eliminato", + "mealplan-deletion-failed": "Eliminazione piano alimentare fallita", + "mealplan-update-failed": "Aggiornamento piano alimentare fallito", + "mealplan-updated": "Piano Alimentare Aggiornato", + "no-meal-plan-defined-yet": "Ancora nessun piano alimentare definito", + "no-meal-planned-for-today": "Nessun piano alimentare per oggi", + "only-recipes-with-these-categories-will-be-used-in-meal-plans": "Solo ricette con queste categorie possono essere utilizzate un un Piano Alimentare", + "planner": "Pianificatore", + "quick-week": "Settimana Veloce", + "shopping-list": "Lista della Spesa", + "start-date": "Data Inizio" }, "migration": { "chowdown": { - "description": "Migrate data from Chowdown", + "description": "Migra dati da Chowdown", "title": "Chowdown" }, - "migration-data-removed": "Migration data removed", + "migration-data-removed": "Dati di migrazione rimossi", "nextcloud": { - "description": "Migrate data from a Nextcloud Cookbook intance", + "description": "Migra i dati da Nextcloud Cookbook", "title": "Nextcloud Cookbook" }, - "no-migration-data-available": "No Migration Data Avaiable", - "recipe-migration": "Recipe Migration" + "no-migration-data-available": "Dati Di Migrazione Non Disponibili", + "recipe-migration": "Migrazione Ricetta" }, "new-recipe": { - "bulk-add": "Bulk Add", - "error-message": "Looks like there was an error parsing the URL. Check the log and debug/last_recipe.json to see what went wrong.", - "from-url": "Import a Recipe", - "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", - "recipe-url": "Recipe URL", - "url-form-hint": "Copy and paste a link from your favorite recipe website" + "bulk-add": "Aggiungi In Massa", + "error-message": "Sembra che ci sia stato un errore nell'analisi dell'URL. Controlla il log e debug/last_recipe.json per vedere cosa è andato storto.", + "from-url": "Importa ricetta", + "paste-in-your-recipe-data-each-line-will-be-treated-as-an-item-in-a-list": "Incolla nei dati delle tue ricette. Ogni riga sarà trattata come un elemento in una lista", + "recipe-url": "URL Ricetta", + "url-form-hint": "Copia e incolla un link dal tuo sito di ricette preferito" }, "page": { - "all-recipes": "All Recipes", + "all-recipes": "Tutte le ricette", "home-page": "Home Page", - "new-page-created": "New page created", - "page-creation-failed": "Page creation failed", - "page-deleted": "Page deleted", - "page-deletion-failed": "Page deletion failed", - "page-update-failed": "Page update failed", - "page-updated": "Page updated", - "pages-update-failed": "Pages update failed", - "pages-updated": "Pages updated", - "recent": "Recent" + "new-page-created": "Nuova pagina creata", + "page-creation-failed": "Creazione pagina fallita", + "page-deleted": "Pagina eliminata", + "page-deletion-failed": "Eliminazione pagina fallita", + "page-update-failed": "Aggiornamento pagina fallito", + "page-updated": "Pagina aggiornata", + "pages-update-failed": "Aggiornamento pagine fallito", + "pages-updated": "Pagine aggiornate", + "recent": "Recente" }, "recipe": { - "add-key": "Add Key", + "add-key": "Aggiungi Chiave", "api-extras": "API Extras", - "assets": "Assets", - "calories": "Calories", - "calories-suffix": "calories", - "carbohydrate-content": "Carbohydrate", - "categories": "Categories", - "delete-confirmation": "Are you sure you want to delete this recipe?", - "delete-recipe": "Delete Recipe", - "description": "Description", - "fat-content": "Fat", - "fiber-content": "Fiber", - "grams": "grams", - "ingredient": "Ingredient", - "ingredients": "Ingredients", - "instructions": "Instructions", - "key-name-required": "Key Name Required", - "landscape-view-coming-soon": "Landscape View (Coming Soon)", - "milligrams": "milligrams", - "new-asset": "New Asset", - "new-key-name": "New Key Name", - "no-white-space-allowed": "No White Space Allowed", - "note": "Note", - "notes": "Notes", - "nutrition": "Nutrition", - "object-key": "Object Key", - "object-value": "Object Value", - "original-url": "Original URL", - "perform-time": "Cook Time", - "prep-time": "Prep Time", - "protein-content": "Protein", - "public-recipe": "Public Recipe", - "recipe-created": "Recipe created", - "recipe-creation-failed": "Recipe creation failed", - "recipe-deleted": "Recipe deleted", - "recipe-image": "Recipe Image", - "recipe-image-updated": "Recipe image updated", - "recipe-name": "Recipe Name", - "recipe-settings": "Recipe Settings", - "recipe-update-failed": "Recipe update failed", - "recipe-updated": "Recipe updated", - "servings": "Servings", - "show-assets": "Show Assets", - "show-nutrition-values": "Show Nutrition Values", - "sodium-content": "Sodium", - "step-index": "Step: {step}", - "sugar-content": "Sugar", - "title": "Title", - "total-time": "Total Time", - "unable-to-delete-recipe": "Unable to Delete Recipe", - "view-recipe": "View Recipe" + "assets": "Risorse", + "calories": "Calorie", + "calories-suffix": "calorie", + "carbohydrate-content": "Carboidrati", + "categories": "Categorie", + "delete-confirmation": "Sei sicuro di voler eliminare questa ricetta?", + "delete-recipe": "Elimina Ricetta", + "description": "Descrizione", + "fat-content": "Grassi", + "fiber-content": "Fibre", + "grams": "grammi", + "ingredient": "Ingrediente", + "ingredients": "Ingredienti", + "instructions": "Istruzioni", + "key-name-required": "Nome Chiave Richiesto", + "landscape-view-coming-soon": "Vista Orizzontale (Prossimamente)", + "milligrams": "milligrammi", + "new-asset": "Nuova Risorsa", + "new-key-name": "Nuovo Nome Chiave", + "no-white-space-allowed": "Nessun Spazio Consentito", + "note": "Nota", + "notes": "Note", + "nutrition": "Nutrienti", + "object-key": "Chiave Oggetto", + "object-value": "Valore Oggetto", + "original-url": "URL Originale", + "perform-time": "Tempo Cottura", + "prep-time": "Tempo Preparazione", + "protein-content": "Proteine", + "public-recipe": "Ricetta Pubblica", + "recipe-created": "Ricetta creata", + "recipe-creation-failed": "Creatione ricetta fallita", + "recipe-deleted": "Ricetta eliminata", + "recipe-image": "Immagine Ricetta", + "recipe-image-updated": "Immagine ricetta aggiornata", + "recipe-name": "Nome Ricetta", + "recipe-settings": "Impostazioni Ricetta", + "recipe-update-failed": "Aggiornamento ricetta fallito", + "recipe-updated": "Ricetta aggiornata", + "servings": "Portate", + "show-assets": "Mostra Risorse", + "show-nutrition-values": "Mostra Valori Nutrizionali", + "sodium-content": "Sodio", + "step-index": "Passo: {step}", + "sugar-content": "Zuccheri", + "title": "Titolo", + "total-time": "Tempo Totale", + "unable-to-delete-recipe": "Impossibile eliminare ricetta", + "view-recipe": "Vedi Ricetta" }, "search": { - "and": "and", - "exclude": "Exclude", - "include": "Include", - "max-results": "Max Results", - "or": "Or", - "search": "Search", - "search-mealie": "Search Mealie (press /)", - "search-placeholder": "Search...", - "tag-filter": "Tag Filter" + "and": "e", + "exclude": "Escludi", + "include": "Includi", + "max-results": "Risultati Massimi", + "or": "O", + "search": "Cerca", + "search-mealie": "Cerca Mealie (premi /)", + "search-placeholder": "Cerca...", + "tag-filter": "Filtro Tag" }, "settings": { - "add-a-new-theme": "Add a New Theme", - "admin-settings": "Admin Settings", - "available-backups": "Available Backups", + "add-a-new-theme": "Aggiungi un Nuovo Tema", + "admin-settings": "Impostazioni Amministratore", + "available-backups": "Backups Disponibili", "backup": { - "backup-created-at-response-export_path": "Backup Created at {path}", - "backup-deleted": "Backup deleted", - "backup-tag": "Backup Tag", - "create-heading": "Create a Backup", - "error-creating-backup-see-log-file": "Error Creating Backup. See Log File", - "full-backup": "Full Backup", - "import-summary": "Import Summary", - "partial-backup": "Partial Backup", - "unable-to-delete-backup": "Unable to Delete Backup." + "backup-created-at-response-export_path": "Backup Creato in {path}", + "backup-deleted": "Backup eliminato", + "backup-tag": "Tag Backup", + "create-heading": "Crea un Backup", + "error-creating-backup-see-log-file": "Errore nella creazione del Backup. Vedi il file di log", + "full-backup": "Backup Completo", + "import-summary": "Importa Riepilogo", + "partial-backup": "Backup Parziale", + "unable-to-delete-backup": "Impossibile rimuovere backup." }, "backup-and-exports": "Backups", - "backup-info": "Backups are exported in standard JSON format along with all the images stored on the file system. In your backup folder you'll find a .zip file that contains all of the recipe JSON and images from the database. Additionally, if you selected a markdown file, those will also be stored in the .zip file. To import a backup, it must be located in your backups folder. Automated backups are done each day at 3:00 AM.", - "change-password": "Change Password", - "current": "Version:", - "custom-pages": "Custom Pages", - "edit-page": "Edit Page", - "first-day-of-week": "First day of the week", - "group-settings-updated": "Group Settings Updated", + "backup-info": "I backup sono esportati in formato standard JSON insieme a tutte le immagine salvate nei file di sistema. Nella tua cartella di backup troverai un file .zip contenente tutto le ricette JSON e immagini del database. In aggiunta, se hai selezionato un file markdown, anche questo verrà salvato nel file .zip. Per importare il backup, deve essere salvato nella cartella di backup. Backup automatici vengono eseguiti ogni giorno alle 03:00.", + "change-password": "Modifica Password", + "current": "Versione:", + "custom-pages": "Pagine Personalizzate", + "edit-page": "Modifica Pagina", + "first-day-of-week": "Primo giorno della settimana", + "group-settings-updated": "Impostazioni Gruppo Aggiornate", "homepage": { - "all-categories": "All Categories", - "card-per-section": "Card Per Section", + "all-categories": "Tutte le Categorie", + "card-per-section": "Scheda Per Sezione", "home-page": "Home Page", - "home-page-sections": "Home Page Sections", - "show-recent": "Show Recent" + "home-page-sections": "Sezioni Home Page", + "show-recent": "Mostra Recenti" }, - "language": "Language", - "latest": "Latest", - "local-api": "Local API", - "locale-settings": "Locale settings", - "manage-users": "Manage Users", - "migrations": "Migrations", - "new-page": "New Page", - "page-name": "Page Name", - "pages": "Pages", - "profile": "Profile", - "remove-existing-entries-matching-imported-entries": "Remove existing entries matching imported entries", - "set-new-time": "Set New Time", - "settings-update-failed": "Settings update failed", - "settings-updated": "Settings updated", - "site-settings": "Site Settings", + "language": "Lingua", + "latest": "Recenti", + "local-api": "API Locale", + "locale-settings": "Impostazioni regionali", + "manage-users": "Gestisci Utenti", + "migrations": "Migrazioni", + "new-page": "Nuova Pagina", + "page-name": "Nome Pagina", + "pages": "Pagine", + "profile": "Profilo", + "remove-existing-entries-matching-imported-entries": "Rimuovi le voci esistenti corrispondenti alle voci importate", + "set-new-time": "Imposta Nuova Ora", + "settings-update-failed": "Aggiornamento impostazioni fallito", + "settings-updated": "Impostazioni aggiornate", + "site-settings": "Impostazioni Sito", "theme": { - "accent": "Accent", - "are-you-sure-you-want-to-delete-this-theme": "Are you sure you want to delete this theme?", - "choose-how-mealie-looks-to-you-set-your-theme-preference-to-follow-your-system-settings-or-choose-to-use-the-light-or-dark-theme": "Choose how Mealie looks to you. Set your theme preference to follow your system settings, or choose to use the light or dark theme.", - "dark": "Dark", - "dark-mode": "Dark Mode", - "default-to-system": "Default to system", - "delete-theme": "Delete Theme", - "error": "Error", - "error-creating-theme-see-log-file": "Error creating theme. See log file.", - "error-deleting-theme": "Error deleting theme", - "error-updating-theme": "Error updating theme", + "accent": "Accento", + "are-you-sure-you-want-to-delete-this-theme": "Sei sicuro di voler eliminare questo tema?", + "choose-how-mealie-looks-to-you-set-your-theme-preference-to-follow-your-system-settings-or-choose-to-use-the-light-or-dark-theme": "Scegli l'aspetto die Mealie. Seleziona la di seguire le impostazioni di sistema, oppure segli tema scuro o chiaro.", + "dark": "Scuro", + "dark-mode": "Modalità Scura", + "default-to-system": "Predefinito di sistema", + "delete-theme": "Elimina Tema", + "error": "Errore", + "error-creating-theme-see-log-file": "Errore creazione tema. Guarda i log.", + "error-deleting-theme": "Errore eliminazione tema", + "error-updating-theme": "Errore aggiornamento tema", "info": "Info", - "light": "Light", - "primary": "Primary", - "secondary": "Secondary", - "select-a-theme-from-the-dropdown-or-create-a-new-theme-note-that-the-default-theme-will-be-served-to-all-users-who-have-not-set-a-theme-preference": "Select a theme from the dropdown or create a new theme. Note that the default theme will be served to all users who have not set a theme preference.", - "success": "Success", - "theme": "Theme", - "theme-deleted": "Theme deleted", - "theme-name": "Theme Name", - "theme-name-is-required": "Theme Name is required.", - "theme-saved": "Theme Saved", - "theme-settings": "Theme Settings", - "theme-updated": "Theme updated", - "warning": "Warning" + "light": "Chiaro", + "primary": "Primario", + "secondary": "Secondario", + "select-a-theme-from-the-dropdown-or-create-a-new-theme-note-that-the-default-theme-will-be-served-to-all-users-who-have-not-set-a-theme-preference": "Seleziona un tema dalla tendina oppure creane uno nuovo. Nota che il tema predefinito verrà impostato a tutti gli utenti che non hanno selezionato un tema.", + "success": "Successo", + "theme": "Tema", + "theme-deleted": "Tema eliminato", + "theme-name": "Nome Tema", + "theme-name-is-required": "Nome Tema obbligatorio.", + "theme-saved": "Tema Salvato", + "theme-settings": "Impostazioni Tema", + "theme-updated": "Tema aggiornato", + "warning": "Avviso" }, "toolbox": { - "assign-all": "Assign All", - "bulk-assign": "Bulk Assign", - "new-name": "New Name", - "no-unused-items": "No Unused Items", - "recipes-affected": "No Recipes Affected|One Recipe Affected|{count} Recipes Affected", - "remove-unused": "Remove Unused", - "title-case-all": "Title Case All", - "toolbox": "Toolbox" + "assign-all": "Assegna Tutto", + "bulk-assign": "Assegna Bulk", + "new-name": "Nuovo nome", + "no-unused-items": "Nessun Elemento Inutilizzato", + "recipes-affected": "Nessuna Ricetta Interessata|Una Ricetta Interessata|{count} Ricette Interessate", + "remove-unused": "Rimuovi Inutilizzate", + "title-case-all": "Maiuscole Ovunque", + "toolbox": "Strumenti" }, "webhooks": { - "meal-planner-webhooks": "Meal Planner Webhooks", - "test-webhooks": "Test 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": "The URLs listed below will receive webhooks containing the recipe data for the meal plan on it's scheduled day. Currently Webhooks will execute at", - "webhook-url": "Webhook URL" + "meal-planner-webhooks": "Webhooks Pianificatore Alimentare", + "test-webhooks": "Testa 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": "Gli URL elencati sotto riceveranno dei webhooks contenenti i dati delle ricette per il piano alimentare nel giorno programmato. I Webhooks correnti verrano eseguiti alle", + "webhook-url": "URL Webhook" } }, "signup": { - "display-name": "Display Name", - "error-signing-up": "Error Signing Up", - "sign-up": "Sign Up", - "sign-up-link-created": "Sign up link created", - "sign-up-link-creation-failed": "Sign up link creation failed", - "sign-up-links": "Sign Up Links", - "sign-up-token-deleted": "Sign Up Token Deleted", - "sign-up-token-deletion-failed": "Sign up token deletion failed", - "welcome-to-mealie": "Welcome to Mealie! To become a user of this instance you are required to have a valid invitation link. If you haven't recieved an invitation you are unable to sign-up. To recieve a link, contact the sites administrator." + "display-name": "Mostra Nome", + "error-signing-up": "Errore Registrazione", + "sign-up": "Registrati", + "sign-up-link-created": "Link di registrazione creato", + "sign-up-link-creation-failed": "Creazione del link di registrazione fallita", + "sign-up-links": "Link Registrazione", + "sign-up-token-deleted": "Token Registrazione Eliminato", + "sign-up-token-deletion-failed": "Eliminazione token registrazione fallita", + "welcome-to-mealie": "Benvenuto a Mealie! Per diventare un utente hai bisogno di un link di invito alla registazione. Se non hai ricevuto il link non puoi registrarti. Per ricevere il link contatta l'amministratore." }, "tag": { - "tag-created": "Tag created", - "tag-creation-failed": "Tag creation failed", - "tag-deleted": "Tag deleted", - "tag-deletion-failed": "Tag deletion failed", - "tag-update-failed": "Tag update failed", - "tag-updated": "Tag updated", + "tag-created": "Tag creato", + "tag-creation-failed": "Creazione tag fallita", + "tag-deleted": "Tag eliminato", + "tag-deletion-failed": "Eliminazione tag fallita", + "tag-update-failed": "Aggiornamento tag fallito", + "tag-updated": "Tag aggiornato", "tags": "Tags" }, "user": { - "admin": "Admin", - "are-you-sure-you-want-to-delete-the-link": "Are you sure you want to delete the link {link}?", - "are-you-sure-you-want-to-delete-the-user": "Are you sure you want to delete the user {activeName} ID: {activeId}?", - "confirm-link-deletion": "Confirm Link Deletion", - "confirm-password": "Confirm Password", - "confirm-user-deletion": "Confirm User Deletion", - "could-not-validate-credentials": "Could Not Validate Credentials", - "create-link": "Create Link", - "create-user": "Create User", - "current-password": "Current Password", - "e-mail-must-be-valid": "E-mail must be valid", - "edit-user": "Edit User", + "admin": "Amministratore", + "are-you-sure-you-want-to-delete-the-link": "Sei sicuro di voler eliminare il link {link}?", + "are-you-sure-you-want-to-delete-the-user": "Sei dicuro di voler eliminare l'utente {activeName} ID: {activeId}?", + "confirm-link-deletion": "Conferma Eliminazione Link", + "confirm-password": "Conferma Password", + "confirm-user-deletion": "Conferma Eliminazione Utente", + "could-not-validate-credentials": "Credenziale Non Valide", + "create-link": "Crea Link", + "create-user": "Crea Utente", + "current-password": "Password Corrente", + "e-mail-must-be-valid": "E-mail deve essere valida", + "edit-user": "Modifica Utente", "email": "Email", - "error-cannot-delete-super-user": "Error! Cannot Delete Super User", - "existing-password-does-not-match": "Existing password does not match", - "full-name": "Full Name", - "incorrect-username-or-password": "Incorrect username or password", + "error-cannot-delete-super-user": "Errore! Impossibile Eliminare Super User", + "existing-password-does-not-match": "La nuova password non corrisponde", + "full-name": "Nome", + "incorrect-username-or-password": "Passwor o nome untete sbagliato", "link-id": "Link ID", - "link-name": "Link Name", + "link-name": "Link Nome", "login": "Login", "logout": "Logout", - "new-password": "New Password", - "new-user": "New User", + "new-password": "Nuova Password", + "new-user": "Nuovo Utente", "password": "Password", - "password-has-been-reset-to-the-default-password": "Password has been reset to the default password", - "password-must-match": "Password must match", - "password-reset-failed": "Password reset failed", - "password-updated": "Password updated", - "reset-password": "Reset Password", - "sign-in": "Sign in", - "total-mealplans": "Total MealPlans", - "total-users": "Total Users", - "upload-photo": "Upload Photo", - "use-8-characters-or-more-for-your-password": "Use 8 characters or more for your password", - "user-created": "User created", - "user-creation-failed": "User creation failed", - "user-deleted": "User deleted", - "user-id": "User ID", - "user-id-with-value": "User ID: {id}", - "user-password": "User Password", - "user-successfully-logged-in": "User Successfully Logged In", - "user-update-failed": "User update failed", - "user-updated": "User updated", - "users": "Users", - "webhook-time": "Webhook Time", - "webhooks-enabled": "Webhooks Enabled", - "you-are-not-allowed-to-create-a-user": "You are not allowed to create a user", - "you-are-not-allowed-to-delete-this-user": "You are not allowed to delete this user" + "password-has-been-reset-to-the-default-password": "La password è stata reimpostata a quella di default", + "password-must-match": "Le password devono essere uguali", + "password-reset-failed": "Reimpostazione della password fallita", + "password-updated": "Password aggiornata", + "reset-password": "Reimposta Password", + "sign-in": "Accedi", + "total-mealplans": "Totale Piani Alimentari", + "total-users": "Totale Utenti", + "upload-photo": "Carica Foto", + "use-8-characters-or-more-for-your-password": "Usa 8 caratteri o piú per la password", + "user-created": "Utente creato", + "user-creation-failed": "Creazione utente fallita", + "user-deleted": "Utente eliminato", + "user-id": "ID Utente", + "user-id-with-value": "ID Utente:{id}", + "user-password": "Password Utente", + "user-successfully-logged-in": "Autenticato Con Successo", + "user-update-failed": "Aggiornamento Utenete Fallito", + "user-updated": "Utente Aggiornato", + "users": "Utenti", + "webhook-time": "Ora Webhook", + "webhooks-enabled": "Webhooks Abilitati", + "you-are-not-allowed-to-create-a-user": "Non sei autorizzato per la creazione di utenti", + "you-are-not-allowed-to-delete-this-user": "Non sei autorizzato per la eliminazione di utenti" } } \ No newline at end of file From c1370afb166d4311de21fbdec6f3ce77a315cdcc Mon Sep 17 00:00:00 2001 From: Hayden <64056131+hay-kot@users.noreply.github.com> Date: Tue, 4 May 2021 20:45:11 -0800 Subject: [PATCH 07/10] Refactor/backend routers (#388) * update router * update caddy file * setup depends in docker-fole * make changes for serving on subpath * set dev config * fix router signups * consolidate links * backup-functionality to dashboard * new user card * consolidate theme into profile * fix theme tests * fix pg tests * fix pg tests * remove unused import * mobile margin Co-authored-by: hay-kot --- .vscode/settings.json | 19 +- Caddyfile | 10 +- Caddyfile.dev | 9 + docker-compose.yml | 2 + frontend/src/api/recipe.js | 10 +- frontend/src/api/themes.js | 20 +- .../FormHelpers/ColorPickerDialog.vue | 12 +- .../FormHelpers}/ImportOptions.vue | 8 +- .../src/components/Recipe/Parts/Assets.vue | 15 +- .../components/Recipe/RecipeViewer/index.vue | 2 +- .../components/UI/Buttons/TheUploadBtn.vue | 7 +- frontend/src/components/UI/CardSection.vue | 66 +++--- .../components/UI/Dialogs/BackupDialog.vue | 142 ++++++++++++ .../src/components/UI/Dialogs/BaseDialog.vue | 35 ++- .../UI/Dialogs}/ImportDialog.vue | 2 +- .../Dashboard => components/UI}/StatCard.vue | 27 ++- frontend/src/components/UI/TheSidebar.vue | 10 - .../Admin/Backup/AvailableBackupCard.vue | 79 ------- .../src/pages/Admin/Backup/NewBackupCard.vue | 113 --------- frontend/src/pages/Admin/Backup/index.vue | 75 ------ .../pages/Admin/Dashboard/BackupViewer.vue | 15 +- .../src/pages/Admin/Dashboard/EventViewer.vue | 4 +- frontend/src/pages/Admin/Dashboard/index.vue | 2 +- .../src/pages/Admin/Profile/ThemeCard.vue | 215 ++++++++++++++++++ frontend/src/pages/Admin/Profile/UserCard.vue | 190 ++++++++++++++++ frontend/src/pages/Admin/Profile/index.vue | 215 ++---------------- .../src/pages/Admin/Theme/NewThemeDialog.vue | 89 -------- frontend/src/pages/Admin/Theme/ThemeCard.vue | 88 ------- frontend/src/pages/Admin/Theme/index.vue | 155 ------------- frontend/src/pages/HomePage.vue | 1 + frontend/src/pages/Recipes/AllRecipes.vue | 1 + frontend/src/routes/admin.js | 17 -- frontend/src/routes/index.js | 1 + frontend/src/store/modules/userSettings.js | 2 +- mealie/app.py | 24 +- mealie/db/database.py | 2 +- mealie/db/models/theme.py | 34 +-- mealie/routes/about/__init__.py | 4 +- mealie/routes/groups/__init__.py | 8 + mealie/routes/mealplans/__init__.py | 9 + mealie/routes/media/__init__.py | 7 + mealie/routes/media/recipe.py | 41 ++++ mealie/routes/media/user.py | 0 mealie/routes/recipe/__init__.py | 13 +- mealie/routes/recipe/recipe_crud_routes.py | 33 ++- mealie/routes/recipe/recipe_media.py | 70 ------ mealie/routes/site_settings/__init__.py | 9 + mealie/routes/theme_routes.py | 20 +- mealie/routes/users/__init__.py | 9 + mealie/routes/users/users.py | 9 - mealie/schema/theme.py | 5 +- .../integration_tests/test_settings_routes.py | 17 +- 52 files changed, 878 insertions(+), 1094 deletions(-) create mode 100644 Caddyfile.dev rename frontend/src/{pages/Admin/Backup => components/FormHelpers}/ImportOptions.vue (92%) create mode 100644 frontend/src/components/UI/Dialogs/BackupDialog.vue rename frontend/src/{pages/Admin/Backup => components/UI/Dialogs}/ImportDialog.vue (97%) rename frontend/src/{pages/Admin/Dashboard => components/UI}/StatCard.vue (71%) delete mode 100644 frontend/src/pages/Admin/Backup/AvailableBackupCard.vue delete mode 100644 frontend/src/pages/Admin/Backup/NewBackupCard.vue delete mode 100644 frontend/src/pages/Admin/Backup/index.vue create mode 100644 frontend/src/pages/Admin/Profile/ThemeCard.vue create mode 100644 frontend/src/pages/Admin/Profile/UserCard.vue delete mode 100644 frontend/src/pages/Admin/Theme/NewThemeDialog.vue delete mode 100644 frontend/src/pages/Admin/Theme/ThemeCard.vue delete mode 100644 frontend/src/pages/Admin/Theme/index.vue create mode 100644 mealie/routes/media/__init__.py create mode 100644 mealie/routes/media/recipe.py create mode 100644 mealie/routes/media/user.py delete mode 100644 mealie/routes/recipe/recipe_media.py delete mode 100644 mealie/routes/users/users.py diff --git a/.vscode/settings.json b/.vscode/settings.json index 21ff09b98..b0dfef42d 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,28 +1,19 @@ { "python.formatting.provider": "black", "python.pythonPath": ".venv/bin/python3.9", - "python.linting.pylintEnabled": true, + "python.linting.pylintEnabled": false, "python.linting.enabled": true, "python.testing.unittestEnabled": false, "python.testing.nosetestsEnabled": false, "python.testing.pytestEnabled": true, "python.testing.autoTestDiscoverOnSaveEnabled": false, "python.testing.pytestArgs": ["tests"], - "cSpell.enableFiletypes": [ - "!javascript", - "!python", - "!yaml" - ], + "cSpell.enableFiletypes": ["!javascript", "!python", "!yaml"], "i18n-ally.localesPaths": "frontend/src/locales/messages", "i18n-ally.sourceLanguage": "en-US", "i18n-ally.enabledFrameworks": ["vue"], "i18n-ally.keystyle": "nested", - "cSpell.words": [ - "compression", - "hkotel", - "performant", - "postgres", - "webp" - ], - "search.mode": "reuseEditor" + "cSpell.words": ["compression", "hkotel", "performant", "postgres", "webp"], + "search.mode": "reuseEditor", + "python.linting.flake8Enabled": true } diff --git a/Caddyfile b/Caddyfile index 0faf9fe7d..73fb21ab3 100644 --- a/Caddyfile +++ b/Caddyfile @@ -6,11 +6,11 @@ :80 { @proxied path /api/* /docs /openapi.json - root * /app/dist - encode gzip + encode gzip zstd uri strip_suffix / - handle_path /api/recipes/media/* { + # Handles Recipe Images / Assets + handle_path /api/media/recipes/* { root * /app/data/recipes/ file_server } @@ -20,8 +20,8 @@ } handle { - try_files {path}.html {path} / + root * /app/dist + try_files {path}.html {path} /index.html file_server } - } \ No newline at end of file diff --git a/Caddyfile.dev b/Caddyfile.dev new file mode 100644 index 000000000..f8d99bb39 --- /dev/null +++ b/Caddyfile.dev @@ -0,0 +1,9 @@ +{ + admin off +} + +localhost { + handle /mealie/* { + reverse_proxy http://127.0.0.1:9090 + } +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index 07593c567..09452c92c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -6,6 +6,8 @@ services: dockerfile: Dockerfile container_name: mealie restart: always + depends_on: + - "postgres" ports: - 9090:80 environment: diff --git a/frontend/src/api/recipe.js b/frontend/src/api/recipe.js index 4410b23a1..948f766d5 100644 --- a/frontend/src/api/recipe.js +++ b/frontend/src/api/recipe.js @@ -135,14 +135,18 @@ export const recipeAPI = { }, recipeImage(recipeSlug) { - return `/api/recipes/media/${recipeSlug}/images/original.webp`; + return `/api/media/recipes/${recipeSlug}/images/original.webp`; }, recipeSmallImage(recipeSlug) { - return `/api/recipes/media/${recipeSlug}/images/min-original.webp`; + return `/api/media/recipes/${recipeSlug}/images/min-original.webp`; }, recipeTinyImage(recipeSlug) { - return `/api/recipes/media/${recipeSlug}/images/tiny-original.webp`; + return `/api/media/recipes/${recipeSlug}/images/tiny-original.webp`; + }, + + recipeAssetPath(recipeSlug, assetName) { + return `api/media/recipes/${recipeSlug}/assets/${assetName}`; }, }; diff --git a/frontend/src/api/themes.js b/frontend/src/api/themes.js index cfd433594..0917afb31 100644 --- a/frontend/src/api/themes.js +++ b/frontend/src/api/themes.js @@ -6,10 +6,10 @@ const prefix = baseURL + "themes"; const settingsURLs = { allThemes: `${baseURL}themes`, - specificTheme: themeName => `${prefix}/${themeName}`, + specificTheme: id => `${prefix}/${id}`, createTheme: `${prefix}/create`, - updateTheme: themeName => `${prefix}/${themeName}`, - deleteTheme: themeName => `${prefix}/${themeName}`, + updateTheme: id => `${prefix}/${id}`, + deleteTheme: id => `${prefix}/${id}`, }; export const themeAPI = { @@ -32,22 +32,18 @@ export const themeAPI = { ); }, - update(themeName, colors) { - const body = { - name: themeName, - colors: colors, - }; + update(data) { return apiReq.put( - settingsURLs.updateTheme(themeName), - body, + settingsURLs.updateTheme(data.id), + data, () => i18n.t("settings.theme.error-updating-theme"), () => i18n.t("settings.theme.theme-updated") ); }, - delete(themeName) { + delete(id) { return apiReq.delete( - settingsURLs.deleteTheme(themeName), + settingsURLs.deleteTheme(id), null, () => i18n.t("settings.theme.error-deleting-theme"), () => i18n.t("settings.theme.theme-deleted") diff --git a/frontend/src/components/FormHelpers/ColorPickerDialog.vue b/frontend/src/components/FormHelpers/ColorPickerDialog.vue index 3c0fc13be..13ba003ec 100644 --- a/frontend/src/components/FormHelpers/ColorPickerDialog.vue +++ b/frontend/src/components/FormHelpers/ColorPickerDialog.vue @@ -3,7 +3,7 @@

{{ buttonText }}

- + diff --git a/frontend/src/pages/Admin/Backup/ImportOptions.vue b/frontend/src/components/FormHelpers/ImportOptions.vue similarity index 92% rename from frontend/src/pages/Admin/Backup/ImportOptions.vue rename to frontend/src/components/FormHelpers/ImportOptions.vue index 35e783c06..74f133edb 100644 --- a/frontend/src/pages/Admin/Backup/ImportOptions.vue +++ b/frontend/src/components/FormHelpers/ImportOptions.vue @@ -1,8 +1,8 @@ diff --git a/frontend/src/pages/Admin/Backup/ImportDialog.vue b/frontend/src/components/UI/Dialogs/ImportDialog.vue similarity index 97% rename from frontend/src/pages/Admin/Backup/ImportDialog.vue rename to frontend/src/components/UI/Dialogs/ImportDialog.vue index 0aed4c0e2..49eef5a33 100644 --- a/frontend/src/pages/Admin/Backup/ImportDialog.vue +++ b/frontend/src/components/UI/Dialogs/ImportDialog.vue @@ -48,7 +48,7 @@ - - diff --git a/frontend/src/pages/Admin/Backup/NewBackupCard.vue b/frontend/src/pages/Admin/Backup/NewBackupCard.vue deleted file mode 100644 index e7d903a12..000000000 --- a/frontend/src/pages/Admin/Backup/NewBackupCard.vue +++ /dev/null @@ -1,113 +0,0 @@ - - - - - diff --git a/frontend/src/pages/Admin/Backup/index.vue b/frontend/src/pages/Admin/Backup/index.vue deleted file mode 100644 index 6644901b5..000000000 --- a/frontend/src/pages/Admin/Backup/index.vue +++ /dev/null @@ -1,75 +0,0 @@ - - - - - diff --git a/frontend/src/pages/Admin/Dashboard/BackupViewer.vue b/frontend/src/pages/Admin/Dashboard/BackupViewer.vue index 5ef125968..cda4e95df 100644 --- a/frontend/src/pages/Admin/Dashboard/BackupViewer.vue +++ b/frontend/src/pages/Admin/Dashboard/BackupViewer.vue @@ -26,6 +26,8 @@ + + mdi-plus Create @@ -36,7 +38,7 @@ - mdi-backup-restore + mdi-database @@ -65,13 +67,14 @@ import TheUploadBtn from "@/components/UI/Buttons/TheUploadBtn"; import ImportSummaryDialog from "@/components/ImportSummaryDialog"; import { api } from "@/api"; -import StatCard from "./StatCard"; -import ImportDialog from "../Backup/ImportDialog"; +import StatCard from "@/components/UI/StatCard"; +import BackupDialog from "@/components/UI/Dialogs/BackupDialog"; +import ImportDialog from "@/components/UI/Dialogs/ImportDialog"; export default { - components: { StatCard, ImportDialog, TheUploadBtn, ImportSummaryDialog }, + components: { StatCard, ImportDialog, TheUploadBtn, ImportSummaryDialog, BackupDialog }, data() { return { - color: "secondary", + color: "accent", selectedName: "", selectedDate: "", loading: false, @@ -91,7 +94,6 @@ export default { async getAvailableBackups() { const response = await api.backups.requestAvailable(); this.availableBackups = response.imports; - console.log(this.availableBackups); }, async deleteBackup(name) { @@ -106,6 +108,7 @@ export default { this.selectedName = backup.name; this.$refs.import_dialog.open(); }, + async importBackup(data) { this.loading = true; const response = await api.backups.import(data.name, data); diff --git a/frontend/src/pages/Admin/Dashboard/EventViewer.vue b/frontend/src/pages/Admin/Dashboard/EventViewer.vue index 5070c677b..417fe1a17 100644 --- a/frontend/src/pages/Admin/Dashboard/EventViewer.vue +++ b/frontend/src/pages/Admin/Dashboard/EventViewer.vue @@ -49,12 +49,12 @@ + + \ No newline at end of file diff --git a/frontend/src/pages/Admin/Profile/UserCard.vue b/frontend/src/pages/Admin/Profile/UserCard.vue new file mode 100644 index 000000000..190c0fd65 --- /dev/null +++ b/frontend/src/pages/Admin/Profile/UserCard.vue @@ -0,0 +1,190 @@ + + + + + diff --git a/frontend/src/pages/Admin/Profile/index.vue b/frontend/src/pages/Admin/Profile/index.vue index 106d4b989..9aea25370 100644 --- a/frontend/src/pages/Admin/Profile/index.vue +++ b/frontend/src/pages/Admin/Profile/index.vue @@ -1,206 +1,27 @@ diff --git a/frontend/src/pages/Admin/Theme/NewThemeDialog.vue b/frontend/src/pages/Admin/Theme/NewThemeDialog.vue deleted file mode 100644 index f704bada5..000000000 --- a/frontend/src/pages/Admin/Theme/NewThemeDialog.vue +++ /dev/null @@ -1,89 +0,0 @@ - - - - - diff --git a/frontend/src/pages/Admin/Theme/ThemeCard.vue b/frontend/src/pages/Admin/Theme/ThemeCard.vue deleted file mode 100644 index e0d67e285..000000000 --- a/frontend/src/pages/Admin/Theme/ThemeCard.vue +++ /dev/null @@ -1,88 +0,0 @@ - - - - - diff --git a/frontend/src/pages/Admin/Theme/index.vue b/frontend/src/pages/Admin/Theme/index.vue deleted file mode 100644 index 4c30adf7d..000000000 --- a/frontend/src/pages/Admin/Theme/index.vue +++ /dev/null @@ -1,155 +0,0 @@ - - - - - diff --git a/frontend/src/pages/HomePage.vue b/frontend/src/pages/HomePage.vue index abc1f7d52..f8d0b5cd1 100644 --- a/frontend/src/pages/HomePage.vue +++ b/frontend/src/pages/HomePage.vue @@ -1,6 +1,7 @@ diff --git a/frontend/src/api/users.js b/frontend/src/api/users.js index 11609ff29..1ded3c6e9 100644 --- a/frontend/src/api/users.js +++ b/frontend/src/api/users.js @@ -16,17 +16,10 @@ const usersURLs = { userID: id => `${userPrefix}/${id}`, password: id => `${userPrefix}/${id}/password`, resetPassword: id => `${userPrefix}/${id}/reset-password`, + userAPICreate: `${userPrefix}/api-tokens`, + userAPIDelete: id => `${userPrefix}/api-tokens/${id}`, }; -function deleteErrorText(response) { - switch (response.data.detail) { - case "SUPER_USER": - return i18n.t("user.error-cannot-delete-super-user"); - - default: - return i18n.t("user.you-are-not-allowed-to-delete-this-user"); - } -} export const userAPI = { async login(formData) { let response = await apiReq.post(authURLs.token, formData, null, function() { @@ -90,4 +83,21 @@ export const userAPI = { () => i18n.t("user.password-has-been-reset-to-the-default-password") ); }, + async createAPIToken(name) { + const response = await apiReq.post(usersURLs.userAPICreate, { name }); + return response.data; + }, + async deleteAPIToken(id) { + const response = await apiReq.delete(usersURLs.userAPIDelete(id)); + return response.data; + }, +}; + +const deleteErrorText = response => { + switch (response.data.detail) { + case "SUPER_USER": + return i18n.t("user.error-cannot-delete-super-user"); + default: + return i18n.t("user.you-are-not-allowed-to-delete-this-user"); + } }; diff --git a/frontend/src/components/FormHelpers/CategoryTagSelector.vue b/frontend/src/components/FormHelpers/CategoryTagSelector.vue index 4a95208b5..32b5eeba3 100644 --- a/frontend/src/components/FormHelpers/CategoryTagSelector.vue +++ b/frontend/src/components/FormHelpers/CategoryTagSelector.vue @@ -100,7 +100,10 @@ export default { } }, flat() { - return this.selected.length > 0 && this.solo; + if (this.selected) { + return this.selected.length > 0 && this.solo; + } + return false; }, }, methods: { diff --git a/frontend/src/components/UI/CardSection.vue b/frontend/src/components/UI/CardSection.vue index c9e712739..da98e3b6b 100644 --- a/frontend/src/components/UI/CardSection.vue +++ b/frontend/src/components/UI/CardSection.vue @@ -7,26 +7,50 @@ {{ title }} - Random + + mdi-dice-multiple + + {{ $t("general.random") }} - + + + mdi-order-alphabetical-ascending + {{ $t("general.sort-alphabetically") }} + + mdi-star + {{ $t("general.rating") }} + + + mdi-new-box + + {{ $t("general.created") }} + + + mdi-update + {{ $t("general.updated") }} - - {{ $t("general.created") }} + + + mdi-shuffle-variant + + {{ $t("general.shuffle") }} @@ -114,6 +138,7 @@ export default { }, data() { return { + sortLoading: false, cardLimit: 30, loading: false, EVENTS: { @@ -121,6 +146,7 @@ export default { rating: "rating", created: "created", updated: "updated", + shuffle: "shuffle", }, }; }, @@ -165,6 +191,7 @@ export default { this.$router.push(`/recipe/${recipe.slug}`); }, sortRecipes(sortType) { + this.sortLoading = true; let sortTarget = [...this.recipes]; switch (sortType) { case this.EVENTS.az: @@ -179,11 +206,16 @@ export default { case this.EVENTS.updated: utils.recipe.sortByUpdated(sortTarget); break; + case this.EVENTS.shuffle: + utils.recipe.shuffle(sortTarget); + break; default: console.log("Unknown Event", sortType); return; } + this.$emit(SORT_EVENT, sortTarget); + this.sortLoading = false; }, }, }; diff --git a/frontend/src/components/UI/TheSidebar.vue b/frontend/src/components/UI/TheSidebar.vue index bf9543f7e..6153efec6 100644 --- a/frontend/src/components/UI/TheSidebar.vue +++ b/frontend/src/components/UI/TheSidebar.vue @@ -2,7 +2,7 @@
+ diff --git a/frontend/src/pages/Recipes/CategoryPage.vue b/frontend/src/pages/Recipes/CategoryPage.vue index 2d0acf225..89818210f 100644 --- a/frontend/src/pages/Recipes/CategoryPage.vue +++ b/frontend/src/pages/Recipes/CategoryPage.vue @@ -1,13 +1,6 @@ @@ -22,20 +15,30 @@ export default { return { title: "", recipes: [], + sortedResults: [], }; }, computed: { currentCategory() { return this.$route.params.category; }, + shownRecipes() { + if (this.sortedResults.length > 0) { + return this.sortedResults; + } else { + return this.recipes; + } + }, }, watch: { async currentCategory() { + this.sortedResults = []; this.getRecipes(); }, }, mounted() { this.getRecipes(); + this.sortedResults = []; }, methods: { async getRecipes() { @@ -43,11 +46,8 @@ export default { this.title = data.name; this.recipes = data.recipes; }, - sortAZ() { - this.recipes.sort((a, b) => (a.name > b.name ? 1 : -1)); - }, - sortRecent() { - this.recipes.sort((a, b) => (a.dateAdded > b.dateAdded ? -1 : 1)); + assignSorted(val) { + this.sortedResults = val.slice(); }, }, }; diff --git a/frontend/src/pages/Recipes/CustomPage.vue b/frontend/src/pages/Recipes/CustomPage.vue index 91d2bbe82..ad6f6003a 100644 --- a/frontend/src/pages/Recipes/CustomPage.vue +++ b/frontend/src/pages/Recipes/CustomPage.vue @@ -1,24 +1,23 @@