Initial curated chimera source from a8ae5b1db07fb49926c94585e2718e5130330974
This commit is contained in:
commit
c737a37f71
531 files changed
+119270
No files matched your search
@@ -0,0 +1,391 @@
|
||||
// ── Manage panel ──────────────────────────────────────────────────────────────
|
||||
let uploadInProgress = false;
|
||||
let uploadLocalPath = null;
|
||||
|
||||
window.pork.on('ftp:upload:progress', info => {
|
||||
const log = $('manage-upload-log');
|
||||
if (!log) return;
|
||||
// Directory upload shape has filesTotal; single-file shape has bytes/size
|
||||
if (info.filesTotal != null) {
|
||||
const pct = info.filesTotal > 0 ? Math.round((info.filesDone / info.filesTotal) * 100) : 0;
|
||||
log.textContent = `Uploading… File ${info.filesDone}/${info.filesTotal} (${pct}%)${info.file ? ' ' + info.file : ''}`;
|
||||
} else {
|
||||
const done = info.bytes || 0;
|
||||
const total = info.size || 0;
|
||||
const pct = total > 0 ? Math.round((done / total) * 100) : 0;
|
||||
log.textContent = `Uploading… ${pct}% (${fmt(done)} / ${fmt(total)})`;
|
||||
}
|
||||
});
|
||||
|
||||
async function renderManagePanel(g, backups) {
|
||||
// ── Backup file list (local only — backpork entries shown in Backpork Versions) ─
|
||||
const list = $('manage-backup-list');
|
||||
backups = backups.filter(b => b.source !== 'backpork');
|
||||
$('mtab-manage-count').textContent = backups.length || '';
|
||||
|
||||
if (!backups.length) {
|
||||
list.innerHTML = '<div class="prospero-placeholder">No backup files recorded.</div>';
|
||||
} else {
|
||||
list.innerHTML = backups.map(b => {
|
||||
const filename = b.backup_path.replace(/\\/g, '/').split('/').pop();
|
||||
return `
|
||||
<div class="manage-backup-item" data-backup-id="${b.id}" data-game="${escHtml(g.game_id)}"
|
||||
data-local="${escHtml(b.backup_path)}" data-folder="${escHtml(b.backup_path)}">
|
||||
<div class="manage-backup-info">
|
||||
<span class="manage-filename" title="${escHtml(b.backup_path)}">${escHtml(filename)}</span>
|
||||
<span class="manage-size">${fmt(b.size)}</span>
|
||||
<span class="pill pill-num">${escHtml(b.backup_type || 'dump')}</span>
|
||||
</div>
|
||||
<div class="manage-backup-actions">
|
||||
<button class="btn btn-sm manage-btn-folder">Open Folder</button>
|
||||
<button class="btn btn-sm btn-teal manage-btn-upload">Upload to PS5</button>
|
||||
<button class="btn btn-sm btn-danger manage-btn-remove">Remove Record</button>
|
||||
</div>
|
||||
</div>`;
|
||||
}).join('');
|
||||
}
|
||||
|
||||
// ── Upload section state ────────────────────────────────────────────────────
|
||||
uploadLocalPath = null;
|
||||
$('manage-remote-path').value = '';
|
||||
$('manage-remote-path').readOnly = true;
|
||||
$('btn-manage-upload').disabled = true;
|
||||
$('manage-upload-log').hidden = true;
|
||||
|
||||
// ── Backed-up / Installed status buttons ─────────────────────────────────────
|
||||
const toggleBackedBtn = $('btn-manage-toggle-backed');
|
||||
if (g.backed_up) {
|
||||
toggleBackedBtn.textContent = 'Clear Backed Up Status';
|
||||
toggleBackedBtn.className = 'btn btn-danger';
|
||||
} else {
|
||||
toggleBackedBtn.textContent = 'Mark as Backed Up';
|
||||
toggleBackedBtn.className = 'btn btn-teal';
|
||||
}
|
||||
const toggleInstBtn = $('btn-manage-toggle-installed');
|
||||
if (g.installed) {
|
||||
toggleInstBtn.textContent = 'Mark as Not Installed';
|
||||
toggleInstBtn.className = 'btn btn-danger';
|
||||
} else {
|
||||
toggleInstBtn.textContent = 'Mark as Installed';
|
||||
toggleInstBtn.className = 'btn';
|
||||
}
|
||||
|
||||
// ── Install location display ──────────────────────────────────────────────────
|
||||
renderManageInstallLocation(g);
|
||||
|
||||
// ── Backpork versions section ────────────────────────────────────────────────
|
||||
await renderBackporkSection(g);
|
||||
}
|
||||
|
||||
// ── Manage: install location display ─────────────────────────────────────────
|
||||
|
||||
function renderManageInstallLocation(g) {
|
||||
const locRow = $('manage-install-location');
|
||||
const noLocRow = $('manage-no-location');
|
||||
const locPath = $('manage-loc-path');
|
||||
const deleteBtn = $('btn-manage-delete-ps5');
|
||||
const backupBtn = $('btn-manage-backup-ps5');
|
||||
|
||||
if (g?.ftp_path) {
|
||||
locRow.hidden = false;
|
||||
noLocRow.hidden = true;
|
||||
locPath.textContent = g.ftp_path;
|
||||
deleteBtn.disabled = !state.connected;
|
||||
backupBtn.disabled = !state.connected;
|
||||
} else {
|
||||
locRow.hidden = true;
|
||||
noLocRow.hidden = false;
|
||||
locPath.textContent = '';
|
||||
deleteBtn.disabled = true;
|
||||
backupBtn.disabled = true;
|
||||
}
|
||||
}
|
||||
|
||||
$('btn-manage-forget-loc').addEventListener('click', async () => {
|
||||
if (!state.currentGame) return;
|
||||
const gameId = state.currentGame.game_id;
|
||||
await window.pork.updateGame({ game_id: gameId, fields: { ftp_path: '' } });
|
||||
state.currentGame = { ...state.currentGame, ftp_path: '' };
|
||||
renderManageInstallLocation(state.currentGame);
|
||||
setStatus('PS5 install location forgotten', 'ok');
|
||||
});
|
||||
|
||||
$('btn-manage-backup-ps5').addEventListener('click', async () => {
|
||||
if (!state.currentGame || !state.connected) return;
|
||||
const { game_id, ftp_path } = state.currentGame;
|
||||
if (!ftp_path) { setStatus('No PS5 location stored — run a scan first', 'error'); return; }
|
||||
|
||||
const btn = $('btn-manage-backup-ps5');
|
||||
btn.disabled = true;
|
||||
try {
|
||||
await window.pork.ftpDownloadFolder(game_id);
|
||||
setStatus('Backup queued — see Transfers for progress', 'ok');
|
||||
} catch (e) {
|
||||
setStatus(e.message, 'error');
|
||||
} finally {
|
||||
btn.disabled = !state.connected || !state.currentGame?.ftp_path;
|
||||
}
|
||||
});
|
||||
|
||||
$('btn-manage-delete-ps5').addEventListener('click', async () => {
|
||||
if (!state.currentGame) return;
|
||||
const g = state.currentGame;
|
||||
if (!g.ftp_path) { setStatus('No PS5 location stored — cannot delete', 'error'); return; }
|
||||
|
||||
const confirmation = await showExactConfirmation(
|
||||
`Delete "${g.title || g.game_id}" from PS5?\n\nFolder: ${g.ftp_path}\n\nThis cannot be undone.`,
|
||||
'VERWIJDER VAN PS5'
|
||||
);
|
||||
if (!confirmation) return;
|
||||
|
||||
const btn = $('btn-manage-delete-ps5');
|
||||
btn.disabled = true;
|
||||
btn.textContent = 'Deleting…';
|
||||
|
||||
try {
|
||||
await window.pork.ftpDeleteGame(g.game_id, confirmation);
|
||||
state.currentGame = { ...g, availability: { ...(g.availability || {}), state: 'absent', confirmedInstallCount: 0 }, ftp_path: '' };
|
||||
state.games = await window.pork.listGames({});
|
||||
renderManageInstallLocation(state.currentGame);
|
||||
const toggleInstBtn = $('btn-manage-toggle-installed');
|
||||
toggleInstBtn.textContent = 'Mark as Installed';
|
||||
toggleInstBtn.className = 'btn';
|
||||
setStatus(`${g.title || g.game_id} deleted from PS5`, 'ok');
|
||||
} catch (err) {
|
||||
setStatus(`Delete failed: ${err.message}`, 'error');
|
||||
} finally {
|
||||
btn.textContent = 'Delete from PS5';
|
||||
btn.disabled = !state.connected || !state.currentGame?.ftp_path;
|
||||
}
|
||||
});
|
||||
|
||||
// ── Backpork section in Manage tab ────────────────────────────────────────────
|
||||
let porkInProgress = false;
|
||||
|
||||
window.pork.on('backporks:pork:progress', ({ file, filesDone, filesTotal, bytesOverall }) => {
|
||||
const fill = $('pork-progress-fill');
|
||||
const label = $('pork-progress-label');
|
||||
const pct = filesTotal > 0 ? Math.round((filesDone / filesTotal) * 100) : 0;
|
||||
if (fill) fill.style.width = `${pct}%`;
|
||||
if (label) label.textContent = `Uploading ${filesDone + 1}/${filesTotal}: ${escHtml(file || '')}`;
|
||||
});
|
||||
|
||||
async function renderBackporkSection(g) {
|
||||
const section = $('manage-backpork-section');
|
||||
const entries = await window.pork.backporksEntries(g.game_id);
|
||||
|
||||
if (!entries.length) { section.hidden = true; return; }
|
||||
section.hidden = false;
|
||||
|
||||
// Porked-to status
|
||||
const statusEl = $('manage-porked-status');
|
||||
if (g.porked_firmware) {
|
||||
const when = g.porked_at ? ` · ${new Date(g.porked_at).toLocaleDateString()}` : '';
|
||||
statusEl.innerHTML = `
|
||||
<div class="manage-porked-status">
|
||||
✓ Porked to firmware <strong>${escHtml(g.porked_firmware)}</strong>${escHtml(when)}
|
||||
<button class="clear-pork btn btn-sm" data-game="${escHtml(g.game_id)}">Clear</button>
|
||||
</div>`;
|
||||
} else {
|
||||
statusEl.innerHTML = '';
|
||||
}
|
||||
|
||||
// Entry list
|
||||
const listEl = $('manage-backpork-list');
|
||||
listEl.innerHTML = entries.map(e => {
|
||||
const gamePath = e.game_path || e.folder_path;
|
||||
const displayPath = gamePath.replace(/\\/g, '/').split('/').pop();
|
||||
return `
|
||||
<div class="manage-backpork-entry"
|
||||
data-fw="${escHtml(e.folder_name)}"
|
||||
data-path="${escHtml(e.folder_path)}"
|
||||
data-game="${escHtml(g.game_id)}">
|
||||
<span class="manage-backpork-fw">${escHtml(e.folder_name)}</span>
|
||||
<span class="manage-backpork-path" title="${escHtml(gamePath)}">${escHtml(displayPath)}</span>
|
||||
<button class="btn btn-sm manage-btn-view-backpork"
|
||||
data-open="${escHtml(gamePath)}">View Backpork</button>
|
||||
<button class="btn btn-sm btn-teal pork-btn"
|
||||
${!state.connected ? 'disabled title="Connect to PS5 first"' : ''}>
|
||||
Pork to PS5
|
||||
</button>
|
||||
</div>`;
|
||||
}).join('');
|
||||
|
||||
// Reset progress bar
|
||||
$('manage-pork-progress').hidden = true;
|
||||
$('pork-progress-fill').style.width = '0%';
|
||||
}
|
||||
|
||||
// Clear porked status
|
||||
$('manage-porked-status').addEventListener('click', async e => {
|
||||
const btn = e.target.closest('.clear-pork');
|
||||
if (!btn) return;
|
||||
const gameId = btn.dataset.game;
|
||||
await window.pork.updateGame({ game_id: gameId, fields: { porked_firmware: null, porked_at: null } });
|
||||
state.games = await window.pork.listGames({});
|
||||
const updated = state.games.find(x => x.game_id === gameId) || state.currentGame;
|
||||
state.currentGame = { ...state.currentGame, porked_firmware: null, porked_at: null };
|
||||
await renderBackporkSection(state.currentGame);
|
||||
setStatus('Porked status cleared', 'ok');
|
||||
});
|
||||
|
||||
// Backpork list click delegation
|
||||
$('manage-backpork-list').addEventListener('click', async e => {
|
||||
// ── View Backpork folder ─────────────────────────────────────────────────────
|
||||
const viewBtn = e.target.closest('.manage-btn-view-backpork');
|
||||
if (viewBtn) {
|
||||
window.pork.openShell(viewBtn.dataset.open);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pork to PS5
|
||||
const btn = e.target.closest('.pork-btn');
|
||||
if (!btn || porkInProgress) return;
|
||||
const entry = btn.closest('.manage-backpork-entry');
|
||||
if (!entry) return;
|
||||
|
||||
const gameId = entry.dataset.game;
|
||||
const folderName = entry.dataset.fw;
|
||||
const folderPath = entry.dataset.path;
|
||||
|
||||
// Ensure a destination is remembered before porking
|
||||
const ftpDest = await ensureGameFtpPath(gameId);
|
||||
if (!ftpDest) return; // user cancelled picker
|
||||
|
||||
porkInProgress = true;
|
||||
btn.disabled = true;
|
||||
$('manage-pork-progress').hidden = false;
|
||||
$('pork-progress-fill').style.width = '0%';
|
||||
$('pork-progress-label').textContent = 'Starting…';
|
||||
setStatus(`Porking ${gameId} (${folderName})…`);
|
||||
|
||||
try {
|
||||
const { remotePath } = await window.pork.backporksPork(gameId, folderName, folderPath);
|
||||
$('manage-pork-progress').hidden = true;
|
||||
setStatus(`Porked to PS5 at ${remotePath}`, 'ok');
|
||||
|
||||
// Refresh game state
|
||||
state.games = await window.pork.listGames({});
|
||||
const updated = state.games.find(x => x.game_id === gameId);
|
||||
if (updated) {
|
||||
state.currentGame = updated;
|
||||
await renderBackporkSection(updated);
|
||||
// Update installed toggle
|
||||
const toggleInstBtn = $('btn-manage-toggle-installed');
|
||||
toggleInstBtn.textContent = 'Mark as Not Installed';
|
||||
toggleInstBtn.className = 'btn btn-danger';
|
||||
}
|
||||
} catch (err) {
|
||||
$('manage-pork-progress').hidden = true;
|
||||
setStatus(`Pork failed: ${err.message}`, 'error');
|
||||
} finally {
|
||||
porkInProgress = false;
|
||||
btn.disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle installed status
|
||||
$('btn-manage-toggle-installed').addEventListener('click', async () => {
|
||||
setStatus('Install status is derived from complete console scan evidence.', 'info');
|
||||
});
|
||||
|
||||
// ── Delegation for all manage-panel interactions (attached once, never re-bound)
|
||||
$('manage-backup-list').addEventListener('click', async e => {
|
||||
const item = e.target.closest('.manage-backup-item');
|
||||
if (!item) return;
|
||||
|
||||
const backupId = Number(item.dataset.backupId);
|
||||
const gameId = item.dataset.game;
|
||||
const local = item.dataset.local;
|
||||
const folder = item.dataset.folder;
|
||||
|
||||
// ── Open Folder ─────────────────────────────────────────────────────────────
|
||||
if (e.target.closest('.manage-btn-folder')) {
|
||||
window.pork.openShell(folder);
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Upload to PS5 — inline expand ───────────────────────────────────────────
|
||||
if (e.target.closest('.manage-btn-upload')) {
|
||||
if (uploadInProgress) return;
|
||||
await openInstallModal(gameId, 'pfs');
|
||||
return;
|
||||
}
|
||||
|
||||
// ── Remove backup record ─────────────────────────────────────────────────────
|
||||
if (e.target.closest('.manage-btn-remove')) {
|
||||
const btn = e.target.closest('.manage-btn-remove');
|
||||
const confirmation = await showExactConfirmation('Remove this backup record from Chimera?', 'VERWIJDER');
|
||||
if (!confirmation) return;
|
||||
btn.disabled = true;
|
||||
try {
|
||||
await window.pork.deleteBackup(backupId, confirmation);
|
||||
setStatus('Backup record removed', 'ok');
|
||||
|
||||
// Refresh the modal's backup-dependent sections
|
||||
const backups = await window.pork.listBackups(gameId);
|
||||
const localBackups = backups.filter(b => b.source !== 'backpork');
|
||||
state.games = await window.pork.listGames({});
|
||||
const g = state.games.find(x => x.game_id === gameId) || state.currentGame;
|
||||
state.currentGame = g;
|
||||
|
||||
await renderManagePanel(g, backups);
|
||||
await renderHashSection(gameId, backups);
|
||||
$('modal-backup-list').innerHTML = localBackups.length
|
||||
? localBackups.map(b => `<li><span>${escHtml(b.backup_path)}</span><span>${fmt(b.size)}</span></li>`).join('')
|
||||
: '<li style="justify-content:center">No backups recorded</li>';
|
||||
} catch (err) {
|
||||
setStatus(err.message, 'error');
|
||||
btn.disabled = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
});
|
||||
|
||||
$('btn-manage-upload').addEventListener('click', async () => {
|
||||
if (uploadInProgress || !uploadLocalPath) return;
|
||||
const remotePath = $('manage-remote-path').value.trim();
|
||||
if (!remotePath) { setStatus('Enter a remote path on the PS5', 'error'); return; }
|
||||
|
||||
uploadInProgress = true;
|
||||
$('btn-manage-upload').disabled = true;
|
||||
const log = $('manage-upload-log');
|
||||
log.hidden = false;
|
||||
log.textContent = 'Starting upload…';
|
||||
|
||||
try {
|
||||
await window.pork.ftpUpload({ localPath: uploadLocalPath, remotePath });
|
||||
log.textContent = 'Upload complete ✓';
|
||||
setStatus('Uploaded to PS5 successfully', 'ok');
|
||||
uploadLocalPath = null;
|
||||
$('manage-remote-path').value = '';
|
||||
$('manage-remote-path').readOnly = true;
|
||||
document.querySelectorAll('.manage-item-selected').forEach(el => el.classList.remove('manage-item-selected'));
|
||||
} catch (e) {
|
||||
log.textContent = `Upload failed: ${e.message}`;
|
||||
setStatus(e.message, 'error');
|
||||
$('btn-manage-upload').disabled = false;
|
||||
} finally {
|
||||
uploadInProgress = false;
|
||||
}
|
||||
});
|
||||
|
||||
$('btn-manage-toggle-backed').addEventListener('click', async () => {
|
||||
setStatus('Protection status is derived from registered local-copy evidence.', 'info');
|
||||
});
|
||||
|
||||
$('btn-manage-refresh-meta').addEventListener('click', async () => {
|
||||
if (!state.currentGame) return;
|
||||
const { game_id } = state.currentGame;
|
||||
$('btn-manage-refresh-meta').disabled = true;
|
||||
setStatus('Refreshing metadata…');
|
||||
try {
|
||||
await window.pork.prosperoFetch(game_id);
|
||||
setStatus('Metadata refreshed', 'ok');
|
||||
} catch (e) {
|
||||
setStatus(`Refresh failed: ${e.message}`, 'error');
|
||||
} finally {
|
||||
$('btn-manage-refresh-meta').disabled = false;
|
||||
}
|
||||
});
|
||||
|
||||
Reference in new issue
Block a user