feat: Tandoor integration — settings, test connection, import, duplicate detection
Add TandoorClient (app/tandoor.py) with full recipe creation, image upload, and duplicate detection via the Tandoor REST API. Settings page now has separate Mealie and Tandoor sections. Import page shows both send buttons based on which services are configured. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
+68
-18
@@ -180,9 +180,16 @@
|
||||
<button class="add-btn mt-1 mb-2" onclick="addInstruction('')">+ Lépés hozzáadása</button>
|
||||
|
||||
<div class="flex mt-2">
|
||||
<button class="btn btn-success" id="sendBtn" onclick="sendToMealie()">
|
||||
{% if has_mealie %}
|
||||
<button class="btn btn-success" id="sendMealieBtn" onclick="sendToMealie()">
|
||||
Importálás Mealie-be
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if has_tandoor %}
|
||||
<button class="btn btn-success" id="sendTandoorBtn" onclick="sendToTandoor()">
|
||||
Importálás Tandoor-ba
|
||||
</button>
|
||||
{% endif %}
|
||||
<span id="sendStatus"></span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -192,11 +199,7 @@
|
||||
<div class="result-card" id="resultCard">
|
||||
<div class="card">
|
||||
<h2 class="text-success">Recept sikeresen importálva!</h2>
|
||||
<p class="mt-1">
|
||||
<a id="resultLink" href="#" target="_blank" style="color:var(--accent);">
|
||||
Megnyitás Mealie-ben →
|
||||
</a>
|
||||
</p>
|
||||
<p class="mt-1" id="resultLinks"></p>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -216,6 +219,7 @@ async function scrapeRecipe() {
|
||||
|
||||
document.getElementById('previewCard').classList.remove('visible');
|
||||
document.getElementById('resultCard').classList.remove('visible');
|
||||
Object.keys(importedLinks).forEach(k => delete importedLinks[k]);
|
||||
|
||||
try {
|
||||
const form = new FormData();
|
||||
@@ -233,10 +237,17 @@ async function scrapeRecipe() {
|
||||
populatePreview(currentRecipe);
|
||||
document.getElementById('previewCard').classList.add('visible');
|
||||
|
||||
const warnings = [];
|
||||
if (data.duplicate) {
|
||||
status.innerHTML = '<span class="text-warning">⚠ Ez a recept már létezik Mealie-ben: '
|
||||
+ '<a href="' + escHtml(data.duplicate.url) + '" target="_blank" style="color:var(--accent)">'
|
||||
+ escHtml(data.duplicate.name) + '</a></span>';
|
||||
warnings.push('Mealie: <a href="' + escHtml(data.duplicate.url) + '" target="_blank" style="color:var(--accent)">'
|
||||
+ escHtml(data.duplicate.name) + '</a>');
|
||||
}
|
||||
if (data.tandoor_duplicate) {
|
||||
warnings.push('Tandoor: <a href="' + escHtml(data.tandoor_duplicate.url) + '" target="_blank" style="color:var(--accent)">'
|
||||
+ escHtml(data.tandoor_duplicate.name) + '</a>');
|
||||
}
|
||||
if (warnings.length > 0) {
|
||||
status.innerHTML = '<span class="text-warning">⚠ Ez a recept már létezik: ' + warnings.join(' | ') + '</span>';
|
||||
} else {
|
||||
status.innerHTML = '<span class="text-success">✓ Beolvasva</span>';
|
||||
}
|
||||
@@ -351,17 +362,16 @@ function gatherRecipe() {
|
||||
};
|
||||
}
|
||||
|
||||
const importedLinks = {};
|
||||
|
||||
async function sendToMealie() {
|
||||
const recipe = gatherRecipe();
|
||||
if (!recipe.title) {
|
||||
alert('A recept neve kötelező!');
|
||||
return;
|
||||
}
|
||||
if (!recipe.title) { alert('A recept neve kötelező!'); return; }
|
||||
|
||||
const btn = document.getElementById('sendBtn');
|
||||
const btn = document.getElementById('sendMealieBtn');
|
||||
const status = document.getElementById('sendStatus');
|
||||
btn.disabled = true;
|
||||
status.innerHTML = '<span class="spinner"></span> Importálás...';
|
||||
status.innerHTML = '<span class="spinner"></span> Importálás Mealie-be...';
|
||||
|
||||
try {
|
||||
const resp = await fetch('/send', {
|
||||
@@ -377,15 +387,55 @@ async function sendToMealie() {
|
||||
return;
|
||||
}
|
||||
|
||||
status.innerHTML = '';
|
||||
document.getElementById('resultLink').href = data.url;
|
||||
document.getElementById('resultCard').classList.add('visible');
|
||||
status.innerHTML = '<span class="text-success">✓ Mealie kész</span>';
|
||||
importedLinks['Mealie'] = data.url;
|
||||
showResultCard();
|
||||
} catch (e) {
|
||||
status.innerHTML = '<span class="text-danger">Hálózati hiba: ' + e.message + '</span>';
|
||||
}
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
async function sendToTandoor() {
|
||||
const recipe = gatherRecipe();
|
||||
if (!recipe.title) { alert('A recept neve kötelező!'); return; }
|
||||
|
||||
const btn = document.getElementById('sendTandoorBtn');
|
||||
const status = document.getElementById('sendStatus');
|
||||
btn.disabled = true;
|
||||
status.innerHTML = '<span class="spinner"></span> Importálás Tandoor-ba...';
|
||||
|
||||
try {
|
||||
const resp = await fetch('/send-tandoor', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(recipe),
|
||||
});
|
||||
const data = await resp.json();
|
||||
|
||||
if (!data.ok) {
|
||||
status.innerHTML = '<span class="text-danger">Hiba: ' + data.error + '</span>';
|
||||
btn.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
status.innerHTML = '<span class="text-success">✓ Tandoor kész</span>';
|
||||
importedLinks['Tandoor'] = data.url;
|
||||
showResultCard();
|
||||
} catch (e) {
|
||||
status.innerHTML = '<span class="text-danger">Hálózati hiba: ' + e.message + '</span>';
|
||||
}
|
||||
btn.disabled = false;
|
||||
}
|
||||
|
||||
function showResultCard() {
|
||||
const links = Object.entries(importedLinks).map(([name, url]) =>
|
||||
'<a href="' + escHtml(url) + '" target="_blank" style="color:var(--accent);">Megnyitás ' + name + '-ben →</a>'
|
||||
).join('<br>');
|
||||
document.getElementById('resultLinks').innerHTML = links;
|
||||
document.getElementById('resultCard').classList.add('visible');
|
||||
}
|
||||
|
||||
function escHtml(s) {
|
||||
const d = document.createElement('div');
|
||||
d.textContent = s;
|
||||
|
||||
Reference in New Issue
Block a user