mirror of
https://github.com/hay-kot/mealie.git
synced 2025-08-22 14:33:33 -07:00
Improved import summary dialog
This commit is contained in:
parent
b33bab1194
commit
4de7bd8016
7 changed files with 281 additions and 22 deletions
|
@ -8,9 +8,9 @@ const backupURLs = {
|
|||
// Backup
|
||||
available: `${backupBase}available`,
|
||||
createBackup: `${backupBase}export/database`,
|
||||
importBackup: (fileName) => `${backupBase}${fileName}/import`,
|
||||
deleteBackup: (fileName) => `${backupBase}${fileName}/delete`,
|
||||
downloadBackup: (fileName) => `${backupBase}${fileName}/download`,
|
||||
importBackup: fileName => `${backupBase}${fileName}/import`,
|
||||
deleteBackup: fileName => `${backupBase}${fileName}/delete`,
|
||||
downloadBackup: fileName => `${backupBase}${fileName}/download`,
|
||||
};
|
||||
|
||||
export default {
|
||||
|
|
|
@ -69,10 +69,9 @@ export default {
|
|||
this.$emit("loading");
|
||||
let response = await api.backups.import(data.name, data);
|
||||
|
||||
let failed = response.data.failed;
|
||||
let succesful = response.data.successful;
|
||||
let importData = response.data;
|
||||
|
||||
this.$emit("finished", succesful, failed);
|
||||
this.$emit("finished", importData);
|
||||
},
|
||||
deleteBackup(data) {
|
||||
this.$emit("loading");
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
<template>
|
||||
<div>
|
||||
<v-data-table
|
||||
dense
|
||||
:headers="dataHeaders"
|
||||
:items="dataSet"
|
||||
item-key="name"
|
||||
class="elevation-1 mt-2"
|
||||
show-expand
|
||||
:expanded.sync="expanded"
|
||||
:footer-props="{
|
||||
'items-per-page-options': [100, 200, 300, 400, -1],
|
||||
}"
|
||||
:items-per-page="100"
|
||||
>
|
||||
<template v-slot:item.status="{ item }">
|
||||
<div :class="item.status ? 'success--text' : 'error--text'">
|
||||
{{ item.status ? "Imported" : "Failed" }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<template v-slot:expanded-item="{ headers, item }">
|
||||
<td :colspan="headers.length">
|
||||
<div class="ma-2">
|
||||
{{ item.exception }}
|
||||
</div>
|
||||
</td>
|
||||
</template>
|
||||
</v-data-table>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
props: {
|
||||
dataSet: Array,
|
||||
dataHeaders: Array,
|
||||
},
|
||||
data: () => ({
|
||||
singleExpand: false,
|
||||
expanded: [],
|
||||
}),
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -0,0 +1,152 @@
|
|||
<template>
|
||||
<div class="text-center">
|
||||
<v-dialog v-model="dialog" width="70%">
|
||||
<v-card>
|
||||
<v-card-title> Import Summary </v-card-title>
|
||||
<v-card-text>
|
||||
<v-row class="mb-n9">
|
||||
<v-card flat>
|
||||
<v-card-text>
|
||||
<div>
|
||||
<h3>Recipes</h3>
|
||||
</div>
|
||||
<div class="success--text">
|
||||
Success: {{ recipeNumbers.success }}
|
||||
</div>
|
||||
<div class="error--text">
|
||||
Failed: {{ recipeNumbers.failure }}
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-card flat>
|
||||
<v-card-text>
|
||||
<div>
|
||||
<h3>Themes</h3>
|
||||
</div>
|
||||
<div class="success--text">
|
||||
Success: {{ themeNumbers.success }}
|
||||
</div>
|
||||
<div class="error--text">
|
||||
Failed: {{ themeNumbers.failure }}
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
<v-card flat>
|
||||
<v-card-text>
|
||||
<div>
|
||||
<h3>Settings</h3>
|
||||
</div>
|
||||
<div class="success--text">
|
||||
Success: {{ settingsNumbers.success }}
|
||||
</div>
|
||||
<div class="error--text">
|
||||
Failed: {{ settingsNumbers.failure }}
|
||||
</div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-row>
|
||||
</v-card-text>
|
||||
<v-tabs v-model="tab">
|
||||
<v-tab>Recipes</v-tab>
|
||||
<v-tab>Themes</v-tab>
|
||||
<v-tab>Settings</v-tab>
|
||||
</v-tabs>
|
||||
<v-tabs-items v-model="tab">
|
||||
<v-tab-item>
|
||||
<v-card flat>
|
||||
<DataTable :data-headers="recipeHeaders" :data-set="recipeData" />
|
||||
</v-card>
|
||||
</v-tab-item>
|
||||
<v-tab-item>
|
||||
<v-card>
|
||||
<DataTable
|
||||
:data-headers="recipeHeaders"
|
||||
:data-set="themeData"
|
||||
/> </v-card
|
||||
></v-tab-item>
|
||||
<v-tab-item>
|
||||
<v-card
|
||||
><DataTable
|
||||
:data-headers="recipeHeaders"
|
||||
:data-set="settingsData"
|
||||
/>
|
||||
</v-card>
|
||||
</v-tab-item>
|
||||
</v-tabs-items>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import DataTable from "./DataTable";
|
||||
export default {
|
||||
components: {
|
||||
DataTable,
|
||||
},
|
||||
data: () => ({
|
||||
tab: null,
|
||||
dialog: false,
|
||||
recipeData: [],
|
||||
themeData: [],
|
||||
settingsData: [],
|
||||
recipeHeaders: [
|
||||
{
|
||||
text: "Status",
|
||||
value: "status",
|
||||
},
|
||||
{
|
||||
text: "Name",
|
||||
align: "start",
|
||||
sortable: true,
|
||||
value: "name",
|
||||
},
|
||||
|
||||
{ text: "Exception", value: "data-table-expand", align: "center" },
|
||||
],
|
||||
allDataTables: [],
|
||||
}),
|
||||
|
||||
computed: {
|
||||
recipeNumbers() {
|
||||
let numbers = { success: 0, failure: 0 };
|
||||
this.recipeData.forEach(element => {
|
||||
if (element.status) {
|
||||
numbers.success++;
|
||||
} else numbers.failure++;
|
||||
});
|
||||
return numbers;
|
||||
},
|
||||
settingsNumbers() {
|
||||
let numbers = { success: 0, failure: 0 };
|
||||
this.settingsData.forEach(element => {
|
||||
if (element.status) {
|
||||
numbers.success++;
|
||||
} else numbers.failure++;
|
||||
});
|
||||
return numbers;
|
||||
},
|
||||
themeNumbers() {
|
||||
let numbers = { success: 0, failure: 0 };
|
||||
this.themeData.forEach(element => {
|
||||
if (element.status) {
|
||||
numbers.success++;
|
||||
} else numbers.failure++;
|
||||
});
|
||||
return numbers;
|
||||
},
|
||||
},
|
||||
|
||||
methods: {
|
||||
open(importData) {
|
||||
this.recipeData = importData.recipeImports;
|
||||
this.themeData = importData.themeReport;
|
||||
this.settingsData = importData.settingsReport;
|
||||
this.dialog = true;
|
||||
},
|
||||
},
|
||||
};
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
|
@ -41,6 +41,7 @@
|
|||
:failed-header="$t('settings.backup.failed-imports')"
|
||||
:failed="failedImports"
|
||||
/>
|
||||
<ImportSummaryDialog ref="report" :import-data="importData" />
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
@ -48,6 +49,7 @@
|
|||
<script>
|
||||
import api from "@/api";
|
||||
import SuccessFailureAlert from "../../UI/SuccessFailureAlert";
|
||||
import ImportSummaryDialog from "./ImportSummaryDialog";
|
||||
import UploadBtn from "../../UI/UploadBtn";
|
||||
import AvailableBackupCard from "./AvailableBackupCard";
|
||||
import NewBackupCard from "./NewBackupCard";
|
||||
|
@ -58,6 +60,7 @@ export default {
|
|||
UploadBtn,
|
||||
AvailableBackupCard,
|
||||
NewBackupCard,
|
||||
ImportSummaryDialog,
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
|
@ -65,6 +68,7 @@ export default {
|
|||
successfulImports: [],
|
||||
backupLoading: false,
|
||||
availableBackups: [],
|
||||
importData: [],
|
||||
};
|
||||
},
|
||||
mounted() {
|
||||
|
@ -87,12 +91,10 @@ export default {
|
|||
this.backupLoading = false;
|
||||
}
|
||||
},
|
||||
processFinished(successful = null, failed = null) {
|
||||
processFinished(data) {
|
||||
this.getAvailableBackups();
|
||||
this.backupLoading = false;
|
||||
this.successfulImports = successful;
|
||||
this.failedImports = failed;
|
||||
this.$refs.report.open();
|
||||
this.$refs.report.open(data);
|
||||
},
|
||||
},
|
||||
};
|
||||
|
|
20
mealie/models/import_models.py
Normal file
20
mealie/models/import_models.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
from typing import Optional
|
||||
|
||||
from pydantic.main import BaseModel
|
||||
|
||||
|
||||
class RecipeImport(BaseModel):
|
||||
name: Optional[str]
|
||||
slug: str
|
||||
status: bool
|
||||
exception: Optional[str]
|
||||
|
||||
class ThemeImport(BaseModel):
|
||||
name: str
|
||||
status: bool
|
||||
exception: Optional[str]
|
||||
|
||||
class SettingsImport(BaseModel):
|
||||
name: str
|
||||
status: bool
|
||||
exception: Optional[str]
|
|
@ -1,12 +1,13 @@
|
|||
import json
|
||||
import shutil
|
||||
import zipfile
|
||||
from logging import error
|
||||
from logging import error, exception
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
from app_config import BACKUP_DIR, IMG_DIR, TEMP_DIR
|
||||
from db.database import db
|
||||
from models.import_models import RecipeImport, SettingsImport, ThemeImport
|
||||
from models.theme_models import SiteTheme
|
||||
from services.recipe_services import Recipe
|
||||
from services.settings_services import SiteSettings
|
||||
|
@ -57,23 +58,29 @@ class ImportDatabase:
|
|||
raise Exception("Import file does not exist")
|
||||
|
||||
def run(self):
|
||||
report = {}
|
||||
recipe_report = []
|
||||
settings_report = []
|
||||
theme_report = []
|
||||
if self.imp_recipes:
|
||||
report = self.import_recipes()
|
||||
recipe_report = self.import_recipes()
|
||||
if self.imp_settings:
|
||||
self.import_settings()
|
||||
settings_report = self.import_settings()
|
||||
if self.imp_themes:
|
||||
self.import_themes()
|
||||
theme_report = self.import_themes()
|
||||
|
||||
self.clean_up()
|
||||
|
||||
return report if report else None
|
||||
return {
|
||||
"recipeImports": recipe_report,
|
||||
"settingsReport": settings_report,
|
||||
"themeReport": theme_report,
|
||||
}
|
||||
|
||||
def import_recipes(self):
|
||||
recipe_dir: Path = self.import_dir.joinpath("recipes")
|
||||
|
||||
imports = []
|
||||
successful_imports = []
|
||||
failed_imports = []
|
||||
|
||||
for recipe in recipe_dir.glob("*.json"):
|
||||
with open(recipe, "r") as f:
|
||||
|
@ -82,16 +89,27 @@ class ImportDatabase:
|
|||
try:
|
||||
recipe_obj = Recipe(**recipe_dict)
|
||||
recipe_obj.save_to_db(self.session)
|
||||
import_status = RecipeImport(
|
||||
name=recipe_obj.name, slug=recipe_obj.slug, status=True
|
||||
)
|
||||
imports.append(import_status)
|
||||
successful_imports.append(recipe.stem)
|
||||
logger.info(f"Imported: {recipe.stem}")
|
||||
|
||||
except Exception as inst:
|
||||
logger.error(inst)
|
||||
logger.info(f"Failed Import: {recipe.stem}")
|
||||
failed_imports.append(recipe.stem)
|
||||
import_status = RecipeImport(
|
||||
name=recipe.stem,
|
||||
slug=recipe.stem,
|
||||
status=False,
|
||||
exception=str(inst),
|
||||
)
|
||||
imports.append(import_status)
|
||||
|
||||
self._import_images(successful_imports)
|
||||
|
||||
return {"successful": successful_imports, "failed": failed_imports}
|
||||
return imports
|
||||
|
||||
@staticmethod
|
||||
def _recipe_migration(recipe_dict: dict) -> dict:
|
||||
|
@ -130,7 +148,7 @@ class ImportDatabase:
|
|||
|
||||
def import_themes(self):
|
||||
themes_file = self.import_dir.joinpath("themes", "themes.json")
|
||||
|
||||
theme_imports = []
|
||||
with open(themes_file, "r") as f:
|
||||
themes: list[dict] = json.loads(f.read())
|
||||
for theme in themes:
|
||||
|
@ -138,17 +156,38 @@ class ImportDatabase:
|
|||
continue
|
||||
new_theme = SiteTheme(**theme)
|
||||
try:
|
||||
|
||||
db.themes.create(self.session, new_theme.dict())
|
||||
except:
|
||||
theme_imports.append(ThemeImport(name=new_theme.name, status=True))
|
||||
except Exception as inst:
|
||||
logger.info(f"Unable Import Theme {new_theme.name}")
|
||||
theme_imports.append(
|
||||
ThemeImport(name=new_theme.name, status=False, exception=str(inst))
|
||||
)
|
||||
|
||||
return theme_imports
|
||||
|
||||
def import_settings(self):
|
||||
settings_file = self.import_dir.joinpath("settings", "settings.json")
|
||||
settings_imports = []
|
||||
|
||||
with open(settings_file, "r") as f:
|
||||
settings: dict = json.loads(f.read())
|
||||
|
||||
db.settings.update(self.session, settings.get("name"), settings)
|
||||
name = settings.get("name")
|
||||
|
||||
try:
|
||||
db.settings.update(self.session, name, settings)
|
||||
import_status = SettingsImport(name=name, status=True)
|
||||
|
||||
except Exception as inst:
|
||||
import_status = SettingsImport(
|
||||
name=name, status=False, exception=str(inst)
|
||||
)
|
||||
|
||||
settings_imports.append(import_status)
|
||||
|
||||
return settings_imports
|
||||
|
||||
def clean_up(self):
|
||||
shutil.rmtree(TEMP_DIR)
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue