'use strict'; let _operationsLoaded = false; let _operationsStatus = null; let _operationsBusy = new Set(); let _operationsStorageReport = null; let _operationsStorageTopology = null; function operationsDate(value) { if (!value) return '—'; try { return new Date(Number(value) || value).toLocaleString(window.i18n?.current || 'en'); } catch (_) { return '—'; } } function operationsDuration(value) { const ms = Number(value || 0); if (!ms) return '—'; if (ms < 1000) return `${ms} ms`; if (ms < 60_000) return `${(ms / 1000).toFixed(1)} s`; return `${Math.floor(ms / 60_000)}m ${Math.round((ms % 60_000) / 1000)}s`; } function operationsBytes(value) { const bytes = Number(value || 0); if (!Number.isFinite(bytes) || bytes <= 0) return '—'; const units = ['B', 'KB', 'MB', 'GB', 'TB']; let amount = bytes; let unit = 0; while (amount >= 1024 && unit < units.length - 1) { amount /= 1024; unit += 1; } return `${amount >= 10 || unit === 0 ? amount.toFixed(0) : amount.toFixed(1)} ${units[unit]}`; } function operationsRunSummary(run) { const raw = String(run.error || run.summary || '').trim(); if (!raw) return run.status === 'completed' ? 'Completed' : ''; if (run.error) return raw.length > 240 ? `${raw.slice(0, 237)}...` : raw; let result; try { result = JSON.parse(raw); } catch (_) { return raw.length > 240 ? `${raw.slice(0, 237)}...` : raw; } if (result == null) return 'Completed'; if (typeof result === 'string' || typeof result === 'number') return String(result); if (run.task_id === 'library-scan') { const total = Number(result.total ?? result.found?.length ?? result.found ?? 0); const added = Number(result.added || 0); return `${total} game${total === 1 ? '' : 's'} found · ${added} new`; } if (run.task_id === 'metadata-refresh') { const queued = Number(result.queued ?? (typeof result === 'number' ? result : 0)); return `${queued} metadata item${queued === 1 ? '' : 's'} queued`; } if (run.task_id === 'payload-sync' && Array.isArray(result)) { const failed = result.filter(item => item?.error).length; return `${result.length} source${result.length === 1 ? '' : 's'} checked${failed ? ` · ${failed} error${failed === 1 ? '' : 's'}` : ''}`; } if (run.task_id === 'cheat-sync') { const indexed = Number(result.ecosystem?.total || 0); const cached = Number(result.legacy?.total || 0); const failed = Number(result.legacy?.failed || 0) + Number(result.ecosystem?.errors?.length || 0); return `${indexed} modern cheats or patches indexed · ${cached} legacy items cached${failed ? ` · ${failed} error${failed === 1 ? '' : 's'}` : ''}`; } if (run.task_id === 'storage-audit') { const summary = result.summary || {}; const issues = Number(summary.missingBackups || 0) + Number(summary.partialFiles || 0) + Number(summary.orphanGameFolders || 0); return `${Number(summary.games || 0)} games · ${Number(summary.backups || 0)} backups · ${issues} attention item${issues === 1 ? '' : 's'}`; } if (run.task_id === 'recovery-snapshot') { const localFiles = Array.isArray(result.files) ? result.files.length : 0; const remoteFiles = Array.isArray(result.remote) ? result.remote.filter(item => item.included).length : 0; return `${localFiles} local and ${remoteFiles} PS5 files secured`; } const values = Object.entries(result).filter(([, value]) => ['string', 'number', 'boolean'].includes(typeof value)).slice(0, 3); return values.length ? values.map(([key, value]) => `${key}: ${value}`).join(' · ') : 'Completed'; } function operationsStatusBadge(status) { const labels = { completed: 'Succeeded', failed: 'Failed', running: 'Running', compatible: 'Compatible', warning: 'Warning', unknown: 'Unknown' }; return `${escHtml(labels[status] || status || 'Unknown')}`; } async function operationsAction(key, button, task) { if (_operationsBusy.has(key)) return null; _operationsBusy.add(key); const prior = button?.textContent; if (button) { button.disabled = true; button.textContent = window.t('common.loading', 'Loading…'); } try { return await task(); } finally { _operationsBusy.delete(key); if (button) { button.disabled = false; button.textContent = prior; } } } function renderOperationsAutomation(data) { _operationsStatus = data; const scheduler = $('operations-scheduler-state'); scheduler.textContent = data.running ? 'Active' : 'Stopped'; scheduler.className = `badge ${data.running ? 'badge-green' : 'badge-red'}`; const tasks = data.tasks || []; $('operations-task-list').innerHTML = tasks.map(task => `
${escHtml(task.label)}${task.running ? 'Running now' : task.enabled ? `Next: ${operationsDate(task.nextRunAt)}` : 'Disabled'}
`).join('') || '

No tasks configured.

'; const runs = (data.recentRuns || []).slice(0, 25); $('operations-run-list').innerHTML = runs.map(run => { const summary = operationsRunSummary(run); return `
${escHtml(tasks.find(task => task.id === run.task_id)?.label || run.task_id)}${escHtml(run.trigger || 'manual')} · ${operationsDate(run.started_at)}
${operationsStatusBadge(run.status)} ${operationsDuration(run.duration_ms)} ${escHtml(summary)}
`; }).join('') || '

No runs yet.

'; const failed = runs.filter(run => run.status === 'failed').length; const health = $('operations-health'); health.textContent = failed ? window.tf('operations.status.failures', '{count} recent failure(s)', { count: failed }) : window.t('operations.status.healthy', 'Automation healthy'); health.className = `operations-health ${failed ? 'warning' : 'healthy'}`; const badge = $('nav-operations-badge'); if (badge) { badge.hidden = failed === 0; badge.textContent = failed || ''; } } function renderOperationsStorage(report) { _operationsStorageReport = report; if (!report) { $('operations-storage-summary').innerHTML = ''; $('operations-storage-details').innerHTML = '

No storage report available yet.

'; return; } const summary = report.summary || {}; const metrics = [ [window.t('operations.storage.metrics.mounts', 'Mounts'), summary.roots], [window.t('operations.storage.metrics.games', 'Games'), summary.games], [window.t('operations.storage.metrics.backups', 'Backups'), summary.backups], [window.t('operations.storage.metrics.missing', 'Missing'), summary.missingBackups], [window.t('operations.storage.metrics.duplicates', 'Duplicate games'), summary.duplicateGames], [window.t('operations.storage.metrics.partial', 'Partial files'), summary.partialFiles], [window.t('operations.storage.metrics.orphaned', 'Orphaned folders'), summary.orphanGameFolders], ]; $('operations-storage-summary').innerHTML = metrics.map(([label, value]) => `
${Number(value || 0)}${escHtml(label)}
`).join(''); const sections = []; if ((report.roots || []).length) sections.push(`

${escHtml(window.t('operations.storage.volumes', 'Volumes'))}

${report.roots.map(root => `
${escHtml(root.path)}${root.error ? escHtml(root.error) : `${operationsBytes(root.availableBytes)} ${escHtml(window.t('operations.storage.freeOf', 'free of'))} ${operationsBytes(root.totalBytes)}`}
`).join('')}
`); const issue = (title, items, render) => { if (!items?.length) return; sections.push(`
${escHtml(title)} ${items.length}${items.slice(0, 100).map(render).join('')}${items.length > 100 ? `

${escHtml(window.t('operations.storage.first100', 'Only the first 100 items are shown.'))}

` : ''}
`); }; issue(window.t('operations.storage.issues.missing', 'Missing backups'), report.missingBackups, item => `
${escHtml(item.gameId)}${escHtml(item.path)}
`); issue(window.t('operations.storage.issues.duplicates', 'Duplicate backup registrations'), report.duplicateBackups, item => `
${escHtml(item.gameId)} (${item.count})${item.paths.map(escHtml).join('
')}
`); issue(window.t('operations.storage.issues.partial', 'Partial files'), report.partialFiles, item => `
${escHtml(item)}
`); issue(window.t('operations.storage.issues.orphaned', 'Orphaned game folders'), report.orphanGameFolders, item => `
${escHtml(item.gameId)}${escHtml(item.path)}
`); if (!report.missingBackups?.length && !report.partialFiles?.length && !report.orphanGameFolders?.length) sections.push(`
✓ ${escHtml(window.t('operations.storage.healthy', 'No missing backups, partial files or orphaned game folders found.'))}
`); $('operations-storage-details').innerHTML = `

${escHtml(window.t('operations.storage.reportAt', 'Report from'))} ${operationsDate(report.generatedAt)}

${sections.join('')}`; } function renderStorageTopology(packet) { const target = $('operations-storage-topology'); if (!target || !packet) return; _operationsStorageTopology = packet; target.innerHTML = `

${escHtml(window.t('operations.storage.consoleSource', 'Console source'))}: ${escHtml(packet.provenance?.console || 'unknown')} · ${escHtml(window.t('operations.storage.serverSource', 'server source'))}: ${escHtml(packet.provenance?.server || 'unknown')}

${(packet.volumes || []).map(volume => `
${escHtml(volume.scope === 'console' ? `PS5 ${volume.kind}` : volume.mountpoint)}${escHtml(volume.mountpoint || 'unknown')} · ${escHtml(volume.filesystem || 'unknown')} · ${volume.freeBytes == null ? escHtml(window.t('operations.storage.freeUnknown', 'free space unknown')) : `${operationsBytes(volume.freeBytes)} ${escHtml(window.t('operations.storage.free', 'free'))}`} · ${volume.totalBytes == null ? escHtml(window.t('operations.storage.totalUnknown', 'total unknown')) : operationsBytes(volume.totalBytes)}
${operationsStatusBadge(volume.status === 'unknown' || volume.source === 'unavailable' ? 'unknown' : volume.readOnly === true ? 'warning' : 'compatible')}
`).join('')}
`; } async function loadStorageTopology() { if (!window.pork.consoleStorageTopology) return; renderStorageTopology(await window.pork.consoleStorageTopology()); } function renderOperationsRecovery(snapshots) { $('operations-recovery-list').innerHTML = (snapshots || []).map(snapshot => { const included = (snapshot.remote || []).filter(item => item.included).length; return `
↺
${escHtml(snapshot.label || 'Restore point')}${operationsDate(snapshot.createdAt)} · Chimera ${escHtml(snapshot.chimeraVersion || '?')} · ${included} remote files · ${snapshot.integrity === 'sha256' ? 'SHA-256' : 'legacy verification'}${escHtml(snapshot.id)}
`; }).join('') || '
No restore points yetCreate a restore point before making major changes.
'; } function operationsAuditFilters() { const since = $('operations-audit-since').value; const until = $('operations-audit-until').value; return { level: $('operations-audit-level').value, classification: $('operations-audit-classification').value, result: $('operations-audit-result').value, search: $('operations-audit-search').value.trim(), since: since ? new Date(since).getTime() : null, until: until ? new Date(until).getTime() : null, limit: 200, }; } function renderOperationsAudit(result, health) { const summary = result.summary || {}; $('operations-audit-summary').innerHTML = [ ['Matched', result.totalMatched || 0], ['Succeeded', summary.success || 0], ['Failed', summary.failed || 0], ['Sensitive', summary.sensitive || 0], ['Destructive', summary.destructive || 0], ['Errors', summary.errors || 0], ].map(([label, value]) => `
${Number(value)}${escHtml(label)}
`).join(''); const files = health?.files || []; $('operations-audit-health').textContent = `${files.length} log file(s) · ${operationsBytes(health?.totalBytes)} · rotation ${operationsBytes(health?.retention?.maxBytes)} × ${Number(health?.retention?.maxFiles || 0)} · secrets redacted at rest`; $('operations-audit-list').innerHTML = (result.entries || []).map(entry => `
${escHtml(entry.command || entry.event || entry.level)}${operationsDate(entry.time)}${entry.requestIp ? ` · ${escHtml(entry.requestIp)}` : ''}
${operationsStatusBadge(entry.ok === true ? 'completed' : entry.ok === false ? 'failed' : 'unknown')} ${escHtml(entry.classification || entry.level)} ${escHtml(entry.error || (entry.durationMs == null ? entry.event : `${entry.event} · ${operationsDuration(entry.durationMs)}`))}
`).join('') || '
No audit recordsNo records match these filters.
'; } async function loadOperationsAudit() { const [result, health] = await Promise.all([window.pork.auditQuery(operationsAuditFilters()), window.pork.auditHealth()]); renderOperationsAudit(result, health); return result; } function renderOperationsCompatibility(data) { const profile = data?.profile || {}; $('operations-profile-firmware').value = profile.firmware || ''; $('operations-profile-hen').value = profile.hen || ''; $('operations-profile-hen-version').value = profile.henVersion || ''; $('operations-profile-kstuff').value = profile.kstuffVersion || ''; $('operations-profile-loader').value = profile.loader || ''; $('operations-profile-notes').value = profile.notes || ''; $('operations-compatibility-checks').innerHTML = (data?.checks || []).map(check => `
${escHtml(check.label)}${escHtml(check.detail)}
${operationsStatusBadge(check.status)}
`).join('') || '

No checks available.

'; } function renderReplicationStatus(status) { const targetState = status.available && status.writable ? 'Available and writable' : status.error || 'Unavailable'; const free = status.storage ? `${operationsBytes(status.storage.availableBytes)} free` : 'free space unknown'; const protection = status.protection || {}; const rpo = protection.effectiveRpoSeconds == null ? 'not established' : protection.effectiveRpoSeconds < 3600 ? `${Math.ceil(protection.effectiveRpoSeconds / 60)} min` : `${Math.round(protection.effectiveRpoSeconds / 360) / 10} h`; const rto = protection.lastRestoreDrillDurationMs == null ? 'not measured' : `${Math.max(1, Math.round(protection.lastRestoreDrillDurationMs / 1000))} s drill`; $('operations-replication-status').innerHTML = `
${escHtml(targetState)}${escHtml(status.target || '/replication')}${escHtml(free)}${status.running ? ' · replication running' : ''}
${protection.threeTwoOneSatisfied ? 'Met' : 'Not met'}3-2-1 status
${escHtml(rpo)}Effective RPO
${escHtml(rto)}Observed restore drill
${protection.immutableObserved ? 'Yes' : 'No'}Immutable target
Target contract: ${escHtml(status.targetContract?.type || 'unknown')}

Atomic: ${status.targetContract?.atomicPublish ? 'yes' : 'unknown'} · hashes: ${status.targetContract?.hashVerified ? 'yes' : 'unknown'} · immutable: ${status.targetContract?.immutable ? 'yes' : 'no'} · off-host: ${escHtml(status.targetContract?.offHost || 'unknown')}

${protection.blockers?.length ? `` : ''}
`; $('operations-replication-retention-enabled').checked = status.retention?.enabled !== false; $('operations-replication-retention-count').value = status.retention?.maxSnapshots || 14; $('operations-replication-retention-days').value = status.retention?.maxAgeDays || 180; $('operations-replication-run').disabled = !status.available || !status.writable || status.running; $('operations-replication-list').innerHTML = (status.snapshots || []).map(snapshot => snapshot.invalid ? `
Invalid snapshot${escHtml(snapshot.id)}${escHtml(snapshot.error)}
` : `
${escHtml(snapshot.label || 'Replication snapshot')}${operationsDate(snapshot.completedAt || snapshot.createdAt)} · ${Number(snapshot.files || 0)} files · ${operationsBytes(snapshot.bytes)}${snapshot.verifiedAt ? ` · verified ${operationsDate(snapshot.verifiedAt)}` : ''}${snapshot.lastRestoreDrillAt ? ` · restore drill ${operationsDate(snapshot.lastRestoreDrillAt)}` : ''}${escHtml(snapshot.id)}
`).join('') || '

No replication snapshots yet.

'; } function renderMonitoring(value) { const metrics = [ ['Uptime', `${Math.round(Number(value.uptimeSeconds || 0) / 60)} min.`], ['Memory', operationsBytes(value.memory?.rssBytes)], ['Games', value.library?.totalGames || 0], ['Backups', value.library?.totalBackups || 0], ['FTP', value.ftpConnected ? 'Connected' : 'Offline'], ['Task failures', value.automation?.recentFailures || 0], ['Replications', value.replication?.snapshots || 0], ['API p95', `${Number(value.api?.p95DurationMs || 0)} ms`], ['Last scan', value.scans?.lastDurationMs == null ? 'not measured' : `${Number(value.scans.lastDurationMs)} ms`], ['Transfer / 24h', operationsBytes(value.transfers?.bytes)], ['Cache hit', `${Math.round(Number(value.transport?.staticCacheHitRatio || 0) * 100)}%`], ['WebSocket disconnects', Number(value.transport?.wsDisconnects || 0)], ]; $('operations-monitoring-summary').innerHTML = metrics.map(([label, result]) => `
${escHtml(result)}${escHtml(label)}
`).join(''); } function renderSessions(result) { $('operations-session-list').innerHTML = (result.sessions || []).map(session => `
${session.current ? 'This browser' : escHtml(session.userAgent || 'Unknown browser')}${escHtml(session.ip || 'unknown')} · last active ${operationsDate(session.lastSeenAt)} · expires ${operationsDate(session.expiresAt)}${escHtml(session.id)}
`).join('') || '

No active sessions.

'; } async function loadOperationsReliability({ focus = '' } = {}) { if (focus === 'security') { if (!window.pork.authSessions) { $('operations-session-list').innerHTML = '

Session management is available only in server mode.

'; return; } renderSessions(await window.pork.authSessions()); return; } if (!window.pork.backupReplicationStatus || !window.pork.authSessions) { $('operations-replication-status').innerHTML = '

Session and replication management are available only in server mode.

'; return; } const [replication, monitoring, token, sessions] = await Promise.all([ window.pork.backupReplicationStatus(), window.pork.monitoringSnapshot(), window.pork.monitoringTokenStatus(), window.pork.authSessions(), ]); renderReplicationStatus(replication); renderMonitoring(monitoring); $('operations-monitoring-token-state').textContent = token.configured ? window.t('operations.monitoring.tokenActive', 'Metrics token active · endpoint /metrics') : window.t('operations.monitoring.tokenDisabled', 'Metrics disabled until a token is created'); renderSessions(sessions); } async function loadOperations({ background = false } = {}) { const [status, storage, snapshots, compatibility, topology] = await Promise.all([ window.pork.operationsStatus(), window.pork.storageStatus().catch(() => null), window.pork.recoveryList().catch(() => []), window.pork.compatibilityGet().catch(() => ({ profile: {}, checks: [] })), window.pork.consoleStorageTopology?.().catch(() => null) || Promise.resolve(null), ]); renderOperationsAutomation(status); renderOperationsStorage(storage); renderOperationsRecovery(snapshots); renderOperationsCompatibility(compatibility); renderStorageTopology(topology); _operationsLoaded = true; if (!background) $('operations-health').title = window.tf('operations.status.updated', 'Updated {time}', { time: new Date().toLocaleTimeString(window.i18n?.current || 'en'), }); } async function loadOperationsShadowMount() { const result = await window.pork.shadowMountStatus(); const config = result.files?.['shadowmount-config'] || {}; const manual = result.files?.['shadowmount-manual'] || {}; $('operations-shadow-config').value = config.text || ''; $('operations-shadow-manual').value = manual.text || ''; for (const [element, file] of [[$('operations-shadow-config-state'), config], [$('operations-shadow-manual-state'), manual]]) { element.textContent = file.ok ? 'Loaded' : 'Unavailable'; element.className = `badge ${file.ok ? 'badge-green' : 'badge-red'}`; element.title = file.error || ''; } renderOperationsShadowMountModel(result.model || { summary: {}, entries: [], unknownStatusLines: [] }); renderOperationsShadowPreflight(result.preflight); } function renderOperationsShadowPreflight(preflight) { const target = $('operations-shadow-preflight'); if (!target) return; if (!preflight) { target.innerHTML = 'Configuration preflight unavailable

Load a reachable config.ini or run the check again while saving.

'; return; } const counts = preflight.counts || {}; const headline = !preflight.ok ? 'Save blocked' : preflight.requiresRiskConfirmation ? 'Explicit risk confirmation required' : 'Preflight passed'; const findings = (preflight.findings || []).map(item => `
  • ${escHtml(item.severity)}${item.line ? ` · line ${Number(item.line)}` : ''}${item.key ? ` · ${escHtml(item.key)}` : ''}: ${escHtml(item.message)}
  • `).join(''); target.innerHTML = `${escHtml(headline)}

    ${Number(counts.invalid || 0)} invalid · ${Number(counts.critical || 0)} critical · ${Number(counts.warning || 0)} warning(s) · ${Number(counts.unknown || 0)} unknown future key(s)

    ${findings ? `` : '

    No safety issues in the recognized configuration.

    '}`; } function renderOperationsShadowMountModel(model) { const summary = model.summary || {}; const entries = model.entries || []; const statusBadge = $('operations-shadow-status-state'); statusBadge.textContent = summary.invalid || summary.unknownStatusLines ? 'review needed' : `${summary.installed || 0} installed`; statusBadge.className = `badge ${summary.invalid || summary.unknownStatusLines ? 'badge-yellow' : 'badge-green'}`; $('operations-shadow-summary').innerHTML = [ ['Active sources', summary.active || 0], ['Installed', summary.installed || 0], ['Pending', summary.pending || 0], ['Deleted', summary.deleted || 0], ['Experimental', summary.experimental || 0], ['Library matches', summary.libraryMatches || 0], ].map(([label, value]) => `
    ${escHtml(value)}${escHtml(label)}
    `).join(''); const stateLabel = { installed: 'Installed', pending: 'Pending', deleted: 'Deleted', invalid: 'Invalid' }; const supportLabel = { recommended: 'Recommended', compatibility: 'Compatibility', experimental: 'Experimental', normal: 'Folder', unknown: 'Unknown' }; const rows = entries.map(entry => `
    ${escHtml(entry.titleName || entry.titleId || entry.sourceName || 'Unknown source')}${escHtml(entry.titleId || 'no title ID')} · ${escHtml(entry.format?.label || 'Unknown')} · ${escHtml(supportLabel[entry.format?.support] || entry.format?.support || 'Unknown')}${escHtml(entry.sourcePath)}${entry.warnings?.length ? `${entry.warnings.map(escHtml).join(' ')}` : ''}
    ${escHtml(stateLabel[entry.state] || entry.state)}
    `); if (model.unknownStatusLines?.length) rows.push(`
    ${model.unknownStatusLines.length} unknown status line(s)Preserved because the upstream format was not recognized.${escHtml(model.unknownStatusLines.map(item => `line ${item.line}: ${item.text}`).join('\n'))}
    Raw
    `); $('operations-shadow-entries').innerHTML = rows.length ? rows.join('') : '

    manual.lst and manual.status do not contain game sources yet.

    '; } async function loadOperationsPackages() { const result = await window.pork.packagesStatus(); $('operations-package-services').innerHTML = (result.services || []).map(service => `
    ${escHtml(service.label)}Port ${service.port}${service.error ? ` · ${escHtml(service.error)}` : ''}
    ${service.reachable ? 'Online' : 'Offline'}
    `).join(''); return result; } function showOperationsJson(element, value) { element.hidden = false; element.textContent = typeof value === 'string' ? value : JSON.stringify(value, null, 2); } function klogFilters() { return { severity: $('operations-klog-severity').value, search: $('operations-klog-search').value.trim(), limit: 200 }; } function renderKlogStatus(status) { const badge = $('operations-klog-state'); badge.textContent = status.running ? status.connected ? 'Capture active' : 'Connecting…' : 'Stopped'; badge.className = `badge ${status.running ? status.connected ? 'badge-green' : 'badge-yellow' : ''}`; $('operations-klog-start').disabled = status.running || !status.available; $('operations-klog-stop').disabled = !status.running; $('operations-klog-summary').innerHTML = [ ['Port', status.port || 3232], ['Status', status.reason || 'unknown'], ['Lines', status.lines || 0], ['Received', operationsBytes(status.bytes)], ['Write contract', status.zeroWriteContract ? '0 bytes' : 'unknown'], ['Payload started', status.payloadLaunchAttempted ? 'yes' : 'no'], ].map(([label, value]) => `
    ${escHtml(value)}${escHtml(label)}
    `).join(''); } function renderKlogEntries(result) { $('operations-klog-list').innerHTML = (result.entries || []).map(entry => `
    ${escHtml(entry.severity.toUpperCase())}${operationsDate(entry.time)}
    ${operationsStatusBadge(entry.severity === 'error' ? 'failed' : entry.severity === 'warn' ? 'warning' : 'completed')} ${escHtml(String(entry.sessionId || '').slice(0, 8))} ${escHtml(entry.line)}
    `).join('') || '

    No kernel log records match these filters.

    '; } async function loadKlog() { const [status, result] = await Promise.all([window.pork.klogStatus(), window.pork.klogQuery(klogFilters())]); renderKlogStatus(status); renderKlogEntries(result); } document.querySelectorAll('.operations-tab').forEach(button => button.addEventListener('click', async () => { const tab = button.dataset.operationsTab; document.querySelectorAll('.operations-tab').forEach(item => item.classList.toggle('active', item === button)); document.querySelectorAll('.operations-panel').forEach(panel => { const active = panel.dataset.operationsPanel === tab; panel.classList.toggle('active', active); panel.hidden = !active; }); try { if (tab === 'shadowmount') await loadOperationsShadowMount(); if (tab === 'storage') await loadStorageTopology(); if (tab === 'packages') await loadOperationsPackages(); if (tab === 'reliability') await loadOperationsReliability(); if (tab === 'audit') await loadOperationsAudit(); if (tab === 'diagnostics') await loadKlog(); } catch (error) { showToast(error.message, 'error'); } })); $('operations-refresh')?.addEventListener('click', event => operationsAction('refresh', event.currentTarget, () => loadOperations()).catch(error => showToast(error.message, 'error'))); $('operations-task-list')?.addEventListener('click', async event => { const row = event.target.closest('.operations-task'); if (!row) return; const taskId = row.dataset.taskId; try { if (event.target.closest('.operations-task-run')) { await operationsAction(`run-${taskId}`, event.target.closest('button'), () => window.pork.operationsRunTask(taskId)); showToast('Task completed.', 'ok'); await loadOperations(); } if (event.target.closest('.operations-task-save')) { await operationsAction(`save-${taskId}`, event.target.closest('button'), () => window.pork.operationsConfigureTask(taskId, { enabled: row.querySelector('.operations-task-enabled').checked, intervalMinutes: Number(row.querySelector('.operations-task-interval').value), })); showToast('Schedule saved.', 'ok'); await loadOperations(); } } catch (error) { showToast(error.message, 'error'); } }); $('operations-storage-run')?.addEventListener('click', async event => { try { const report = await operationsAction('storage', event.currentTarget, () => window.pork.storageAudit()); renderOperationsStorage(report); showToast('Storage audit completed. Nothing was deleted.', 'ok'); } catch (error) { showToast(error.message, 'error'); } }); $('operations-storage-topology-refresh')?.addEventListener('click', event => operationsAction('storage-topology', event.currentTarget, loadStorageTopology).catch(error => showToast(error.message, 'error'))); $('operations-recovery-create')?.addEventListener('click', async event => { const label = await showPrompt('Name for this restore point:', 'Manual restore point'); if (label == null) return; try { await operationsAction('recovery-create', event.currentTarget, () => window.pork.recoveryCreate({ label, includeRemote: true })); renderOperationsRecovery(await window.pork.recoveryList()); showToast('Restore point saved safely.', 'ok'); } catch (error) { showToast(error.message, 'error'); } }); $('operations-recovery-import')?.addEventListener('click', async event => { try { const result = await operationsAction('recovery-import', event.currentTarget, () => window.pork.recoveryImport()); if (!result) return; renderOperationsRecovery(await window.pork.recoveryList()); showToast(`Archive imported as ${result.id} and verified with SHA-256. Nothing was restored.`, 'ok', 9000); } catch (error) { showToast(error.message, 'error', 10000); } }); $('operations-recovery-list')?.addEventListener('click', async event => { const row = event.target.closest('.operations-recovery-item'); if (!row) return; const id = row.dataset.snapshotId; try { if (event.target.closest('.operations-recovery-verify')) { const verified = await operationsAction(`verify-${id}`, event.target.closest('button'), () => window.pork.recoveryVerify(id)); showToast(`${verified.files.length} files verified (${verified.integrity}). Nothing was restored.`, 'ok'); } if (event.target.closest('.operations-recovery-export')) await operationsAction(`export-${id}`, event.target.closest('button'), () => window.pork.recoveryExport(id)); if (event.target.closest('.operations-recovery-restore')) { if (!await showExactConfirmation('This restores the database, settings and available PS5 configuration. A new safety recovery point is created first.', 'RESTORE')) return; await operationsAction(`restore-${id}`, event.target.closest('button'), () => window.pork.recoveryRestore(id, true)); showToast('Restore completed. Refresh the page if settings are not immediately visible.', 'ok', 8000); await loadOperations(); } if (event.target.closest('.operations-recovery-delete')) { if (!await showExactConfirmation('Permanently delete this recovery point?', 'DELETE')) return; await operationsAction(`delete-${id}`, event.target.closest('button'), () => window.pork.recoveryDelete(id)); renderOperationsRecovery(await window.pork.recoveryList()); } } catch (error) { showToast(error.message, 'error'); } }); $('operations-replication-refresh')?.addEventListener('click', event => operationsAction('replication-refresh', event.currentTarget, loadOperationsReliability).catch(error => showToast(error.message, 'error'))); $('operations-replication-run')?.addEventListener('click', async event => { const includeGameBackups = $('operations-replication-games').checked; const warning = includeGameBackups ? '\n\nFull game backups are selected and may use a large amount of storage.' : ''; if (!await showConfirm(`Create a new hash-verified snapshot on the secondary backup target? Nothing is removed from the source.${warning}`)) return; try { const label = `Manual replication ${new Date().toLocaleString(window.i18n?.current || 'en')}`; await operationsAction('replication-run', event.currentTarget, () => window.pork.backupReplicationRun({ includeGameBackups, label })); await loadOperationsReliability(); showToast('Replication completed and every copied file was verified.', 'ok', 8000); } catch (error) { showToast(error.message, 'error', 10000); } }); $('operations-replication-list')?.addEventListener('click', async event => { const row = event.target.closest('[data-replication-id]'); if (!row) return; try { const button = event.target.closest('button'); if (button?.classList.contains('operations-replication-verify')) { await operationsAction(`replication-verify-${row.dataset.replicationId}`, button, () => window.pork.backupReplicationVerify(row.dataset.replicationId)); showToast('Snapshot fully verified with SHA-256.', 'ok'); } else if (button?.classList.contains('operations-replication-drill')) { if (!await showConfirm('Copy every snapshot file to a temporary local destination, verify its hash, then remove the temporary copies? The PS5 is not modified.')) return; const result = await operationsAction(`replication-drill-${row.dataset.replicationId}`, button, () => window.pork.backupReplicationRestoreDrill(row.dataset.replicationId)); showToast(`Restore drill succeeded: ${result.files} files, PS5 unchanged.`, 'ok', 8000); } else return; await loadOperationsReliability(); } catch (error) { showToast(error.message, 'error', 10000); } }); $('operations-replication-retention')?.addEventListener('submit', async event => { event.preventDefault(); try { const preview = await operationsAction('replication-retention', event.submitter, () => window.pork.backupReplicationRetentionSave({ enabled: $('operations-replication-retention-enabled').checked, maxSnapshots: Number($('operations-replication-retention-count').value), maxAgeDays: Number($('operations-replication-retention-days').value) })); showToast(`Retention saved. ${preview.removable?.length || 0} snapshot(s) will be eligible after the next successful replication.`, 'ok', 9000); await loadOperationsReliability(); } catch (error) { showToast(error.message, 'error'); } }); $('operations-monitoring-refresh')?.addEventListener('click', event => operationsAction('monitoring-refresh', event.currentTarget, async () => renderMonitoring(await window.pork.monitoringSnapshot())).catch(error => showToast(error.message, 'error'))); $('operations-monitoring-token')?.addEventListener('click', async event => { if (!await showConfirm('Create a new metrics token? An existing token becomes invalid immediately.')) return; try { const result = await operationsAction('metrics-token', event.currentTarget, () => window.pork.monitoringTokenRotate()); $('operations-monitoring-token-state').textContent = 'New token active. Save it now; Chimera will not show it again.'; showOperationsJson($('operations-monitoring-token-output'), `Endpoint: ${location.origin}${result.endpoint}\nAuthorization: Bearer ${result.token}\n\nThis token is shown only once.`); } catch (error) { showToast(error.message, 'error'); } }); $('operations-sessions-refresh')?.addEventListener('click', event => operationsAction('sessions-refresh', event.currentTarget, async () => renderSessions(await window.pork.authSessions())).catch(error => showToast(error.message, 'error'))); $('operations-session-list')?.addEventListener('click', async event => { const row = event.target.closest('[data-session-id]'); if (!row || !event.target.closest('.operations-session-revoke') || !await showExactConfirmation('Revoke this session immediately?', 'REVOKE')) return; try { const result = await window.pork.authSessionRevoke(row.dataset.sessionId); if (result.current) { location.replace('/server-login.html'); return; } renderSessions(await window.pork.authSessions()); showToast('Session revoked.', 'ok'); } catch (error) { showToast(error.message, 'error'); } }); $('operations-password-form')?.addEventListener('submit', async event => { event.preventDefault(); const currentPassword = $('operations-password-current').value; const newPassword = $('operations-password-new').value; if (newPassword !== $('operations-password-repeat').value) { showToast('The new passwords do not match.', 'error'); return; } if (!await showConfirm('Change the password and revoke all other active sessions?')) return; try { const result = await operationsAction('password-change', event.submitter, () => window.pork.authPasswordChange(currentPassword, newPassword)); event.currentTarget.reset(); showToast(`Password changed; ${result.otherSessionsRevoked} other session(s) revoked.`, 'ok', 8000); renderSessions(await window.pork.authSessions()); } catch (error) { showToast(error.message, 'error'); } }); $('operations-profile-save')?.addEventListener('click', async event => { try { const result = await operationsAction('profile', event.currentTarget, () => window.pork.compatibilitySet({ firmware: $('operations-profile-firmware').value, hen: $('operations-profile-hen').value, henVersion: $('operations-profile-hen-version').value, kstuffVersion: $('operations-profile-kstuff').value, loader: $('operations-profile-loader').value, notes: $('operations-profile-notes').value, })); renderOperationsCompatibility(result); showToast('PS5 profile saved.', 'ok'); } catch (error) { showToast(error.message, 'error'); } }); $('operations-shadow-reload')?.addEventListener('click', event => operationsAction('shadow-load', event.currentTarget, loadOperationsShadowMount).catch(error => showToast(error.message, 'error'))); $('operations-shadow-propose')?.addEventListener('click', async event => { try { const proposal = await operationsAction('shadow-proposal', event.currentTarget, () => window.pork.shadowMountProposal()); $('operations-shadow-manual').value = proposal.proposedManual; $('operations-shadow-preflight').innerHTML = `Library proposal · ${proposal.additions.length} addition(s)

    ${proposal.unchanged.length} unchanged · ${proposal.unresolved.length} unresolved. Nothing was written to the PS5.

    ${proposal.unresolved.length ? `` : ''}`; showToast(proposal.additions.length ? 'ShadowMount proposal loaded for review; save is still separate.' : 'No new PS5-visible paths were found.', proposal.unresolved.length ? 'info' : 'ok', 9000); } catch (error) { showToast(error.message, 'error', 10000); } }); $('operations-shadow-save')?.addEventListener('click', async event => { try { const config = $('operations-shadow-config').value; const manual = $('operations-shadow-manual').value; const preflight = await operationsAction('shadow-preflight', event.currentTarget, () => window.pork.shadowMountPreflight(config)); renderOperationsShadowPreflight(preflight); if (!preflight.ok) throw new Error('The configuration contains invalid values. Resolve the preflight findings before saving.'); let confirmation = null; if (preflight.requiresRiskConfirmation) { confirmation = await showExactConfirmation('This configuration activates critical ShadowMount options. Review the preflight carefully.', preflight.confirmationPhrase); if (!confirmation) return; } else if (!await showConfirm('Save the ShadowMount configuration? Chimera creates a recovery point first.')) return; const result = await operationsAction('shadow-save', event.currentTarget, () => window.pork.shadowMountSave({ config, manual, confirmation })); showToast(`ShadowMount saved. Safety restore point: ${result.snapshotId}`, 'ok', 8000); await loadOperationsShadowMount(); } catch (error) { showToast(error.message, 'error'); } }); $('operations-shadow-logs')?.addEventListener('click', async event => { try { const result = await operationsAction('shadow-logs', event.currentTarget, () => window.pork.shadowMountLogs()); if (!result.ok) throw new Error(result.error); showOperationsJson($('operations-shadow-output'), result.text || 'debug.log is empty.'); } catch (error) { showToast(error.message, 'error'); } }); $('operations-package-probe')?.addEventListener('click', event => operationsAction('package-probe', event.currentTarget, loadOperationsPackages).catch(error => showToast(error.message, 'error'))); $('operations-package-list')?.addEventListener('click', async event => { try { showOperationsJson($('operations-package-output'), await operationsAction('package-list', event.currentTarget, () => window.pork.packagesList())); } catch (error) { showToast(error.message, 'error'); } }); $('operations-package-progress')?.addEventListener('click', async event => { try { showOperationsJson($('operations-package-output'), await operationsAction('package-progress', event.currentTarget, () => window.pork.packagesProgress())); } catch (error) { showToast(error.message, 'error'); } }); $('operations-package-install')?.addEventListener('click', async event => { const url = $('operations-package-url').value.trim(); if (!url || !await showConfirm(`Send this HTTPS URL to etaHEN DPI?\n\n${url}`)) return; try { const result = await operationsAction('package-install', event.currentTarget, () => window.pork.packagesInstallUrl(url)); showOperationsJson($('operations-package-output'), result); showToast('Package URL sent to etaHEN DPI.', 'ok'); } catch (error) { showToast(error.message, 'error'); } }); $('operations-diagnostics-run')?.addEventListener('click', async event => { try { const result = await operationsAction('diagnostics', event.currentTarget, () => window.pork.diagnosticsSummary()); const services = result.packages?.services || []; const build = result.chimera?.build || {}; $('operations-diagnostics-summary').innerHTML = [ ['Chimera', result.chimera?.version || '—'], ['Build', build.id || 'development'], ['Revision', String(build.revision || 'development').slice(0, 12)], ['Built', build.builtAt ? operationsDate(build.builtAt) : '—'], ['Uptime', `${Math.round(Number(result.chimera?.uptimeSeconds || 0) / 60)} min.`], ['FTP', result.ps5?.ftpConnected ? 'Connected' : result.ps5?.ftpReconnectAvailable ? 'Reconnect available' : 'Not connected'], ['PS5 services', `${services.filter(item => item.reachable).length}/${services.length} online`], ].map(([label, value]) => `
    ${escHtml(value)}${escHtml(label)}
    `).join(''); showOperationsJson($('operations-diagnostics-log'), result.recentLogs || []); } catch (error) { showToast(error.message, 'error'); } }); $('operations-diagnostics-export')?.addEventListener('click', event => operationsAction('diagnostics-export', event.currentTarget, () => window.pork.diagnosticsExport()).then(() => showToast('Geschoonde supportbundel aangemaakt.', 'ok')).catch(error => showToast(error.message, 'error'))); $('operations-audit-filter')?.addEventListener('submit', event => { event.preventDefault(); operationsAction('audit-filter', event.submitter, loadOperationsAudit).catch(error => showToast(error.message, 'error')); }); $('operations-audit-refresh')?.addEventListener('click', event => operationsAction('audit-refresh', event.currentTarget, loadOperationsAudit).catch(error => showToast(error.message, 'error'))); $('operations-audit-export')?.addEventListener('click', event => operationsAction('audit-export', event.currentTarget, () => window.pork.auditExport(operationsAuditFilters())).then(() => showToast('Gefilterde auditexport aangemaakt.', 'ok')).catch(error => showToast(error.message, 'error'))); $('operations-klog-start')?.addEventListener('click', async event => { try { const status = await operationsAction('klog-start', event.currentTarget, () => window.pork.klogStart({ durationSeconds: Number($('operations-klog-duration').value) })); renderKlogStatus(status); showToast('Passive capture started; Chimera writes nothing to the PS5.', 'ok'); } catch (error) { showToast(error.message, 'error'); } }); $('operations-klog-stop')?.addEventListener('click', async event => { try { renderKlogStatus(await operationsAction('klog-stop', event.currentTarget, () => window.pork.klogStop())); await loadKlog(); } catch (error) { showToast(error.message, 'error'); } }); $('operations-klog-refresh')?.addEventListener('click', event => operationsAction('klog-refresh', event.currentTarget, loadKlog).catch(error => showToast(error.message, 'error'))); $('operations-klog-export')?.addEventListener('click', event => operationsAction('klog-export', event.currentTarget, () => window.pork.klogExport(klogFilters())).then(() => showToast('Geschoonde kernellogexport aangemaakt.', 'ok')).catch(error => showToast(error.message, 'error'))); window.pork?.on?.('operations:task-update', () => { if (document.querySelector('#page-operations.active')) loadOperations().catch(() => {}); }); window.pork?.on?.('klog:status', status => renderKlogStatus(status)); window.addEventListener('chimera:language-changed', () => { if (!document.querySelector('#page-operations.active [data-operations-panel="storage"]:not([hidden])')) return; renderOperationsStorage(_operationsStorageReport); if (_operationsStorageTopology) renderStorageTopology(_operationsStorageTopology); });