95c821deb2
Add detailed [DEBUG] logging to every controller module when logging.level is set to "debug". Each module with stateful debug uses SetDebug(bool) wired from main.go. Covers stacks, backup, cloudflare, integrations, system, monitor, settings, scheduler, web handlers, storage, metrics, API, selfupdate, and assets. Also includes the app export/import (.fab bundles) feature from v0.32.0 and its debug page integration. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
288 lines
11 KiB
HTML
288 lines
11 KiB
HTML
{{define "app_import"}}
|
|
{{template "layout_start" .}}
|
|
|
|
<div class="page-header">
|
|
<div style="display:flex;align-items:center;gap:1rem">
|
|
<a href="/stacks" class="btn btn-sm btn-outline">← Alkalmazások</a>
|
|
<h2>Alkalmazás importálás</h2>
|
|
</div>
|
|
</div>
|
|
|
|
{{if not .Bundles}}
|
|
<div class="card" style="max-width:700px">
|
|
<p style="color:var(--text-muted)">Nem található .fab csomag a regisztrált tárolókon.</p>
|
|
<p style="color:var(--text-muted);font-size:.85rem">Exportálj egy alkalmazást az alkalmazás oldaláról, vagy másolj egy .fab fájlt a <code>{tároló}/felhom-data/exports/</code> könyvtárba.</p>
|
|
</div>
|
|
{{else}}
|
|
<div class="card" style="max-width:900px">
|
|
<table class="table" style="width:100%">
|
|
<thead>
|
|
<tr>
|
|
<th>Alkalmazás</th>
|
|
<th>Dátum</th>
|
|
<th>Méret</th>
|
|
<th>Tároló</th>
|
|
<th>Titkos</th>
|
|
<th></th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{{range .Bundles}}
|
|
<tr>
|
|
<td><strong>{{if .DisplayName}}{{.DisplayName}}{{else}}{{.AppName}}{{end}}</strong></td>
|
|
<td>{{.ExportedAt}}</td>
|
|
<td>{{.SizeHuman}}</td>
|
|
<td>{{if .DriveLabel}}{{.DriveLabel}}{{else}}{{.DrivePath}}{{end}}</td>
|
|
<td>{{if .Encrypted}}🔒{{end}}</td>
|
|
<td><button class="btn btn-sm btn-outline" onclick="showPreview('{{.Path}}', {{.Encrypted}})">Részletek »</button></td>
|
|
</tr>
|
|
{{end}}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
{{end}}
|
|
|
|
<!-- Preview / password modal -->
|
|
<div id="previewCard" class="card" style="max-width:700px;display:none">
|
|
<h3 id="previewTitle">Csomag részletei</h3>
|
|
|
|
<div id="passwordPrompt" style="display:none;margin-bottom:1rem">
|
|
<p>Ez a csomag jelszóval védett. Kérlek add meg a jelszót:</p>
|
|
<div style="display:flex;gap:.5rem">
|
|
<input type="password" id="importPassword" placeholder="Jelszó" style="flex:1;padding:.5rem">
|
|
<button class="btn btn-primary" onclick="loadManifest()">Megnyitás</button>
|
|
</div>
|
|
<div id="passwordError" style="display:none;color:var(--danger);margin-top:.5rem"></div>
|
|
</div>
|
|
|
|
<div id="manifestInfo" style="display:none">
|
|
<div style="background:var(--bg-secondary);border-radius:8px;padding:1rem;margin-bottom:1rem">
|
|
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
|
|
<span>Alkalmazás:</span>
|
|
<strong id="mfName">-</strong>
|
|
</div>
|
|
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
|
|
<span>Exportálva:</span>
|
|
<span id="mfDate">-</span>
|
|
</div>
|
|
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
|
|
<span>Controller verzió:</span>
|
|
<span id="mfVersion">-</span>
|
|
</div>
|
|
<div id="mfDBRow" style="display:flex;justify-content:space-between;margin-bottom:.25rem">
|
|
<span>Adatbázis:</span>
|
|
<span id="mfDB">-</span>
|
|
</div>
|
|
<div style="display:flex;justify-content:space-between;margin-bottom:.25rem">
|
|
<span>Adatok:</span>
|
|
<span id="mfDataType">-</span>
|
|
</div>
|
|
<div style="display:flex;justify-content:space-between">
|
|
<span>Méret:</span>
|
|
<span id="mfSize">-</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div id="overwriteWarning" style="display:none;background:var(--warning-bg, #fff3cd);border:1px solid var(--warning-border, #ffc107);border-radius:8px;padding:1rem;margin-bottom:1rem">
|
|
<strong>⚠ FIGYELEM:</strong> A meglévő <span id="overwriteAppName"></span> alkalmazás konfigurációja és összes adata felül lesz írva!
|
|
</div>
|
|
|
|
<button id="importBtn" class="btn btn-primary" onclick="startImport()" style="width:100%">
|
|
Visszaállítás indítása
|
|
</button>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Progress -->
|
|
<div id="progressCard" class="card" style="max-width:700px;display:none">
|
|
<h3>Importálás folyamata</h3>
|
|
<div id="progressSteps"></div>
|
|
<div id="progressError" style="display:none;color:var(--danger);margin-top:1rem;font-weight:600"></div>
|
|
</div>
|
|
|
|
<div id="doneCard" class="card" style="max-width:700px;display:none">
|
|
<h3 style="color:var(--success)">Importálás kész!</h3>
|
|
<p>Az alkalmazás sikeresen visszaállítva.</p>
|
|
<a id="doneLink" href="/stacks" class="btn btn-primary">Alkalmazások megtekintése</a>
|
|
</div>
|
|
|
|
<script>
|
|
var selectedPath = '';
|
|
var selectedEncrypted = false;
|
|
var selectedManifest = null;
|
|
var pollTimer = null;
|
|
|
|
function csrfH() {
|
|
var el = document.querySelector('meta[name="csrf-token"]');
|
|
return el ? {'X-CSRF-Token': el.content, 'Content-Type': 'application/json'} : {'Content-Type': 'application/json'};
|
|
}
|
|
|
|
function showPreview(path, encrypted) {
|
|
selectedPath = path;
|
|
selectedEncrypted = encrypted;
|
|
selectedManifest = null;
|
|
|
|
document.getElementById('previewCard').style.display = 'block';
|
|
document.getElementById('manifestInfo').style.display = 'none';
|
|
document.getElementById('passwordPrompt').style.display = 'none';
|
|
document.getElementById('passwordError').style.display = 'none';
|
|
document.getElementById('progressCard').style.display = 'none';
|
|
document.getElementById('doneCard').style.display = 'none';
|
|
|
|
if (encrypted) {
|
|
document.getElementById('passwordPrompt').style.display = 'block';
|
|
document.getElementById('importPassword').value = '';
|
|
document.getElementById('importPassword').focus();
|
|
} else {
|
|
loadManifest();
|
|
}
|
|
}
|
|
|
|
async function loadManifest() {
|
|
var password = selectedEncrypted ? document.getElementById('importPassword').value : '';
|
|
|
|
try {
|
|
var resp = await fetch('/api/export/manifest', {
|
|
method: 'POST',
|
|
headers: csrfH(),
|
|
body: JSON.stringify({path: selectedPath, password: password})
|
|
});
|
|
var data = await resp.json();
|
|
|
|
if (!data.ok) {
|
|
if (selectedEncrypted) {
|
|
document.getElementById('passwordError').textContent = data.error || 'Hibás jelszó';
|
|
document.getElementById('passwordError').style.display = 'block';
|
|
}
|
|
return;
|
|
}
|
|
|
|
if (data.needs_password) {
|
|
document.getElementById('passwordPrompt').style.display = 'block';
|
|
document.getElementById('importPassword').focus();
|
|
return;
|
|
}
|
|
|
|
selectedManifest = data.manifest;
|
|
showManifest(data.manifest);
|
|
} catch(e) {
|
|
console.error('Manifest error:', e);
|
|
}
|
|
}
|
|
|
|
function showManifest(m) {
|
|
document.getElementById('passwordPrompt').style.display = 'none';
|
|
document.getElementById('manifestInfo').style.display = 'block';
|
|
|
|
document.getElementById('previewTitle').textContent = (m.display_name || m.app_name) + ' — Csomag részletei';
|
|
document.getElementById('mfName').textContent = m.display_name || m.app_name;
|
|
document.getElementById('mfDate').textContent = m.exported_at ? new Date(m.exported_at).toLocaleString('hu-HU') : '-';
|
|
document.getElementById('mfVersion').textContent = m.controller_version || '-';
|
|
|
|
if (m.has_database && m.db_type) {
|
|
document.getElementById('mfDB').textContent = m.db_type;
|
|
document.getElementById('mfDBRow').style.display = 'flex';
|
|
} else {
|
|
document.getElementById('mfDBRow').style.display = 'none';
|
|
}
|
|
|
|
var dataType = [];
|
|
if (m.has_hdd_data) dataType.push('HDD');
|
|
if (m.has_volume_data) dataType.push('Docker volume');
|
|
document.getElementById('mfDataType').textContent = dataType.length ? dataType.join(', ') : 'Nincs';
|
|
|
|
// Format size
|
|
var bytes = m.total_size_bytes || 0;
|
|
var sizeStr = bytes > 1073741824 ? (bytes / 1073741824).toFixed(1) + ' GB' :
|
|
bytes > 1048576 ? (bytes / 1048576).toFixed(1) + ' MB' :
|
|
bytes > 1024 ? (bytes / 1024).toFixed(1) + ' KB' : bytes + ' B';
|
|
document.getElementById('mfSize').textContent = sizeStr;
|
|
|
|
// Check if app already exists — show overwrite warning
|
|
// We detect this by checking if the app link exists in the nav
|
|
var warn = document.getElementById('overwriteWarning');
|
|
// Simple approach: always show warning for existing app names
|
|
document.getElementById('overwriteAppName').textContent = m.display_name || m.app_name;
|
|
warn.style.display = 'block';
|
|
}
|
|
|
|
async function startImport() {
|
|
document.getElementById('importBtn').disabled = true;
|
|
document.getElementById('progressCard').style.display = 'block';
|
|
document.getElementById('doneCard').style.display = 'none';
|
|
|
|
var password = selectedEncrypted ? document.getElementById('importPassword').value : '';
|
|
|
|
try {
|
|
var resp = await fetch('/api/export/import', {
|
|
method: 'POST',
|
|
headers: csrfH(),
|
|
body: JSON.stringify({path: selectedPath, password: password})
|
|
});
|
|
var data = await resp.json();
|
|
if (!data.ok) {
|
|
showError(data.error || 'Hiba történt');
|
|
return;
|
|
}
|
|
pollStatus();
|
|
} catch(e) {
|
|
showError(e.message);
|
|
}
|
|
}
|
|
|
|
function pollStatus() {
|
|
if (pollTimer) clearInterval(pollTimer);
|
|
pollTimer = setInterval(async function() {
|
|
try {
|
|
var resp = await fetch('/api/export/import/status');
|
|
var data = await resp.json();
|
|
renderSteps(data.steps || []);
|
|
|
|
if (data.error) {
|
|
showError(data.error);
|
|
clearInterval(pollTimer);
|
|
return;
|
|
}
|
|
|
|
if (data.done && !data.error) {
|
|
clearInterval(pollTimer);
|
|
document.getElementById('progressCard').style.display = 'none';
|
|
document.getElementById('doneCard').style.display = 'block';
|
|
if (data.stack_name) {
|
|
document.getElementById('doneLink').href = '/stacks/' + data.stack_name + '/deploy';
|
|
document.getElementById('doneLink').textContent = 'Alkalmazás megtekintése';
|
|
}
|
|
}
|
|
} catch(e) {
|
|
console.error('Poll error:', e);
|
|
}
|
|
}, 1000);
|
|
}
|
|
|
|
function renderSteps(steps) {
|
|
var html = '';
|
|
for (var i = 0; i < steps.length; i++) {
|
|
var s = steps[i];
|
|
var icon = '○';
|
|
if (s.status === 'running') icon = '⟳';
|
|
if (s.status === 'done') icon = '✓';
|
|
if (s.status === 'failed') icon = '✗';
|
|
var cls = s.status === 'failed' ? 'color:var(--danger)' : s.status === 'done' ? 'color:var(--success)' : s.status === 'running' ? 'color:var(--primary)' : '';
|
|
html += '<div style="padding:.25rem 0;' + cls + '">' + icon + ' ' + s.label;
|
|
if (s.error) html += ' <span style="font-size:.85rem">(' + s.error + ')</span>';
|
|
html += '</div>';
|
|
}
|
|
document.getElementById('progressSteps').innerHTML = html;
|
|
}
|
|
|
|
function showError(msg) {
|
|
var el = document.getElementById('progressError');
|
|
el.textContent = msg;
|
|
el.style.display = 'block';
|
|
document.getElementById('importBtn').disabled = false;
|
|
}
|
|
</script>
|
|
|
|
{{template "layout_end" .}}
|
|
{{end}}
|