diff --git a/.gitignore b/.gitignore
index 89c39468..d5cad462 100644
--- a/.gitignore
+++ b/.gitignore
@@ -16,6 +16,7 @@ dev/data/debug/*
dev/data/img/*
dev/data/migration/*
dev/data/users/*
+dev/data/groups/*
.DS_Store
node_modules
diff --git a/dev/data/debug/.gitkeep b/dev/data/debug/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/dev/data/migration/.gitkeep b/dev/data/migration/.gitkeep
deleted file mode 100644
index e69de29b..00000000
diff --git a/docs/docs/documentation/getting-started/installation/installation-checklist.md b/docs/docs/documentation/getting-started/installation/installation-checklist.md
index cc1f5b0e..7b56a8c9 100644
--- a/docs/docs/documentation/getting-started/installation/installation-checklist.md
+++ b/docs/docs/documentation/getting-started/installation/installation-checklist.md
@@ -2,35 +2,59 @@
To install Mealie on your server there are a few steps for proper configuration. Let's go through them.
+!!! tip TLDR
+ Don't need step by step? Checkout the
+
+ - [SQLite docker-compose](./sqlite.md)
+ - [Postgres docker-compose](./postgres.md)
+
## Pre-work
To deploy mealie on your local network it is highly recommended to use docker to deploy the image straight from dockerhub. Using the docker-compose templates provided, you should be able to get a stack up and running easily by changing a few default values and deploying. You can deploy with either SQLite (default) or Postgres. SQLite is sufficient for most use cases. Additionally, with Mealie's automated backup and restore functionality, you can easily move between SQLite and Postgres as you wish.
[Get Docker](https://docs.docker.com/get-docker/)
+[Get Docker Compose](https://docs.docker.com/compose/install/)
+
[Mealie on Dockerhub](https://hub.docker.com/r/hkotel/mealie)
- linux/amd64
- linux/arm64
+!!! warning "32bit Support"
+ Due to a build dependency limitation, Mealie is not supported on 32bit ARM systems. If you're running into this limitation on a newer Raspberry Pi, please consider upgrading to a 64bit operating system on the Raspberry Pi.
+
+
## Step 1: Deciding on Deployment Type
-SQLite is a popular, open source, self-contained, zero-configuration database that is the ideal choice for Mealie when you have 1-20 Users. If you need to support many concurrent users, you may want to consider a more robust database such as PostgreSQL.
+SQLite is a popular, open source, self-contained, zero-configuration database that is the ideal choice for Mealie when you have 1-20 Users and your concurrent write operations will be some-what limited. If you need to support many concurrent users, you may want to consider a more robust database such as PostgreSQL.
You can find the relevant ready to use docker-compose files for supported installations at the links below.
-- [SQLite](/mealie/documentation/getting-started/installation/sqlite/)
-- [PostgreSQL](/mealie/documentation/getting-started/installation/postgres/)
+- [SQLite](./sqlite.md)
+- [PostgreSQL](./postgres.md)
+
+## Step 2: Setting up your files.
+
+The following steps were tested on a Ubuntu 20.04 server, but should work for most other Linux distributions. These steps are not required, but is how I generally will setup services on my server.
+
+
+1. SSH into your server and navigate to the home directory of the user you want to run Mealie as. If that is your current user, you can use `cd ~` to ensure you're in the right directory.
+2. Create a directory called `docker` and navigate into it. `mkdir docker && cd docker`
+3. Do the same for mealie `mkdir mealie && cd mealie`
+4. Create a docker-compose.yaml file in the mealie directory. `touch docker-compose.yaml`
+5. Use the text editor or your choice to edit the file and copy the contents of the docker-compose template for the deployment type you want to use. `nano docker-compose.yaml` or `vi docker-compose.yaml`
+
## Step 2: Customizing The `docker-compose.yaml` files.
-After you've decided on a database it's important to set a few ENV variables to ensure that you can use all the features of Mealie. I recommend that you verify and check that:
+After you've decided setup the files it's important to set a few ENV variables to ensure that you can use all the features of Mealie. I recommend that you verify and check that:
- [x] You've configured the relevant ENV variables for your database selection in the `docker-compose.yaml` files.
-- [x] You've configured the [SMTP server settings](/mealie/documentation/getting-started/installation/backend-config/#email) (used for invitations, password resets, etc)
+- [x] You've configured the [SMTP server settings](./backend-config.md#email) (used for invitations, password resets, etc)
- [x] Verified the port mapped on the `mealie-frontend` container is an open port on your server (Default: 9925)
-- [x] You've set the [`BASE_URL`](/mealie/documentation/getting-started/installation/backend-config/#general) variable.
+- [x] You've set the [`BASE_URL`](./backend-config.md#general) variable.
- [x] You've set the `DEFAULT_EMAIL` and `DEFAULT_GROUP` variable.
-- [x] Make any theme changes on the frontend container. [See Frontend Config](/mealie/documentation/getting-started/installation/frontend-config/#themeing)
+- [x] Make any theme changes on the frontend container. [See Frontend Config](./frontend-config.md#themeing)
## Step 3: Startup
After you've configured your database, and updated the `docker-compose.yaml` files, you can start Mealie by running the following command in the directory where you've added your `docker-compose.yaml`.
@@ -48,6 +72,6 @@ You should see the containers start up without error. You should now be able to
**Password:** MyPassword
## Step 4: Backup
-While v1.0.0 is a great step to data-stability and security, it's not a backup. As a core feature, Mealie will run a backup of the entire database every 24 hours. Optionally, you can also run backups whenever you'd like through the UI or the API.
+While v1.0.0 is a great step to data-stability and security, it's not a backup. As a core feature, Mealie will run a backup every 24 hours. Optionally, you can also run backups whenever you'd like through the UI or the API.
These backups are just plain .zip files that you can download from the UI or access via the mounted volume on your system. For complete data protection you MUST store these backups somewhere safe, and outside of the server where they are deployed. A favorite solution of mine is [autorestic](https://autorestic.vercel.app/) which can be configured via yaml to run an off-site backup on a regular basis.
\ No newline at end of file
diff --git a/docs/docs/overrides/api.html b/docs/docs/overrides/api.html
index 95b6e816..7de978ea 100644
--- a/docs/docs/overrides/api.html
+++ b/docs/docs/overrides/api.html
@@ -14,7 +14,7 @@
diff --git a/frontend/api/admin/admin-about.ts b/frontend/api/admin/admin-about.ts
index dac818f2..6f5933d9 100644
--- a/frontend/api/admin/admin-about.ts
+++ b/frontend/api/admin/admin-about.ts
@@ -17,6 +17,7 @@ export interface AdminAboutInfo {
dbType: string;
dbUrl: string;
defaultGroup: string;
+ versionLatest: string;
}
export interface AdminStatistics {
@@ -31,6 +32,7 @@ export interface CheckAppConfig {
emailReady: boolean;
baseUrlSet: boolean;
isSiteSecure: boolean;
+ isUpToDate: boolean;
ldapReady: boolean;
}
diff --git a/frontend/api/class-interfaces/recipe-bulk-actions.ts b/frontend/api/class-interfaces/recipe-bulk-actions.ts
index 1530642b..d8644d01 100644
--- a/frontend/api/class-interfaces/recipe-bulk-actions.ts
+++ b/frontend/api/class-interfaces/recipe-bulk-actions.ts
@@ -31,10 +31,21 @@ interface BulkActionResponse {
errors: BulkActionError[];
}
+export interface GroupDataExport {
+ id: string;
+ groupId: string;
+ name: string;
+ filename: string;
+ path: string;
+ size: string;
+ expires: Date;
+}
+
const prefix = "/api";
const routes = {
bulkExport: prefix + "/recipes/bulk-actions/export",
+ purgeExports: prefix + "/recipes/bulk-actions/export/purge",
bulkCategorize: prefix + "/recipes/bulk-actions/categorize",
bulkTag: prefix + "/recipes/bulk-actions/tag",
bulkDelete: prefix + "/recipes/bulk-actions/delete",
@@ -56,4 +67,12 @@ export class BulkActionsAPI extends BaseAPI {
async bulkDelete(payload: RecipeBulkDelete) {
return await this.requests.post(routes.bulkDelete, payload);
}
+
+ async fetchExports() {
+ return await this.requests.get(routes.bulkExport);
+ }
+
+ async purgeExports() {
+ return await this.requests.delete(routes.purgeExports);
+ }
}
diff --git a/frontend/components/Domain/Admin/AdminBackupImportOptions.vue b/frontend/components/Domain/Admin/AdminBackupImportOptions.vue
index fe73d2a9..bf5e57d4 100644
--- a/frontend/components/Domain/Admin/AdminBackupImportOptions.vue
+++ b/frontend/components/Domain/Admin/AdminBackupImportOptions.vue
@@ -38,18 +38,6 @@ export default {
value: true,
text: this.$t("general.recipes"),
},
- settings: {
- value: true,
- text: this.$t("general.settings"),
- },
- pages: {
- value: true,
- text: this.$t("settings.pages"),
- },
- themes: {
- value: true,
- text: this.$t("general.themes"),
- },
users: {
value: true,
text: this.$t("user.users"),
@@ -58,10 +46,6 @@ export default {
value: true,
text: this.$t("group.groups"),
},
- notifications: {
- value: true,
- text: this.$t("events.notification"),
- },
},
forceImport: false,
};
@@ -73,12 +57,12 @@ export default {
emitValue() {
this.$emit(UPDATE_EVENT, {
recipes: this.options.recipes.value,
- settings: this.options.settings.value,
- themes: this.options.themes.value,
- pages: this.options.pages.value,
+ settings: false,
+ themes: false,
+ pages: false,
users: this.options.users.value,
groups: this.options.groups.value,
- notifications: this.options.notifications.value,
+ notifications: false,
forceImport: this.forceImport,
});
},
diff --git a/frontend/components/Domain/Group/GroupExportData.vue b/frontend/components/Domain/Group/GroupExportData.vue
new file mode 100644
index 00000000..b1491882
--- /dev/null
+++ b/frontend/components/Domain/Group/GroupExportData.vue
@@ -0,0 +1,60 @@
+
+
+
+ {{ getTimeToExpire(item.expires) }}
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/components/Domain/Recipe/RecipeDataTable.vue b/frontend/components/Domain/Recipe/RecipeDataTable.vue
index f7302b25..0061c37f 100644
--- a/frontend/components/Domain/Recipe/RecipeDataTable.vue
+++ b/frontend/components/Domain/Recipe/RecipeDataTable.vue
@@ -7,6 +7,7 @@
:items="recipes"
:items-per-page="15"
class="elevation-0"
+ :loading="loading"
@input="setValue(selected)"
>
@@ -22,6 +23,9 @@
+
+
+
@@ -49,6 +53,7 @@ interface ShowHeaders {
owner: Boolean;
tags: Boolean;
categories: Boolean;
+ tools: Boolean;
recipeYield: Boolean;
dateAdded: Boolean;
}
@@ -61,6 +66,11 @@ export default defineComponent({
required: false,
default: () => [],
},
+ loading: {
+ type: Boolean,
+ required: false,
+ default: false,
+ },
recipes: {
type: Array as () => Recipe[],
default: () => [],
@@ -103,12 +113,16 @@ export default defineComponent({
if (show.tags) {
hdrs.push({ text: "Tags", value: "tags" });
}
+ if (show.tools) {
+ hdrs.push({ text: "Tools", value: "tools" });
+ }
if (show.recipeYield) {
hdrs.push({ text: "Yield", value: "recipeYield" });
}
if (show.dateAdded) {
hdrs.push({ text: "Date Added", value: "dateAdded" });
}
+
return hdrs;
});
diff --git a/frontend/components/global/BaseCardSectionTitle.vue b/frontend/components/global/BaseCardSectionTitle.vue
index e060769f..a82e588e 100644
--- a/frontend/components/global/BaseCardSectionTitle.vue
+++ b/frontend/components/global/BaseCardSectionTitle.vue
@@ -1,6 +1,13 @@
-
+
{{ icon }}
@@ -12,7 +19,7 @@
-
+
@@ -27,6 +34,10 @@ export default {
type: String,
default: "",
},
+ section: {
+ type: Boolean,
+ default: false,
+ },
},
};
diff --git a/frontend/pages/admin/backups.vue b/frontend/pages/admin/backups.vue
index aac42eff..1a877460 100644
--- a/frontend/pages/admin/backups.vue
+++ b/frontend/pages/admin/backups.vue
@@ -2,7 +2,7 @@
-
+
-
@@ -34,73 +33,74 @@
-
-
-
-
-
- {{ $t("general.custom") }}
-
-
-
+
+ {{ $t("settings.backup.create-heading") }}
+
+
-
-
-
- Templates
-
+ Lorem ipsum dolor sit, amet consectetur adipisicing elit. Dolores molestiae alias incidunt fugiat!
+ Recusandae natus numquam iusto voluptates deserunt quia? Sed voluptate rem facilis tempora, perspiciatis
+ corrupti dolore obcaecati laudantium!
+
+
+
+
-
-
+
+
-
-
- {{ $d(Date.parse(item.date), "medium") }}
-
-
-
-
-
-
-
-
+
+
+
+
+ {{ $d(Date.parse(item.date), "medium") }}
+
+
+
+ {{ $globals.icons.delete }}
+
+
+
+
+
+
+
diff --git a/frontend/pages/admin/site-settings.vue b/frontend/pages/admin/site-settings.vue
index 2a9d24a0..5f689d00 100644
--- a/frontend/pages/admin/site-settings.vue
+++ b/frontend/pages/admin/site-settings.vue
@@ -10,64 +10,49 @@
-
-
-
-
- {{ check.status ? $globals.icons.checkboxMarkedCircle : $globals.icons.close }}
-
-
-
- {{ check.text }}
-
- {{ check.status ? check.successText : check.errorText }}
-
-
-
-
+
+
+ {{ check.text }}
+
+ {{ check.status ? check.successText : check.errorText }}
+
+
-
-
-
-
-
-
-
- {{ appConfig.emailReady ? $globals.icons.checkboxMarkedCircle : $globals.icons.close }}
-
-
-
-
- Email Configuration Status
-
-
- {{ appConfig.emailReady ? "Ready" : "Not Ready - Check Env Variables" }}
-
-
-
-
-
-
-
- {{ $globals.icons.email }}
- {{ $t("general.test") }}
-
-
-
-
-
-
- Email Test Result: {{ success ? "Succeeded" : "Failed" }}
- Errors: {{ error }}
-
-
-
+
+
+ Email Configuration Status
+
+ {{ appConfig.emailReady ? "Ready" : "Not Ready - Check Environmental Variables" }}
+
+
+
+
+
+ {{ $globals.icons.email }}
+ {{ $t("general.test") }}
+
+
+
+
+ Email Test Result: {{ success ? "Succeeded" : "Failed" }}
+ Errors: {{ error }}
+
+
+
+
@@ -101,7 +86,7 @@ import {
useAsync,
useContext,
} from "@nuxtjs/composition-api";
-import { CheckAppConfig } from "~/api/admin/admin-about";
+import { AdminAboutInfo, CheckAppConfig } from "~/api/admin/admin-about";
import { useAdminApi, useUserApi } from "~/composables/api";
import { validators } from "~/composables/use-validators";
import { useAsyncKey } from "~/composables/use-utils";
@@ -128,6 +113,7 @@ export default defineComponent({
emailReady: false,
baseUrlSet: false,
isSiteSecure: false,
+ isUpToDate: false,
ldapReady: false,
});
@@ -151,22 +137,34 @@ export default defineComponent({
const simpleChecks = computed(() => {
return [
{
- status: appConfig.value.baseUrlSet,
- text: "Server Side Base URL",
- errorText: "`BASE_URL` still default on API Server",
- successText: "Server Side URL does not match the default",
+ status: appConfig.value.isUpToDate,
+ text: "Application Version",
+ errorText: `Your current version (${rawAppInfo.value.version}) does not match the latest release. Considering updating to the latest version (${rawAppInfo.value.versionLatest}).`,
+ successText: "Mealie is up to date",
+ warning: true,
},
{
status: appConfig.value.isSiteSecure,
text: "Secure Site",
- errorText: "Serve via localhost or secure with https.",
+ errorText: "Serve via localhost or secure with https. Clipboard and additional browser APIs may not work.",
successText: "Site is accessed by localhost or https",
+ warning: false,
+ },
+ {
+ status: appConfig.value.baseUrlSet,
+ text: "Server Side Base URL",
+ errorText:
+ "`BASE_URL` is still the default value on API Server. This will cause issues with notifications links generated on the server for emails, etc.",
+ successText: "Server Side URL does not match the default",
+ warning: false,
},
{
status: appConfig.value.ldapReady,
text: "LDAP Ready",
- errorText: "Not all LDAP Values are configured",
+ errorText:
+ "Not all LDAP Values are configured. This can be ignored if you are not using LDAP Authentication.",
successText: "Required LDAP variables are all set.",
+ warning: true,
},
];
});
@@ -201,23 +199,30 @@ export default defineComponent({
return false;
});
- function getTextClass(booly: boolean | any) {
- return booly ? "success--text" : "error--text";
- }
- function getColor(booly: boolean | any) {
- return booly ? "success" : "error";
+ function getColor(booly: boolean | any, warning = false) {
+ const falsey = warning ? "warning" : "error";
+ return booly ? "success" : falsey;
}
// ============================================================
// General About Info
+
// @ts-ignore
const { $globals, i18n } = useContext();
+ // @ts-ignore
+ const rawAppInfo = ref({
+ version: "null",
+ versionLatest: "null",
+ });
+
function getAppInfo() {
const statistics = useAsync(async () => {
const { data } = await adminApi.about.about();
if (data) {
+ rawAppInfo.value = data;
+
const prettyInfo = [
{
name: i18n.t("about.version"),
@@ -275,7 +280,6 @@ export default defineComponent({
return {
simpleChecks,
getColor,
- getTextClass,
appConfig,
validEmail,
validators,
diff --git a/frontend/pages/meal-plan/planner.vue b/frontend/pages/meal-plan/planner.vue
index 3e90c1f6..f2fdc2ef 100644
--- a/frontend/pages/meal-plan/planner.vue
+++ b/frontend/pages/meal-plan/planner.vue
@@ -382,10 +382,6 @@ export default defineComponent({