Initial curated chimera source from a8ae5b1db07fb49926c94585e2718e5130330974

This commit is contained in:
Public Source Publisher committed 2026-10-01 01:38:08 +02:00
commit c737a37f71
531 files changed
+119270

No files matched your search

+216
View File
@@ -0,0 +1,216 @@
// ── Payload Manager Settings modal ────────────────────────────────────────────
$('btn-payload-settings').addEventListener('click', async () => {
const s = await window.pork.getSettings();
$('pm-local-path').value = s.payloadLocalPath || '';
$('pm-remote-path').value = s.payloadRemotePath || '';
openAccessibleDialog($('payload-settings-overlay'), $('pm-remote-path'));
});
$('btn-pm-browse-local').addEventListener('click', async () => {
const folder = await window.pork.selectFolder();
if (folder) $('pm-local-path').value = folder;
});
$('btn-pm-settings-save').addEventListener('click', async () => {
await window.pork.setSettings({
payloadLocalPath: $('pm-local-path').value.trim(),
payloadRemotePath: $('pm-remote-path').value.trim(),
});
closeAccessibleDialog($('payload-settings-overlay'));
});
['payload-settings-close', 'btn-pm-settings-cancel'].forEach(id =>
$(id).addEventListener('click', () => closeAccessibleDialog($('payload-settings-overlay')))
);
$('payload-settings-overlay').addEventListener('click', event => {
if (event.target === $('payload-settings-overlay')) closeAccessibleDialog($('payload-settings-overlay'));
});
$('payload-settings-overlay').addEventListener('keydown', event => {
if (event.key === 'Escape') closeAccessibleDialog($('payload-settings-overlay'));
});
// ── Transfer Manager ──────────────────────────────────────────────────────────
function fmtSpeed(bps) {
if (!bps || bps < 0) return '—';
if (bps < 1024) return `${Math.round(bps)} B/s`;
if (bps < 1048576) return `${(bps / 1024).toFixed(1)} KB/s`;
return `${(bps / 1048576).toFixed(1)} MB/s`;
}
let _transferState = { jobs: [], paused: false, maxConcurrent: 1, activeCount: 0, queuedCount: 0, interruptedCount: 0, policy: {} };
function renderTransferPage(st) {
_transferState = st;
// Stats bar
$('tstat-active').textContent = st.activeCount;
$('tstat-queued').textContent = st.queuedCount;
$('tstat-interrupted').textContent = st.interruptedCount || 0;
$('tstat-concurrent').textContent = st.maxConcurrent;
const slider = $('transfer-concurrent-slider');
if (slider && parseInt(slider.value) !== st.maxConcurrent) slider.value = st.maxConcurrent;
if ($('transfer-window-start') && document.activeElement !== $('transfer-window-start')) $('transfer-window-start').value = st.policy?.windowStart || '';
if ($('transfer-window-end') && document.activeElement !== $('transfer-window-end')) $('transfer-window-end').value = st.policy?.windowEnd || '';
if ($('transfer-policy-state')) $('transfer-policy-state').textContent = st.policy?.windowStart && st.policy?.windowEnd
? `${st.policy.windowStart}–${st.policy.windowEnd} · ${window.t('transfers.policy.bandwidthUnsupported', 'bandwidth limiting is not supported by the current FTP layer')}`
: `${window.t('transfers.policy.noWindow', 'No time window')} · ${window.t('transfers.policy.bandwidthUnsupported', 'bandwidth limiting is not supported by the current FTP layer')}`;
// Total speed from all active jobs
const totalSpeed = st.jobs
.filter(j => j.status === 'active')
.reduce((sum, j) => sum + (j.progress?.speedBps || 0), 0);
$('tstat-speed').textContent = fmtSpeed(totalSpeed);
// Pause/resume button
const pauseBtn = $('btn-transfer-pause');
if (pauseBtn) {
pauseBtn.textContent = st.paused
? `▶ ${window.t('transfers.resumeAll', 'Resume All')}`
: `⏸ ${window.t('transfers.pauseAll', 'Pause All')}`;
pauseBtn.disabled = !st.paused && !st.jobs.some(job => ['queued', 'active', 'retrying'].includes(job.status));
}
const clearBtn = $('btn-transfer-clear');
if (clearBtn) clearBtn.disabled = !st.jobs.some(job => ['done', 'error', 'cancelled'].includes(job.status));
// Job list
const list = $('transfers-list');
const empty = $('transfers-empty');
const visibleJobs = st.jobs.filter(j => j.status !== 'cancelled');
if (!visibleJobs.length) {
list.innerHTML = '';
empty.hidden = false;
return;
}
empty.hidden = true;
list.innerHTML = visibleJobs.map(job => {
const statusText = {
queued: 'Queued',
active: 'Transferring…',
done: 'Done',
error: 'Error',
cancelled: 'Cancelled',
interrupted: window.t('transfers.status.interrupted', 'Interrupted after restart'),
retrying: 'Retrying…',
}[job.status] || job.status;
const typeLabel = { upload: 'UPLOAD', download: 'DOWNLOAD', pork: 'PORK' }[job.type] || (job.type || '').toUpperCase();
const typeClass = { upload: 'type-upload', download: 'type-download', pork: 'type-pork' }[job.type] || '';
const pct = job.progress?.percent || 0;
const speed = job.status === 'active' ? fmtSpeed(job.progress?.speedBps) : '';
const file = job.progress?.file || '';
const filesDone = job.progress?.filesDone || 0;
const filesTotal = job.progress?.filesTotal || 0;
const transferred = job.progress?.transferred || 0;
const total = job.progress?.total || 0;
const showCancel = job.cancellable !== false && ['queued', 'active', 'retrying'].includes(job.status);
const showRetry = ['error', 'interrupted'].includes(job.status) && job.retryable;
const showBar = job.status === 'active' || job.status === 'done';
const currentBytes = job.progress?.currentBytes || 0;
const currentFileSize = job.progress?.currentFileSize || 0;
// File counter or single-file byte progress
const sizeStr = filesTotal > 1
? `File ${filesDone}/${filesTotal}`
: (total > 0 ? `${fmt(transferred)} / ${fmt(total)}` : '');
// Current-file byte progress for directory uploads (shows activity on large files)
const fileByteStr = (filesTotal > 1 && currentFileSize > 0 && job.status === 'active')
? `${fmt(currentBytes)} / ${fmt(currentFileSize)}`
: '';
const pctLabel = (showBar && job.status === 'active') ? `${pct}%` : (job.status === 'done' ? '100%' : '');
return `
<div class="transfer-item transfer-item-${job.status}">
<div class="transfer-item-header">
<span class="transfer-label" title="${escHtml(job.localPath || job.remotePath || '')}">${escHtml(job.label)}</span>
<span class="transfer-type-badge ${typeClass}">${typeLabel}</span>
<span class="transfer-status-text">${statusText}</span>
${showRetry ? `<button class="btn btn-sm btn-retry-transfer" data-job-id="${escHtml(job.id)}">Retry</button>` : ''}
${showCancel ? `<button class="btn-cancel-transfer" data-job-id="${job.id}" title="Cancel">×</button>` : ''}
</div>
${showBar ? `
<div class="transfer-progress-wrap">
<div class="transfer-progress-bar">
<div class="transfer-progress-fill" style="width:${pct}%"></div>
</div>
${pctLabel ? `<span class="transfer-pct-label">${pctLabel}</span>` : ''}
</div>` : ''}
<div class="transfer-meta">
${speed ? `<span class="transfer-speed">${speed}</span>` : ''}
${file ? `<span class="transfer-file">${escHtml(file)}</span>` : ''}
${fileByteStr ? `<span class="transfer-sizes">${fileByteStr}</span>` : sizeStr ? `<span class="transfer-sizes">${sizeStr}</span>` : ''}
${job.error ? `<span class="transfer-error">${escHtml(job.error)}</span>` : ''}
${job.cancellable === false && ['queued', 'active', 'retrying'].includes(job.status) ? '<span class="transfer-error">Upstream offers no safe cancellation; only task status is tracked.</span>' : ''}
</div>
</div>`;
}).join('');
}
function updateNavTransferBadge(st) {
const badge = $('nav-transfer-badge');
if (!badge) return;
const n = (st.activeCount || 0) + (st.queuedCount || 0);
badge.hidden = n === 0;
badge.textContent = n;
}
async function loadTransfers() {
const st = await window.pork.transferState();
renderTransferPage(st);
updateNavTransferBadge(st);
}
$('btn-transfer-pause').addEventListener('click', async () => {
if (_transferState.paused) await window.pork.transferResume();
else await window.pork.transferPause();
});
$('btn-transfer-clear').addEventListener('click', () => window.pork.transferClearDone());
$('transfer-concurrent-slider').addEventListener('input', async event => {
const n = parseInt(event.target.value);
$('tstat-concurrent').textContent = n;
await window.pork.transferSetConcurrent(n);
});
$('transfers-list').addEventListener('click', event => {
const retry = event.target.closest('.btn-retry-transfer');
if (retry) {
window.pork.transferRetry?.(retry.dataset.jobId).catch(error => showToast(error.message, 'error'));
return;
}
const cancel = event.target.closest('.btn-cancel-transfer');
if (cancel?.dataset.jobId) window.pork.transferCancel(cancel.dataset.jobId);
});
$('btn-transfer-policy-save').addEventListener('click', async () => {
try {
await window.pork.transferSetPolicy?.({
windowStart: $('transfer-window-start').value || null,
windowEnd: $('transfer-window-end').value || null,
});
showToast(window.t('transfers.policy.saved', 'Transfer policy saved.'), 'ok');
} catch (error) {
showToast(error.message, 'error');
}
});
window.pork.on('transfer:update', state => {
renderTransferPage(state);
updateNavTransferBadge(state);
});
window.addEventListener('chimera:language-changed', () => {
if (document.querySelector('#page-transfers.active')) renderTransferPage(_transferState);
});