Fix JS syntax errors: convert qq{} to heredocs in restore.live.cgi
Perl's qq{} delimiter matches balanced braces, which conflicted with
JavaScript curly braces, producing empty function bodies. Converted
_print_step1_js and _print_step2_js to heredoc blocks.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -232,83 +232,85 @@ sub handle_step1 {
|
||||
|
||||
sub _print_step1_js {
|
||||
my ($esc_type, $next_step) = @_;
|
||||
print qq{<script>\n};
|
||||
print qq{var gnizaType = '$esc_type';\n};
|
||||
print qq{var gnizaNextStep = '$next_step';\n};
|
||||
print qq{\n};
|
||||
print qq{function gnizaLoadSnapshots() {\n};
|
||||
print qq{ var remote = document.getElementById('remote').value;\n};
|
||||
print qq{ var sel = document.getElementById('timestamp');\n};
|
||||
print qq{ var btn = document.getElementById('next-btn');\n};
|
||||
print qq{\n};
|
||||
print qq{ if (!remote) {\n};
|
||||
print qq{ _setSelectPlaceholder(sel, '-- Select remote first --');\n};
|
||||
print qq{ sel.disabled = true;\n};
|
||||
print qq{ btn.disabled = true;\n};
|
||||
print qq{ return;\n};
|
||||
print qq{ }\n};
|
||||
print qq{\n};
|
||||
print qq{ _setSelectPlaceholder(sel, 'Loading...');\n};
|
||||
print qq{ sel.disabled = true;\n};
|
||||
print qq{ btn.disabled = true;\n};
|
||||
print qq{\n};
|
||||
print qq{ var url = 'restore.live.cgi?step=fetch_snapshots&remote=' + encodeURIComponent(remote);\n};
|
||||
print qq{ var xhr = new XMLHttpRequest();\n};
|
||||
print qq{ xhr.open('GET', url, true);\n};
|
||||
print qq{ xhr.onreadystatechange = function() {\n};
|
||||
print qq{ if (xhr.readyState !== 4) return;\n};
|
||||
print qq{ if (xhr.status === 200) {\n};
|
||||
print qq{ try {\n};
|
||||
print qq{ var data = JSON.parse(xhr.responseText);\n};
|
||||
print qq{ if (data.error) {\n};
|
||||
print qq{ _setSelectPlaceholder(sel, 'Error: ' + data.error);\n};
|
||||
print qq{ } else if (data.snapshots && data.snapshots.length > 0) {\n};
|
||||
print qq{ _populateSelect(sel, data.snapshots);\n};
|
||||
print qq{ sel.disabled = false;\n};
|
||||
print qq{ btn.disabled = false;\n};
|
||||
print qq{ } else {\n};
|
||||
print qq{ _setSelectPlaceholder(sel, 'No snapshots found');\n};
|
||||
print qq{ }\n};
|
||||
print qq{ } catch(e) {\n};
|
||||
print qq{ _setSelectPlaceholder(sel, 'Failed to parse response');\n};
|
||||
print qq{ }\n};
|
||||
print qq{ } else {\n};
|
||||
print qq{ _setSelectPlaceholder(sel, 'Request failed');\n};
|
||||
print qq{ }\n};
|
||||
print qq{ };\n};
|
||||
print qq{ xhr.send();\n};
|
||||
print qq{}\n};
|
||||
print qq{\n};
|
||||
print qq{function _setSelectPlaceholder(sel, text) {\n};
|
||||
print qq{ while (sel.options.length) sel.remove(0);\n};
|
||||
print qq{ var opt = document.createElement('option');\n};
|
||||
print qq{ opt.value = '';\n};
|
||||
print qq{ opt.textContent = text;\n};
|
||||
print qq{ sel.appendChild(opt);\n};
|
||||
print qq{}\n};
|
||||
print qq{\n};
|
||||
print qq{function _populateSelect(sel, values) {\n};
|
||||
print qq{ while (sel.options.length) sel.remove(0);\n};
|
||||
print qq{ for (var i = 0; i < values.length; i++) {\n};
|
||||
print qq{ var opt = document.createElement('option');\n};
|
||||
print qq{ opt.value = values[i];\n};
|
||||
print qq{ opt.textContent = values[i];\n};
|
||||
print qq{ sel.appendChild(opt);\n};
|
||||
print qq{ }\n};
|
||||
print qq{}\n};
|
||||
print qq{\n};
|
||||
print qq{function gnizaGoNext() {\n};
|
||||
print qq{ var remote = document.getElementById('remote').value;\n};
|
||||
print qq{ var timestamp = document.getElementById('timestamp').value;\n};
|
||||
print qq{ if (!remote || !timestamp) return;\n};
|
||||
print qq{\n};
|
||||
print qq{ var url = 'restore.live.cgi?step=' + gnizaNextStep\n};
|
||||
print qq{ + '&type=' + encodeURIComponent(gnizaType)\n};
|
||||
print qq{ + '&remote=' + encodeURIComponent(remote)\n};
|
||||
print qq{ + '×tamp=' + encodeURIComponent(timestamp);\n};
|
||||
print qq{ window.location.href = url;\n};
|
||||
print qq{}\n};
|
||||
print qq{</script>\n};
|
||||
print <<"END_JS";
|
||||
<script>
|
||||
var gnizaType = '$esc_type';
|
||||
var gnizaNextStep = '$next_step';
|
||||
|
||||
function gnizaLoadSnapshots() {
|
||||
var remote = document.getElementById('remote').value;
|
||||
var sel = document.getElementById('timestamp');
|
||||
var btn = document.getElementById('next-btn');
|
||||
|
||||
if (!remote) {
|
||||
_setSelectPlaceholder(sel, '-- Select remote first --');
|
||||
sel.disabled = true;
|
||||
btn.disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
_setSelectPlaceholder(sel, 'Loading...');
|
||||
sel.disabled = true;
|
||||
btn.disabled = true;
|
||||
|
||||
var url = 'restore.live.cgi?step=fetch_snapshots&remote=' + encodeURIComponent(remote);
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState !== 4) return;
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
if (data.error) {
|
||||
_setSelectPlaceholder(sel, 'Error: ' + data.error);
|
||||
} else if (data.snapshots && data.snapshots.length > 0) {
|
||||
_populateSelect(sel, data.snapshots);
|
||||
sel.disabled = false;
|
||||
btn.disabled = false;
|
||||
} else {
|
||||
_setSelectPlaceholder(sel, 'No snapshots found');
|
||||
}
|
||||
} catch(e) {
|
||||
_setSelectPlaceholder(sel, 'Failed to parse response');
|
||||
}
|
||||
} else {
|
||||
_setSelectPlaceholder(sel, 'Request failed');
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function _setSelectPlaceholder(sel, text) {
|
||||
while (sel.options.length) sel.remove(0);
|
||||
var opt = document.createElement('option');
|
||||
opt.value = '';
|
||||
opt.textContent = text;
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
|
||||
function _populateSelect(sel, values) {
|
||||
while (sel.options.length) sel.remove(0);
|
||||
for (var i = 0; i < values.length; i++) {
|
||||
var opt = document.createElement('option');
|
||||
opt.value = values[i];
|
||||
opt.textContent = values[i];
|
||||
sel.appendChild(opt);
|
||||
}
|
||||
}
|
||||
|
||||
function gnizaGoNext() {
|
||||
var remote = document.getElementById('remote').value;
|
||||
var timestamp = document.getElementById('timestamp').value;
|
||||
if (!remote || !timestamp) return;
|
||||
|
||||
var url = 'restore.live.cgi?step=' + gnizaNextStep
|
||||
+ '&type=' + encodeURIComponent(gnizaType)
|
||||
+ '&remote=' + encodeURIComponent(remote)
|
||||
+ '×tamp=' + encodeURIComponent(timestamp);
|
||||
window.location.href = url;
|
||||
}
|
||||
</script>
|
||||
END_JS
|
||||
}
|
||||
|
||||
# ── Step 2: Select specific item ─────────────────────────────
|
||||
@@ -409,280 +411,282 @@ sub _render_file_picker {
|
||||
|
||||
sub _print_step2_js {
|
||||
my ($esc_type, $esc_remote, $esc_timestamp) = @_;
|
||||
print qq{<script>\n};
|
||||
print qq{var gnizaType = '$esc_type';\n};
|
||||
print qq{var gnizaRemote = '$esc_remote';\n};
|
||||
print qq{var gnizaTimestamp = '$esc_timestamp';\n};
|
||||
print qq{var fbCache = {};\n};
|
||||
print qq{var fbSelected = '';\n};
|
||||
print qq{\n};
|
||||
print qq{(function() {\n};
|
||||
print qq{ var listTypes = ['database','dbusers','mailbox','domains','ssl','cron'];\n};
|
||||
print qq{ if (listTypes.indexOf(gnizaType) >= 0) {\n};
|
||||
print qq{ loadOptions();\n};
|
||||
print qq{ }\n};
|
||||
print qq{})();\n};
|
||||
print qq{\n};
|
||||
print qq{function loadOptions() {\n};
|
||||
print qq{ var url = 'restore.live.cgi?step=fetch_options'\n};
|
||||
print qq{ + '&remote=' + encodeURIComponent(gnizaRemote)\n};
|
||||
print qq{ + '×tamp=' + encodeURIComponent(gnizaTimestamp)\n};
|
||||
print qq{ + '&type=' + encodeURIComponent(gnizaType);\n};
|
||||
print qq{\n};
|
||||
print qq{ var xhr = new XMLHttpRequest();\n};
|
||||
print qq{ xhr.open('GET', url, true);\n};
|
||||
print qq{ xhr.onreadystatechange = function() {\n};
|
||||
print qq{ if (xhr.readyState !== 4) return;\n};
|
||||
print qq{ var container = document.getElementById('item-list');\n};
|
||||
print qq{ if (xhr.status === 200) {\n};
|
||||
print qq{ try {\n};
|
||||
print qq{ var data = JSON.parse(xhr.responseText);\n};
|
||||
print qq{ if (data.error) {\n};
|
||||
print qq{ container.textContent = 'Error: ' + data.error;\n};
|
||||
print qq{ } else if (gnizaType === 'cron') {\n};
|
||||
print qq{ populatePreview(container, data.options);\n};
|
||||
print qq{ } else {\n};
|
||||
print qq{ populateChecklist(container, data.options);\n};
|
||||
print qq{ }\n};
|
||||
print qq{ } catch(e) {\n};
|
||||
print qq{ container.textContent = 'Failed to parse response';\n};
|
||||
print qq{ }\n};
|
||||
print qq{ } else {\n};
|
||||
print qq{ container.textContent = 'Request failed';\n};
|
||||
print qq{ }\n};
|
||||
print qq{ };\n};
|
||||
print qq{ xhr.send();\n};
|
||||
print qq{}\n};
|
||||
print qq{\n};
|
||||
print qq{function populateChecklist(container, options) {\n};
|
||||
print qq{ var hidden = document.getElementById('selected_items');\n};
|
||||
print qq{ container.textContent = '';\n};
|
||||
print qq{ if (!options || options.length === 0) {\n};
|
||||
print qq{ container.textContent = '(none found)';\n};
|
||||
print qq{ return;\n};
|
||||
print qq{ }\n};
|
||||
print qq{\n};
|
||||
print qq{ var allLabels = {database:'All Databases',dbusers:'All Database Users',mailbox:'All Mailboxes',domains:'All Domains',ssl:'All Certificates'};\n};
|
||||
print qq{ var allLabel = allLabels[gnizaType] || 'All';\n};
|
||||
print qq{\n};
|
||||
print qq{ var allRow = _makeCheckRow(allLabel, '', true);\n};
|
||||
print qq{ allRow.querySelector('input').setAttribute('data-all', '1');\n};
|
||||
print qq{ allRow.querySelector('input').onchange = function() { toggleAll(this.checked); };\n};
|
||||
print qq{ allRow.querySelector('span').className = 'text-sm font-semibold';\n};
|
||||
print qq{ container.appendChild(allRow);\n};
|
||||
print qq{\n};
|
||||
print qq{ for (var i = 0; i < options.length; i++) {\n};
|
||||
print qq{ var row = _makeCheckRow(options[i], options[i], false);\n};
|
||||
print qq{ row.querySelector('input').setAttribute('data-item', '1');\n};
|
||||
print qq{ row.querySelector('input').onchange = function() { syncHidden(); };\n};
|
||||
print qq{ container.appendChild(row);\n};
|
||||
print qq{ }\n};
|
||||
print qq{}\n};
|
||||
print qq{\n};
|
||||
print qq{function _makeCheckRow(labelText, value, isAll) {\n};
|
||||
print qq{ var label = document.createElement('label');\n};
|
||||
print qq{ label.className = 'flex items-center gap-2 cursor-pointer';\n};
|
||||
print qq{ var cb = document.createElement('input');\n};
|
||||
print qq{ cb.type = 'checkbox';\n};
|
||||
print qq{ cb.className = 'checkbox checkbox-sm';\n};
|
||||
print qq{ if (value) cb.value = value;\n};
|
||||
print qq{ var span = document.createElement('span');\n};
|
||||
print qq{ span.className = 'text-sm';\n};
|
||||
print qq{ span.textContent = labelText;\n};
|
||||
print qq{ label.appendChild(cb);\n};
|
||||
print qq{ label.appendChild(span);\n};
|
||||
print qq{ return label;\n};
|
||||
print qq{}\n};
|
||||
print qq{\n};
|
||||
print qq{function toggleAll(checked) {\n};
|
||||
print qq{ var container = document.getElementById('item-list');\n};
|
||||
print qq{ var hidden = document.getElementById('selected_items');\n};
|
||||
print qq{ var items = container.querySelectorAll('input[data-item]');\n};
|
||||
print qq{ for (var i = 0; i < items.length; i++) {\n};
|
||||
print qq{ items[i].disabled = checked;\n};
|
||||
print qq{ if (checked) items[i].checked = false;\n};
|
||||
print qq{ }\n};
|
||||
print qq{ hidden.value = checked ? '__ALL__' : '';\n};
|
||||
print qq{}\n};
|
||||
print qq{\n};
|
||||
print qq{function syncHidden() {\n};
|
||||
print qq{ var container = document.getElementById('item-list');\n};
|
||||
print qq{ var hidden = document.getElementById('selected_items');\n};
|
||||
print qq{ var items = container.querySelectorAll('input[data-item]:checked');\n};
|
||||
print qq{ var vals = [];\n};
|
||||
print qq{ for (var i = 0; i < items.length; i++) {\n};
|
||||
print qq{ vals.push(items[i].value);\n};
|
||||
print qq{ }\n};
|
||||
print qq{ hidden.value = vals.join(',');\n};
|
||||
print qq{}\n};
|
||||
print qq{\n};
|
||||
print qq{function populatePreview(container, options) {\n};
|
||||
print qq{ container.textContent = '';\n};
|
||||
print qq{ if (!options || options.length === 0) {\n};
|
||||
print qq{ container.textContent = '(none found)';\n};
|
||||
print qq{ return;\n};
|
||||
print qq{ }\n};
|
||||
print qq{ var pre = document.createElement('pre');\n};
|
||||
print qq{ pre.className = 'text-xs font-mono bg-base-200 p-3 rounded-lg overflow-x-auto';\n};
|
||||
print qq{ pre.textContent = options.join('\\n');\n};
|
||||
print qq{ container.appendChild(pre);\n};
|
||||
print qq{}\n};
|
||||
print qq{\n};
|
||||
print qq{function gnizaGoConfirm() {\n};
|
||||
print qq{ var url = 'restore.live.cgi?step=3'\n};
|
||||
print qq{ + '&type=' + encodeURIComponent(gnizaType)\n};
|
||||
print qq{ + '&remote=' + encodeURIComponent(gnizaRemote)\n};
|
||||
print qq{ + '×tamp=' + encodeURIComponent(gnizaTimestamp);\n};
|
||||
print qq{\n};
|
||||
print qq{ if (gnizaType === 'files') {\n};
|
||||
print qq{ var path = document.getElementById('path') ? document.getElementById('path').value : '';\n};
|
||||
print qq{ url += '&path=' + encodeURIComponent(path);\n};
|
||||
print qq{ } else if (document.getElementById('selected_items')) {\n};
|
||||
print qq{ url += '&items=' + encodeURIComponent(document.getElementById('selected_items').value);\n};
|
||||
print qq{ }\n};
|
||||
print qq{\n};
|
||||
print qq{ window.location.href = url;\n};
|
||||
print qq{}\n};
|
||||
print qq{\n};
|
||||
print qq{function gnizaOpenBrowser() {\n};
|
||||
print qq{ fbSelected = '';\n};
|
||||
print qq{ document.getElementById('fb-select-btn').disabled = true;\n};
|
||||
print qq{ document.getElementById('fb-modal').showModal();\n};
|
||||
print qq{ gnizaLoadDir('');\n};
|
||||
print qq{}\n};
|
||||
print qq{\n};
|
||||
print qq{function gnizaLoadDir(path) {\n};
|
||||
print qq{ var cacheKey = path;\n};
|
||||
print qq{ if (fbCache[cacheKey]) {\n};
|
||||
print qq{ gnizaRenderFileList(path, fbCache[cacheKey]);\n};
|
||||
print qq{ return;\n};
|
||||
print qq{ }\n};
|
||||
print qq{\n};
|
||||
print qq{ document.getElementById('fb-loading').hidden = false;\n};
|
||||
print qq{ document.getElementById('fb-error').hidden = true;\n};
|
||||
print qq{ document.getElementById('fb-tbody').textContent = '';\n};
|
||||
print qq{\n};
|
||||
print qq{ var url = 'restore.live.cgi?step=fetch_options'\n};
|
||||
print qq{ + '&remote=' + encodeURIComponent(gnizaRemote)\n};
|
||||
print qq{ + '×tamp=' + encodeURIComponent(gnizaTimestamp)\n};
|
||||
print qq{ + '&type=files'\n};
|
||||
print qq{ + (path ? '&path=' + encodeURIComponent(path) : '');\n};
|
||||
print qq{\n};
|
||||
print qq{ var xhr = new XMLHttpRequest();\n};
|
||||
print qq{ xhr.open('GET', url, true);\n};
|
||||
print qq{ xhr.onreadystatechange = function() {\n};
|
||||
print qq{ if (xhr.readyState !== 4) return;\n};
|
||||
print qq{ document.getElementById('fb-loading').hidden = true;\n};
|
||||
print qq{ if (xhr.status === 200) {\n};
|
||||
print qq{ try {\n};
|
||||
print qq{ var data = JSON.parse(xhr.responseText);\n};
|
||||
print qq{ if (data.error) {\n};
|
||||
print qq{ document.getElementById('fb-error').textContent = data.error;\n};
|
||||
print qq{ document.getElementById('fb-error').hidden = false;\n};
|
||||
print qq{ } else {\n};
|
||||
print qq{ fbCache[cacheKey] = data.options;\n};
|
||||
print qq{ gnizaRenderFileList(path, data.options);\n};
|
||||
print qq{ }\n};
|
||||
print qq{ } catch(e) {\n};
|
||||
print qq{ document.getElementById('fb-error').textContent = 'Failed to parse response';\n};
|
||||
print qq{ document.getElementById('fb-error').hidden = false;\n};
|
||||
print qq{ }\n};
|
||||
print qq{ }\n};
|
||||
print qq{ };\n};
|
||||
print qq{ xhr.send();\n};
|
||||
print qq{}\n};
|
||||
print qq{\n};
|
||||
print qq{function gnizaRenderBreadcrumbs(path) {\n};
|
||||
print qq{ var ul = document.createElement('ul');\n};
|
||||
print qq{ var li = document.createElement('li');\n};
|
||||
print qq{ var a = document.createElement('a');\n};
|
||||
print qq{ a.textContent = 'homedir';\n};
|
||||
print qq{ a.href = '#';\n};
|
||||
print qq{ a.onclick = function(e) { e.preventDefault(); gnizaLoadDir(''); };\n};
|
||||
print qq{ li.appendChild(a);\n};
|
||||
print qq{ ul.appendChild(li);\n};
|
||||
print qq{\n};
|
||||
print qq{ if (path) {\n};
|
||||
print qq{ var parts = path.replace(/\\/\$/, '').split('/');\n};
|
||||
print qq{ var built = '';\n};
|
||||
print qq{ for (var i = 0; i < parts.length; i++) {\n};
|
||||
print qq{ built += (i > 0 ? '/' : '') + parts[i];\n};
|
||||
print qq{ li = document.createElement('li');\n};
|
||||
print qq{ if (i < parts.length - 1) {\n};
|
||||
print qq{ a = document.createElement('a');\n};
|
||||
print qq{ a.textContent = parts[i];\n};
|
||||
print qq{ a.href = '#';\n};
|
||||
print qq{ (function(p) { a.onclick = function(e) { e.preventDefault(); gnizaLoadDir(p); }; })(built);\n};
|
||||
print qq{ li.appendChild(a);\n};
|
||||
print qq{ } else {\n};
|
||||
print qq{ li.textContent = parts[i];\n};
|
||||
print qq{ }\n};
|
||||
print qq{ ul.appendChild(li);\n};
|
||||
print qq{ }\n};
|
||||
print qq{ }\n};
|
||||
print qq{\n};
|
||||
print qq{ var bc = document.getElementById('fb-breadcrumbs');\n};
|
||||
print qq{ bc.textContent = '';\n};
|
||||
print qq{ bc.appendChild(ul);\n};
|
||||
print qq{}\n};
|
||||
print qq{\n};
|
||||
print qq{function gnizaRenderFileList(currentPath, entries) {\n};
|
||||
print qq{ gnizaRenderBreadcrumbs(currentPath);\n};
|
||||
print qq{ fbSelected = '';\n};
|
||||
print qq{ document.getElementById('fb-select-btn').disabled = true;\n};
|
||||
print qq{\n};
|
||||
print qq{ var tbody = document.getElementById('fb-tbody');\n};
|
||||
print qq{ tbody.textContent = '';\n};
|
||||
print qq{\n};
|
||||
print qq{ if (!entries || entries.length === 0) {\n};
|
||||
print qq{ var emptyTr = document.createElement('tr');\n};
|
||||
print qq{ var emptyTd = document.createElement('td');\n};
|
||||
print qq{ emptyTd.className = 'text-center text-base-content/60 py-4';\n};
|
||||
print qq{ emptyTd.textContent = '(empty directory)';\n};
|
||||
print qq{ emptyTr.appendChild(emptyTd);\n};
|
||||
print qq{ tbody.appendChild(emptyTr);\n};
|
||||
print qq{ return;\n};
|
||||
print qq{ }\n};
|
||||
print qq{\n};
|
||||
print qq{ for (var i = 0; i < entries.length; i++) {\n};
|
||||
print qq{ var entry = entries[i];\n};
|
||||
print qq{ var isDir = entry.endsWith('/');\n};
|
||||
print qq{ var fullPath = currentPath ? currentPath.replace(/\\/\$/, '') + '/' + entry : entry;\n};
|
||||
print qq{\n};
|
||||
print qq{ var tr = document.createElement('tr');\n};
|
||||
print qq{ tr.className = 'cursor-pointer hover';\n};
|
||||
print qq{ tr.setAttribute('data-path', fullPath);\n};
|
||||
print qq{\n};
|
||||
print qq{ var td = document.createElement('td');\n};
|
||||
print qq{ td.className = 'py-1';\n};
|
||||
print qq{ var icon = isDir ? '\\uD83D\\uDCC1 ' : '\\uD83D\\uDCC4 ';\n};
|
||||
print qq{ td.textContent = icon + entry;\n};
|
||||
print qq{ tr.appendChild(td);\n};
|
||||
print qq{\n};
|
||||
print qq{ (function(row, path, dir) {\n};
|
||||
print qq{ row.onclick = function() {\n};
|
||||
print qq{ var rows = document.getElementById('fb-tbody').querySelectorAll('tr');\n};
|
||||
print qq{ for (var j = 0; j < rows.length; j++) rows[j].classList.remove('bg-primary/10');\n};
|
||||
print qq{ row.classList.add('bg-primary/10');\n};
|
||||
print qq{ fbSelected = path;\n};
|
||||
print qq{ document.getElementById('fb-select-btn').disabled = false;\n};
|
||||
print qq{ };\n};
|
||||
print qq{ if (dir) {\n};
|
||||
print qq{ row.ondblclick = function() { gnizaLoadDir(path.replace(/\\/\$/, '')); };\n};
|
||||
print qq{ }\n};
|
||||
print qq{ })(tr, fullPath, isDir);\n};
|
||||
print qq{\n};
|
||||
print qq{ tbody.appendChild(tr);\n};
|
||||
print qq{ }\n};
|
||||
print qq{}\n};
|
||||
print qq{\n};
|
||||
print qq{function gnizaSelectPath() {\n};
|
||||
print qq{ if (fbSelected) {\n};
|
||||
print qq{ document.getElementById('path').value = fbSelected;\n};
|
||||
print qq{ }\n};
|
||||
print qq{ document.getElementById('fb-modal').close();\n};
|
||||
print qq{}\n};
|
||||
print qq{</script>\n};
|
||||
print <<"END_JS";
|
||||
<script>
|
||||
var gnizaType = '$esc_type';
|
||||
var gnizaRemote = '$esc_remote';
|
||||
var gnizaTimestamp = '$esc_timestamp';
|
||||
var fbCache = {};
|
||||
var fbSelected = '';
|
||||
|
||||
(function() {
|
||||
var listTypes = ['database','dbusers','mailbox','domains','ssl','cron'];
|
||||
if (listTypes.indexOf(gnizaType) >= 0) {
|
||||
loadOptions();
|
||||
}
|
||||
})();
|
||||
|
||||
function loadOptions() {
|
||||
var url = 'restore.live.cgi?step=fetch_options'
|
||||
+ '&remote=' + encodeURIComponent(gnizaRemote)
|
||||
+ '×tamp=' + encodeURIComponent(gnizaTimestamp)
|
||||
+ '&type=' + encodeURIComponent(gnizaType);
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState !== 4) return;
|
||||
var container = document.getElementById('item-list');
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
if (data.error) {
|
||||
container.textContent = 'Error: ' + data.error;
|
||||
} else if (gnizaType === 'cron') {
|
||||
populatePreview(container, data.options);
|
||||
} else {
|
||||
populateChecklist(container, data.options);
|
||||
}
|
||||
} catch(e) {
|
||||
container.textContent = 'Failed to parse response';
|
||||
}
|
||||
} else {
|
||||
container.textContent = 'Request failed';
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function populateChecklist(container, options) {
|
||||
var hidden = document.getElementById('selected_items');
|
||||
container.textContent = '';
|
||||
if (!options || options.length === 0) {
|
||||
container.textContent = '(none found)';
|
||||
return;
|
||||
}
|
||||
|
||||
var allLabels = {database:'All Databases',dbusers:'All Database Users',mailbox:'All Mailboxes',domains:'All Domains',ssl:'All Certificates'};
|
||||
var allLabel = allLabels[gnizaType] || 'All';
|
||||
|
||||
var allRow = _makeCheckRow(allLabel, '', true);
|
||||
allRow.querySelector('input').setAttribute('data-all', '1');
|
||||
allRow.querySelector('input').onchange = function() { toggleAll(this.checked); };
|
||||
allRow.querySelector('span').className = 'text-sm font-semibold';
|
||||
container.appendChild(allRow);
|
||||
|
||||
for (var i = 0; i < options.length; i++) {
|
||||
var row = _makeCheckRow(options[i], options[i], false);
|
||||
row.querySelector('input').setAttribute('data-item', '1');
|
||||
row.querySelector('input').onchange = function() { syncHidden(); };
|
||||
container.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
function _makeCheckRow(labelText, value, isAll) {
|
||||
var label = document.createElement('label');
|
||||
label.className = 'flex items-center gap-2 cursor-pointer';
|
||||
var cb = document.createElement('input');
|
||||
cb.type = 'checkbox';
|
||||
cb.className = 'checkbox checkbox-sm';
|
||||
if (value) cb.value = value;
|
||||
var span = document.createElement('span');
|
||||
span.className = 'text-sm';
|
||||
span.textContent = labelText;
|
||||
label.appendChild(cb);
|
||||
label.appendChild(span);
|
||||
return label;
|
||||
}
|
||||
|
||||
function toggleAll(checked) {
|
||||
var container = document.getElementById('item-list');
|
||||
var hidden = document.getElementById('selected_items');
|
||||
var items = container.querySelectorAll('input[data-item]');
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
items[i].disabled = checked;
|
||||
if (checked) items[i].checked = false;
|
||||
}
|
||||
hidden.value = checked ? '__ALL__' : '';
|
||||
}
|
||||
|
||||
function syncHidden() {
|
||||
var container = document.getElementById('item-list');
|
||||
var hidden = document.getElementById('selected_items');
|
||||
var items = container.querySelectorAll('input[data-item]:checked');
|
||||
var vals = [];
|
||||
for (var i = 0; i < items.length; i++) {
|
||||
vals.push(items[i].value);
|
||||
}
|
||||
hidden.value = vals.join(',');
|
||||
}
|
||||
|
||||
function populatePreview(container, options) {
|
||||
container.textContent = '';
|
||||
if (!options || options.length === 0) {
|
||||
container.textContent = '(none found)';
|
||||
return;
|
||||
}
|
||||
var pre = document.createElement('pre');
|
||||
pre.className = 'text-xs font-mono bg-base-200 p-3 rounded-lg overflow-x-auto';
|
||||
pre.textContent = options.join('\\n');
|
||||
container.appendChild(pre);
|
||||
}
|
||||
|
||||
function gnizaGoConfirm() {
|
||||
var url = 'restore.live.cgi?step=3'
|
||||
+ '&type=' + encodeURIComponent(gnizaType)
|
||||
+ '&remote=' + encodeURIComponent(gnizaRemote)
|
||||
+ '×tamp=' + encodeURIComponent(gnizaTimestamp);
|
||||
|
||||
if (gnizaType === 'files') {
|
||||
var path = document.getElementById('path') ? document.getElementById('path').value : '';
|
||||
url += '&path=' + encodeURIComponent(path);
|
||||
} else if (document.getElementById('selected_items')) {
|
||||
url += '&items=' + encodeURIComponent(document.getElementById('selected_items').value);
|
||||
}
|
||||
|
||||
window.location.href = url;
|
||||
}
|
||||
|
||||
function gnizaOpenBrowser() {
|
||||
fbSelected = '';
|
||||
document.getElementById('fb-select-btn').disabled = true;
|
||||
document.getElementById('fb-modal').showModal();
|
||||
gnizaLoadDir('');
|
||||
}
|
||||
|
||||
function gnizaLoadDir(path) {
|
||||
var cacheKey = path;
|
||||
if (fbCache[cacheKey]) {
|
||||
gnizaRenderFileList(path, fbCache[cacheKey]);
|
||||
return;
|
||||
}
|
||||
|
||||
document.getElementById('fb-loading').hidden = false;
|
||||
document.getElementById('fb-error').hidden = true;
|
||||
document.getElementById('fb-tbody').textContent = '';
|
||||
|
||||
var url = 'restore.live.cgi?step=fetch_options'
|
||||
+ '&remote=' + encodeURIComponent(gnizaRemote)
|
||||
+ '×tamp=' + encodeURIComponent(gnizaTimestamp)
|
||||
+ '&type=files'
|
||||
+ (path ? '&path=' + encodeURIComponent(path) : '');
|
||||
|
||||
var xhr = new XMLHttpRequest();
|
||||
xhr.open('GET', url, true);
|
||||
xhr.onreadystatechange = function() {
|
||||
if (xhr.readyState !== 4) return;
|
||||
document.getElementById('fb-loading').hidden = true;
|
||||
if (xhr.status === 200) {
|
||||
try {
|
||||
var data = JSON.parse(xhr.responseText);
|
||||
if (data.error) {
|
||||
document.getElementById('fb-error').textContent = data.error;
|
||||
document.getElementById('fb-error').hidden = false;
|
||||
} else {
|
||||
fbCache[cacheKey] = data.options;
|
||||
gnizaRenderFileList(path, data.options);
|
||||
}
|
||||
} catch(e) {
|
||||
document.getElementById('fb-error').textContent = 'Failed to parse response';
|
||||
document.getElementById('fb-error').hidden = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
xhr.send();
|
||||
}
|
||||
|
||||
function gnizaRenderBreadcrumbs(path) {
|
||||
var ul = document.createElement('ul');
|
||||
var li = document.createElement('li');
|
||||
var a = document.createElement('a');
|
||||
a.textContent = 'homedir';
|
||||
a.href = '#';
|
||||
a.onclick = function(e) { e.preventDefault(); gnizaLoadDir(''); };
|
||||
li.appendChild(a);
|
||||
ul.appendChild(li);
|
||||
|
||||
if (path) {
|
||||
var parts = path.replace(/\\/\$/, '').split('/');
|
||||
var built = '';
|
||||
for (var i = 0; i < parts.length; i++) {
|
||||
built += (i > 0 ? '/' : '') + parts[i];
|
||||
li = document.createElement('li');
|
||||
if (i < parts.length - 1) {
|
||||
a = document.createElement('a');
|
||||
a.textContent = parts[i];
|
||||
a.href = '#';
|
||||
(function(p) { a.onclick = function(e) { e.preventDefault(); gnizaLoadDir(p); }; })(built);
|
||||
li.appendChild(a);
|
||||
} else {
|
||||
li.textContent = parts[i];
|
||||
}
|
||||
ul.appendChild(li);
|
||||
}
|
||||
}
|
||||
|
||||
var bc = document.getElementById('fb-breadcrumbs');
|
||||
bc.textContent = '';
|
||||
bc.appendChild(ul);
|
||||
}
|
||||
|
||||
function gnizaRenderFileList(currentPath, entries) {
|
||||
gnizaRenderBreadcrumbs(currentPath);
|
||||
fbSelected = '';
|
||||
document.getElementById('fb-select-btn').disabled = true;
|
||||
|
||||
var tbody = document.getElementById('fb-tbody');
|
||||
tbody.textContent = '';
|
||||
|
||||
if (!entries || entries.length === 0) {
|
||||
var emptyTr = document.createElement('tr');
|
||||
var emptyTd = document.createElement('td');
|
||||
emptyTd.className = 'text-center text-base-content/60 py-4';
|
||||
emptyTd.textContent = '(empty directory)';
|
||||
emptyTr.appendChild(emptyTd);
|
||||
tbody.appendChild(emptyTr);
|
||||
return;
|
||||
}
|
||||
|
||||
for (var i = 0; i < entries.length; i++) {
|
||||
var entry = entries[i];
|
||||
var isDir = entry.endsWith('/');
|
||||
var fullPath = currentPath ? currentPath.replace(/\\/\$/, '') + '/' + entry : entry;
|
||||
|
||||
var tr = document.createElement('tr');
|
||||
tr.className = 'cursor-pointer hover';
|
||||
tr.setAttribute('data-path', fullPath);
|
||||
|
||||
var td = document.createElement('td');
|
||||
td.className = 'py-1';
|
||||
var icon = isDir ? '\\uD83D\\uDCC1 ' : '\\uD83D\\uDCC4 ';
|
||||
td.textContent = icon + entry;
|
||||
tr.appendChild(td);
|
||||
|
||||
(function(row, path, dir) {
|
||||
row.onclick = function() {
|
||||
var rows = document.getElementById('fb-tbody').querySelectorAll('tr');
|
||||
for (var j = 0; j < rows.length; j++) rows[j].classList.remove('bg-primary/10');
|
||||
row.classList.add('bg-primary/10');
|
||||
fbSelected = path;
|
||||
document.getElementById('fb-select-btn').disabled = false;
|
||||
};
|
||||
if (dir) {
|
||||
row.ondblclick = function() { gnizaLoadDir(path.replace(/\\/\$/, '')); };
|
||||
}
|
||||
})(tr, fullPath, isDir);
|
||||
|
||||
tbody.appendChild(tr);
|
||||
}
|
||||
}
|
||||
|
||||
function gnizaSelectPath() {
|
||||
if (fbSelected) {
|
||||
document.getElementById('path').value = fbSelected;
|
||||
}
|
||||
document.getElementById('fb-modal').close();
|
||||
}
|
||||
</script>
|
||||
END_JS
|
||||
}
|
||||
|
||||
# ── Step 3: Confirmation ─────────────────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user