mirror of
				https://github.com/mealie-recipes/mealie.git
				synced 2025-10-31 10:13:32 -04:00 
			
		
		
		
	Bug/misc bug fixes (#400)
* potentiall fix #329 * typo * auto purge events * image error * update import dialog * fix scheduler interval time * adjust icon position * check for property Co-authored-by: hay-kot <hay-kot@pm.me>
This commit is contained in:
		| @@ -8,7 +8,9 @@ | |||||||
|       :loading="loading" |       :loading="loading" | ||||||
|     > |     > | ||||||
|       <template v-slot:open="{ open }"> |       <template v-slot:open="{ open }"> | ||||||
|         <v-btn @click="open" class="mx-2" small :color="color"> <v-icon left> mdi-plus </v-icon> {{$t('general.custom')}} </v-btn> |         <v-btn @click="open" class="mx-2" small :color="color"> | ||||||
|  |           <v-icon left> mdi-plus </v-icon> {{ $t("general.custom") }} | ||||||
|  |         </v-btn> | ||||||
|       </template> |       </template> | ||||||
|       <v-card-text class="mt-6"> |       <v-card-text class="mt-6"> | ||||||
|         <v-text-field dense :label="$t('settings.backup.backup-tag')" v-model="tag"></v-text-field> |         <v-text-field dense :label="$t('settings.backup.backup-tag')" v-model="tag"></v-text-field> | ||||||
|   | |||||||
| @@ -1,7 +1,12 @@ | |||||||
| <template> | <template> | ||||||
|   <div> |   <div> | ||||||
|     <slot name="open" v-bind="{ open }"> </slot> |     <slot name="open" v-bind="{ open }"> </slot> | ||||||
|     <v-dialog v-model="dialog" :width="modalWidth + 'px'" :content-class="top ? 'top-dialog' : undefined"> |     <v-dialog | ||||||
|  |       v-model="dialog" | ||||||
|  |       :width="modalWidth + 'px'" | ||||||
|  |       :content-class="top ? 'top-dialog' : undefined" | ||||||
|  |       :fullscreen="$vuetify.breakpoint.xsOnly" | ||||||
|  |     > | ||||||
|       <v-card height="100%"> |       <v-card height="100%"> | ||||||
|         <v-app-bar dark :color="color" class="mt-n1 mb-0"> |         <v-app-bar dark :color="color" class="mt-n1 mb-0"> | ||||||
|           <v-icon large left> |           <v-icon large left> | ||||||
| @@ -24,6 +29,7 @@ | |||||||
|             <v-btn color="error" text @click="deleteEvent" v-if="$listeners.delete"> |             <v-btn color="error" text @click="deleteEvent" v-if="$listeners.delete"> | ||||||
|               {{ $t("general.delete") }} |               {{ $t("general.delete") }} | ||||||
|             </v-btn> |             </v-btn> | ||||||
|  |             <slot name="extra-buttons"> </slot> | ||||||
|             <v-btn color="success" type="submit" @click="submitEvent"> |             <v-btn color="success" type="submit" @click="submitEvent"> | ||||||
|               {{ submitText }} |               {{ submitText }} | ||||||
|             </v-btn> |             </v-btn> | ||||||
|   | |||||||
| @@ -1,58 +1,52 @@ | |||||||
| <template> | <template> | ||||||
|   <div class="text-center"> |   <div class="text-center"> | ||||||
|     <v-dialog v-model="dialog" width="500" :fullscreen="$vuetify.breakpoint.xsOnly"> |     <BaseDialog | ||||||
|       <v-card> |       :title="name" | ||||||
|         <v-toolbar dark color="primary" v-show="$vuetify.breakpoint.xsOnly"> |       titleIcon="mdi-database" | ||||||
|           <v-btn icon dark @click="dialog = false"> |       :submit-text="$t('general.import')" | ||||||
|             <v-icon>mdi-close</v-icon> |       :loading="loading" | ||||||
|           </v-btn> |       ref="baseDialog" | ||||||
|           <v-toolbar-title></v-toolbar-title> |       @submit="raiseEvent" | ||||||
|           <v-spacer></v-spacer> |     > | ||||||
|           <v-toolbar-items> |       <v-card-subtitle class="mb-n3 mt-3" v-if="date"> {{ $d(new Date(date), "medium") }} </v-card-subtitle> | ||||||
|             <v-btn dark text @click="raiseEvent('import')"> |       <v-divider></v-divider> | ||||||
|               {{ $t("general.import") }} |  | ||||||
|  |       <v-card-text> | ||||||
|  |         <ImportOptions @update-options="updateOptions" class="mt-5 mb-2" /> | ||||||
|  |  | ||||||
|  |         <v-divider></v-divider> | ||||||
|  |  | ||||||
|  |         <v-checkbox | ||||||
|  |           dense | ||||||
|  |           :label="$t('settings.remove-existing-entries-matching-imported-entries')" | ||||||
|  |           v-model="forceImport" | ||||||
|  |         ></v-checkbox> | ||||||
|  |       </v-card-text> | ||||||
|  |  | ||||||
|  |       <v-divider></v-divider> | ||||||
|  |       <template v-slot:extra-buttons> | ||||||
|  |         <TheDownloadBtn :download-url="downloadUrl"> | ||||||
|  |           <template v-slot:default="{ downloadFile }"> | ||||||
|  |             <v-btn class="mr-1" color="info" @click="downloadFile"> | ||||||
|  |               <v-icon left> mdi-download </v-icon> | ||||||
|  |               {{ $t("general.download") }} | ||||||
|             </v-btn> |             </v-btn> | ||||||
|           </v-toolbar-items> |           </template> | ||||||
|         </v-toolbar> |         </TheDownloadBtn> | ||||||
|         <v-card-title> {{ name }} </v-card-title> |       </template> | ||||||
|         <v-card-subtitle class="mb-n3" v-if="date"> {{ $d(new Date(date), "medium") }} </v-card-subtitle> |     </BaseDialog> | ||||||
|         <v-divider></v-divider> |  | ||||||
|  |  | ||||||
|         <v-card-text> |  | ||||||
|           <ImportOptions @update-options="updateOptions" class="mt-5 mb-2" /> |  | ||||||
|  |  | ||||||
|           <v-divider></v-divider> |  | ||||||
|  |  | ||||||
|           <v-checkbox |  | ||||||
|             dense |  | ||||||
|             :label="$t('settings.remove-existing-entries-matching-imported-entries')" |  | ||||||
|             v-model="forceImport" |  | ||||||
|           ></v-checkbox> |  | ||||||
|         </v-card-text> |  | ||||||
|  |  | ||||||
|         <v-divider></v-divider> |  | ||||||
|  |  | ||||||
|         <v-card-actions> |  | ||||||
|           <TheDownloadBtn :download-url="downloadUrl" /> |  | ||||||
|           <v-spacer></v-spacer> |  | ||||||
|           <v-btn color="error" text @click="raiseEvent('delete')"> |  | ||||||
|             {{ $t("general.delete") }} |  | ||||||
|           </v-btn> |  | ||||||
|           <v-btn color="success" outlined @click="raiseEvent('import')" v-show="$vuetify.breakpoint.smAndUp"> |  | ||||||
|             {{ $t("general.import") }} |  | ||||||
|           </v-btn> |  | ||||||
|         </v-card-actions> |  | ||||||
|       </v-card> |  | ||||||
|     </v-dialog> |  | ||||||
|   </div> |   </div> | ||||||
| </template> | </template> | ||||||
|  |  | ||||||
| <script> | <script> | ||||||
|  | const IMPORT_EVENT = "import"; | ||||||
|  | import { api } from "@/api"; | ||||||
|  | import BaseDialog from "./BaseDialog"; | ||||||
| import ImportOptions from "@/components/FormHelpers/ImportOptions"; | import ImportOptions from "@/components/FormHelpers/ImportOptions"; | ||||||
| import TheDownloadBtn from "@/components/UI/Buttons/TheDownloadBtn.vue"; | import TheDownloadBtn from "@/components/UI/Buttons/TheDownloadBtn.vue"; | ||||||
| import { backupURLs } from "@/api/backup"; | import { backupURLs } from "@/api/backup"; | ||||||
| export default { | export default { | ||||||
|   components: { ImportOptions, TheDownloadBtn }, |   components: { ImportOptions, TheDownloadBtn, BaseDialog }, | ||||||
|   props: { |   props: { | ||||||
|     name: { |     name: { | ||||||
|       default: "Backup Name", |       default: "Backup Name", | ||||||
| @@ -63,6 +57,7 @@ export default { | |||||||
|   }, |   }, | ||||||
|   data() { |   data() { | ||||||
|     return { |     return { | ||||||
|  |       loading: false, | ||||||
|       options: { |       options: { | ||||||
|         recipes: true, |         recipes: true, | ||||||
|         settings: true, |         settings: true, | ||||||
| @@ -87,12 +82,13 @@ export default { | |||||||
|     }, |     }, | ||||||
|     open() { |     open() { | ||||||
|       this.dialog = true; |       this.dialog = true; | ||||||
|  |       this.$refs.baseDialog.open(); | ||||||
|     }, |     }, | ||||||
|     close() { |     close() { | ||||||
|       this.dialog = false; |       this.dialog = false; | ||||||
|     }, |     }, | ||||||
|     raiseEvent(event) { |     async raiseEvent() { | ||||||
|       let eventData = { |       const eventData = { | ||||||
|         name: this.name, |         name: this.name, | ||||||
|         force: this.forceImport, |         force: this.forceImport, | ||||||
|         rebase: this.rebaseImport, |         rebase: this.rebaseImport, | ||||||
| @@ -102,8 +98,18 @@ export default { | |||||||
|         users: this.options.users, |         users: this.options.users, | ||||||
|         groups: this.options.groups, |         groups: this.options.groups, | ||||||
|       }; |       }; | ||||||
|       this.close(); |       this.loading = true; | ||||||
|       this.$emit(event, eventData); |       const importData = await this.importBackup(eventData); | ||||||
|  |  | ||||||
|  |       this.$emit(IMPORT_EVENT, importData); | ||||||
|  |       this.loading = false; | ||||||
|  |     }, | ||||||
|  |     async importBackup(data) { | ||||||
|  |       this.loading = true; | ||||||
|  |       const response = await api.backups.import(data.name, data); | ||||||
|  |       if (response) { | ||||||
|  |         return response.data; | ||||||
|  |       } | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -12,7 +12,7 @@ | |||||||
|  |  | ||||||
|           <v-list-item-content> |           <v-list-item-content> | ||||||
|             <v-list-item-title> {{ user.fullName }}</v-list-item-title> |             <v-list-item-title> {{ user.fullName }}</v-list-item-title> | ||||||
|             <v-list-item-subtitle> {{ user.admin ? $t('user.admin') : $t('user.user') }}</v-list-item-subtitle> |             <v-list-item-subtitle> {{ user.admin ? $t("user.admin") : $t("user.user") }}</v-list-item-subtitle> | ||||||
|           </v-list-item-content> |           </v-list-item-content> | ||||||
|         </v-list-item> |         </v-list-item> | ||||||
|       </template> |       </template> | ||||||
| @@ -77,6 +77,11 @@ export default { | |||||||
|     this.getVersion(); |     this.getVersion(); | ||||||
|     this.resetView(); |     this.resetView(); | ||||||
|   }, |   }, | ||||||
|  |   watch: { | ||||||
|  |     user() { | ||||||
|  |       this.hideImage = false; | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |  | ||||||
|   computed: { |   computed: { | ||||||
|     isMain() { |     isMain() { | ||||||
|   | |||||||
| @@ -1,6 +1,7 @@ | |||||||
| export const initials = { | export const initials = { | ||||||
|   computed: { |   computed: { | ||||||
|     initials() { |     initials() { | ||||||
|  |       if (!this.user.fullName) return "00" | ||||||
|       const allNames = this.user.fullName.trim().split(" "); |       const allNames = this.user.fullName.trim().split(" "); | ||||||
|       const initials = allNames.reduce( |       const initials = allNames.reduce( | ||||||
|         (acc, curr, index) => { |         (acc, curr, index) => { | ||||||
|   | |||||||
| @@ -112,13 +112,7 @@ export default { | |||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     async importBackup(data) { |     async importBackup(data) { | ||||||
|       this.loading = true; |       this.$refs.report.open(data); | ||||||
|       const response = await api.backups.import(data.name, data); |  | ||||||
|       if (response) { |  | ||||||
|         const importData = response.data; |  | ||||||
|         this.$refs.report.open(importData); |  | ||||||
|       } |  | ||||||
|       this.loading = false; |  | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     async createBackup() { |     async createBackup() { | ||||||
|   | |||||||
| @@ -123,19 +123,12 @@ export default { | |||||||
|       }, |       }, | ||||||
|       showPassword: false, |       showPassword: false, | ||||||
|       loading: false, |       loading: false, | ||||||
|       user: { |       user: {}, | ||||||
|         fullName: "", |  | ||||||
|         email: "", |  | ||||||
|         group: "", |  | ||||||
|         admin: false, |  | ||||||
|         id: 0, |  | ||||||
|       }, |  | ||||||
|     }; |     }; | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   computed: { |   computed: { | ||||||
|     userProfileImage() { |     userProfileImage() { | ||||||
|       this.resetImage(); |  | ||||||
|       return `api/users/${this.user.id}/image`; |       return `api/users/${this.user.id}/image`; | ||||||
|     }, |     }, | ||||||
|   }, |   }, | ||||||
| @@ -144,10 +137,13 @@ export default { | |||||||
|     this.refreshProfile(); |     this.refreshProfile(); | ||||||
|   }, |   }, | ||||||
|  |  | ||||||
|   methods: { |   watch: { | ||||||
|     resetImage() { |     user() { | ||||||
|       this.hideImage = false; |       this.hideImage = false; | ||||||
|     }, |     }, | ||||||
|  |   }, | ||||||
|  |  | ||||||
|  |   methods: { | ||||||
|     async refreshProfile() { |     async refreshProfile() { | ||||||
|       this.user = await api.users.self(); |       this.user = await api.users.self(); | ||||||
|     }, |     }, | ||||||
|   | |||||||
| @@ -38,7 +38,7 @@ class _Recipes(BaseDocument): | |||||||
|         return f"{slug}.{extension}" |         return f"{slug}.{extension}" | ||||||
|  |  | ||||||
|     def count_uncategorized(self, session: Session, count=True, override_schema=None) -> int: |     def count_uncategorized(self, session: Session, count=True, override_schema=None) -> int: | ||||||
|         return self._countr_attribute( |         return self._count_attribute( | ||||||
|             session, |             session, | ||||||
|             attribute_name=RecipeModel.recipe_category, |             attribute_name=RecipeModel.recipe_category, | ||||||
|             attr_match=None, |             attr_match=None, | ||||||
| @@ -47,7 +47,7 @@ class _Recipes(BaseDocument): | |||||||
|         ) |         ) | ||||||
|  |  | ||||||
|     def count_untagged(self, session: Session, count=True, override_schema=None) -> int: |     def count_untagged(self, session: Session, count=True, override_schema=None) -> int: | ||||||
|         return self._countr_attribute( |         return self._count_attribute( | ||||||
|             session, attribute_name=RecipeModel.tags, attr_match=None, count=count, override_schema=override_schema |             session, attribute_name=RecipeModel.tags, attr_match=None, count=count, override_schema=override_schema | ||||||
|         ) |         ) | ||||||
|  |  | ||||||
|   | |||||||
| @@ -178,7 +178,7 @@ class BaseDocument: | |||||||
|         else: |         else: | ||||||
|             return session.query(self.sql_model).filter_by(**{match_key: match_value}).count() |             return session.query(self.sql_model).filter_by(**{match_key: match_value}).count() | ||||||
|  |  | ||||||
|     def _countr_attribute( |     def _count_attribute( | ||||||
|         self, session: Session, attribute_name: str, attr_match: str = None, count=True, override_schema=None |         self, session: Session, attribute_name: str, attr_match: str = None, count=True, override_schema=None | ||||||
|     ) -> Union[int, BaseModel]: |     ) -> Union[int, BaseModel]: | ||||||
|         eff_schema = override_schema or self.schema |         eff_schema = override_schema or self.schema | ||||||
|   | |||||||
| @@ -1,7 +1,10 @@ | |||||||
|  | import datetime | ||||||
|  |  | ||||||
| from apscheduler.schedulers.background import BackgroundScheduler | from apscheduler.schedulers.background import BackgroundScheduler | ||||||
| from mealie.core import root_logger | from mealie.core import root_logger | ||||||
| from mealie.db.database import db | from mealie.db.database import db | ||||||
| from mealie.db.db_setup import create_session | from mealie.db.db_setup import create_session | ||||||
|  | from mealie.db.models.event import Event | ||||||
| from mealie.schema.user import GroupInDB | from mealie.schema.user import GroupInDB | ||||||
| from mealie.services.backups.exports import auto_backup_job | from mealie.services.backups.exports import auto_backup_job | ||||||
| from mealie.services.scheduler.global_scheduler import scheduler | from mealie.services.scheduler.global_scheduler import scheduler | ||||||
| @@ -13,6 +16,21 @@ logger = root_logger.get_logger() | |||||||
| # TODO Fix Scheduler | # TODO Fix Scheduler | ||||||
|  |  | ||||||
|  |  | ||||||
|  | @scheduler.scheduled_job(trigger="interval", minutes=1440) | ||||||
|  | def purge_events_database(): | ||||||
|  |     """ | ||||||
|  |     Ran daily. Purges all events after 100 | ||||||
|  |     """ | ||||||
|  |     logger.info("Purging Events in Database") | ||||||
|  |     expiration_days = 7 | ||||||
|  |     limit = datetime.datetime.now() - datetime.timedelta(days=expiration_days) | ||||||
|  |     session = create_session() | ||||||
|  |     session.query(Event).filter(Event.time_stamp <= limit).delete() | ||||||
|  |     session.commit() | ||||||
|  |     session.close() | ||||||
|  |     logger.info("Events Purges") | ||||||
|  |  | ||||||
|  |  | ||||||
| @scheduler.scheduled_job(trigger="interval", minutes=30) | @scheduler.scheduled_job(trigger="interval", minutes=30) | ||||||
| def update_webhook_schedule(): | def update_webhook_schedule(): | ||||||
|     """ |     """ | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user