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

+700
View File
@@ -0,0 +1,700 @@
'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 `<span class="operations-status operations-status-${escHtml(status || 'unknown')}">${escHtml(labels[status] || status || 'Unknown')}</span>`;
}
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 => `
<div class="operations-task" data-task-id="${escHtml(task.id)}">
<div class="operations-task-main">
<span class="operations-task-indicator ${task.running ? 'running' : task.enabled ? 'enabled' : ''}"></span>
<div><strong>${escHtml(task.label)}</strong><span>${task.running ? 'Running now' : task.enabled ? `Next: ${operationsDate(task.nextRunAt)}` : 'Disabled'}</span></div>
</div>
<label class="operations-switch"><input class="operations-task-enabled" type="checkbox" ${task.enabled ? 'checked' : ''}/><span>Active</span></label>
<label class="operations-interval"><input class="operations-task-interval" type="number" min="5" max="10080" value="${Number(task.intervalMinutes)}"/><span>min.</span></label>
<button class="btn btn-sm operations-task-save">Save</button>
<button class="btn btn-sm btn-accent operations-task-run" ${task.running ? 'disabled' : ''}>Run now</button>
</div>`).join('') || '<p class="hint">No tasks configured.</p>';
const runs = (data.recentRuns || []).slice(0, 25);
$('operations-run-list').innerHTML = runs.map(run => {
const summary = operationsRunSummary(run);
return `
<div class="operations-run">
<div><strong>${escHtml(tasks.find(task => task.id === run.task_id)?.label || run.task_id)}</strong><span>${escHtml(run.trigger || 'manual')} · ${operationsDate(run.started_at)}</span></div>
${operationsStatusBadge(run.status)}
<span>${operationsDuration(run.duration_ms)}</span>
<span class="operations-run-message" title="${escHtml(run.error || run.summary || '')}">${escHtml(summary)}</span>
</div>`;
}).join('') || '<p class="hint">No runs yet.</p>';
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 = '<p class="hint">No storage report available yet.</p>';
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]) => `<div class="operations-metric"><strong>${Number(value || 0)}</strong><span>${escHtml(label)}</span></div>`).join('');
const sections = [];
if ((report.roots || []).length) sections.push(`<div class="operations-audit-section"><h4>${escHtml(window.t('operations.storage.volumes', 'Volumes'))}</h4>${report.roots.map(root => `<div class="operations-audit-row"><code>${escHtml(root.path)}</code><span>${root.error ? escHtml(root.error) : `${operationsBytes(root.availableBytes)} ${escHtml(window.t('operations.storage.freeOf', 'free of'))} ${operationsBytes(root.totalBytes)}`}</span></div>`).join('')}</div>`);
const issue = (title, items, render) => {
if (!items?.length) return;
sections.push(`<details class="operations-audit-section"><summary>${escHtml(title)} <span>${items.length}</span></summary>${items.slice(0, 100).map(render).join('')}${items.length > 100 ? `<p class="hint">${escHtml(window.t('operations.storage.first100', 'Only the first 100 items are shown.'))}</p>` : ''}</details>`);
};
issue(window.t('operations.storage.issues.missing', 'Missing backups'), report.missingBackups, item => `<div class="operations-audit-row"><strong>${escHtml(item.gameId)}</strong><code>${escHtml(item.path)}</code></div>`);
issue(window.t('operations.storage.issues.duplicates', 'Duplicate backup registrations'), report.duplicateBackups, item => `<div class="operations-audit-row"><strong>${escHtml(item.gameId)} (${item.count})</strong><span>${item.paths.map(escHtml).join('<br>')}</span></div>`);
issue(window.t('operations.storage.issues.partial', 'Partial files'), report.partialFiles, item => `<div class="operations-audit-row"><code>${escHtml(item)}</code></div>`);
issue(window.t('operations.storage.issues.orphaned', 'Orphaned game folders'), report.orphanGameFolders, item => `<div class="operations-audit-row"><strong>${escHtml(item.gameId)}</strong><code>${escHtml(item.path)}</code></div>`);
if (!report.missingBackups?.length && !report.partialFiles?.length && !report.orphanGameFolders?.length) sections.push(`<div class="operations-empty-ok">✓ ${escHtml(window.t('operations.storage.healthy', 'No missing backups, partial files or orphaned game folders found.'))}</div>`);
$('operations-storage-details').innerHTML = `<p class="hint">${escHtml(window.t('operations.storage.reportAt', 'Report from'))} ${operationsDate(report.generatedAt)}</p>${sections.join('')}`;
}
function renderStorageTopology(packet) {
const target = $('operations-storage-topology');
if (!target || !packet) return;
_operationsStorageTopology = packet;
target.innerHTML = `<p class="hint">${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')}</p><div class="ecosystem-update-list">${(packet.volumes || []).map(volume => `<div class="ecosystem-service-row"><div><strong>${escHtml(volume.scope === 'console' ? `PS5 ${volume.kind}` : volume.mountpoint)}</strong><span>${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)}</span></div>${operationsStatusBadge(volume.status === 'unknown' || volume.source === 'unavailable' ? 'unknown' : volume.readOnly === true ? 'warning' : 'compatible')}</div>`).join('')}</div>`;
}
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 `<div class="operations-recovery-item" data-snapshot-id="${escHtml(snapshot.id)}">
<div class="operations-recovery-icon">↺</div>
<div class="operations-recovery-info"><strong>${escHtml(snapshot.label || 'Restore point')}</strong><span>${operationsDate(snapshot.createdAt)} · Chimera ${escHtml(snapshot.chimeraVersion || '?')} · ${included} remote files · ${snapshot.integrity === 'sha256' ? 'SHA-256' : 'legacy verification'}</span><code>${escHtml(snapshot.id)}</code></div>
<div class="operations-inline-actions"><button class="btn btn-sm operations-recovery-verify">Verify</button><button class="btn btn-sm operations-recovery-export">Download</button><button class="btn btn-sm operations-recovery-restore">Restore</button><button class="btn btn-sm btn-danger operations-recovery-delete">Delete</button></div>
</div>`;
}).join('') || '<div class="operations-empty"><strong>No restore points yet</strong><span>Create a restore point before making major changes.</span></div>';
}
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]) => `<div class="operations-metric"><strong>${Number(value)}</strong><span>${escHtml(label)}</span></div>`).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 => `<div class="operations-run">
<div><strong>${escHtml(entry.command || entry.event || entry.level)}</strong><span>${operationsDate(entry.time)}${entry.requestIp ? ` · ${escHtml(entry.requestIp)}` : ''}</span></div>
${operationsStatusBadge(entry.ok === true ? 'completed' : entry.ok === false ? 'failed' : 'unknown')}
<span>${escHtml(entry.classification || entry.level)}</span>
<span class="operations-run-message" title="${escHtml(entry.error || '')}">${escHtml(entry.error || (entry.durationMs == null ? entry.event : `${entry.event} · ${operationsDuration(entry.durationMs)}`))}</span>
</div>`).join('') || '<div class="operations-empty"><strong>No audit records</strong><span>No records match these filters.</span></div>';
}
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 => `<div class="operations-check"><div><strong>${escHtml(check.label)}</strong><span>${escHtml(check.detail)}</span></div>${operationsStatusBadge(check.status)}</div>`).join('') || '<p class="hint">No checks available.</p>';
}
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 = `
<div class="operations-audit-row"><strong>${escHtml(targetState)}</strong><code>${escHtml(status.target || '/replication')}</code><span>${escHtml(free)}${status.running ? ' · replication running' : ''}</span></div>
<div class="ecosystem-metrics">
<div class="operations-metric"><strong>${protection.threeTwoOneSatisfied ? 'Met' : 'Not met'}</strong><span>3-2-1 status</span></div>
<div class="operations-metric"><strong>${escHtml(rpo)}</strong><span>Effective RPO</span></div>
<div class="operations-metric"><strong>${escHtml(rto)}</strong><span>Observed restore drill</span></div>
<div class="operations-metric"><strong>${protection.immutableObserved ? 'Yes' : 'No'}</strong><span>Immutable target</span></div>
</div>
<div class="${protection.threeTwoOneSatisfied ? 'operations-empty-ok' : 'operations-warning'}"><strong>Target contract: ${escHtml(status.targetContract?.type || 'unknown')}</strong><p>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')}</p>${protection.blockers?.length ? `<ul>${protection.blockers.map(item => `<li>${escHtml(item)}</li>`).join('')}</ul>` : ''}</div>`;
$('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
? `<div class="operations-recovery-item"><div class="operations-recovery-info"><strong>Invalid snapshot</strong><code>${escHtml(snapshot.id)}</code><span>${escHtml(snapshot.error)}</span></div></div>`
: `<div class="operations-recovery-item" data-replication-id="${escHtml(snapshot.id)}"><div class="operations-recovery-info"><strong>${escHtml(snapshot.label || 'Replication snapshot')}</strong><span>${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)}` : ''}</span><code>${escHtml(snapshot.id)}</code></div><div class="operations-inline-actions"><button class="btn btn-sm operations-replication-verify">Verify SHA-256</button><button class="btn btn-sm operations-replication-drill">Local restore drill</button></div></div>`).join('') || '<p class="hint">No replication snapshots yet.</p>';
}
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]) => `<div class="operations-metric"><strong>${escHtml(result)}</strong><span>${escHtml(label)}</span></div>`).join('');
}
function renderSessions(result) {
$('operations-session-list').innerHTML = (result.sessions || []).map(session => `<div class="operations-recovery-item" data-session-id="${escHtml(session.id)}"><div class="operations-recovery-info"><strong>${session.current ? 'This browser' : escHtml(session.userAgent || 'Unknown browser')}</strong><span>${escHtml(session.ip || 'unknown')} · last active ${operationsDate(session.lastSeenAt)} · expires ${operationsDate(session.expiresAt)}</span><code>${escHtml(session.id)}</code></div><div class="operations-inline-actions"><button class="btn btn-sm btn-danger operations-session-revoke">${session.current ? 'Sign out' : 'Revoke'}</button></div></div>`).join('') || '<p class="hint">No active sessions.</p>';
}
async function loadOperationsReliability({ focus = '' } = {}) {
if (focus === 'security') {
if (!window.pork.authSessions) {
$('operations-session-list').innerHTML = '<p class="hint">Session management is available only in server mode.</p>';
return;
}
renderSessions(await window.pork.authSessions());
return;
}
if (!window.pork.backupReplicationStatus || !window.pork.authSessions) {
$('operations-replication-status').innerHTML = '<p class="hint">Session and replication management are available only in server mode.</p>';
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 = '<strong>Configuration preflight unavailable</strong><p>Load a reachable config.ini or run the check again while saving.</p>';
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 => `<li><strong>${escHtml(item.severity)}</strong>${item.line ? ` · line ${Number(item.line)}` : ''}${item.key ? ` · ${escHtml(item.key)}` : ''}: ${escHtml(item.message)}</li>`).join('');
target.innerHTML = `<strong>${escHtml(headline)}</strong><p>${Number(counts.invalid || 0)} invalid · ${Number(counts.critical || 0)} critical · ${Number(counts.warning || 0)} warning(s) · ${Number(counts.unknown || 0)} unknown future key(s)</p>${findings ? `<ul>${findings}</ul>` : '<p>No safety issues in the recognized configuration.</p>'}`;
}
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]) => `<div class="operations-metric"><strong>${escHtml(value)}</strong><span>${escHtml(label)}</span></div>`).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 => `<article class="operations-detail-row shadowmount-entry ${entry.warnings?.length ? 'has-warning' : ''}">
<div><strong>${escHtml(entry.titleName || entry.titleId || entry.sourceName || 'Unknown source')}</strong><span>${escHtml(entry.titleId || 'no title ID')} · ${escHtml(entry.format?.label || 'Unknown')} · ${escHtml(supportLabel[entry.format?.support] || entry.format?.support || 'Unknown')}</span><code>${escHtml(entry.sourcePath)}</code>${entry.warnings?.length ? `<small>${entry.warnings.map(escHtml).join(' ')}</small>` : ''}</div>
<span class="badge ${entry.state === 'installed' ? 'badge-green' : entry.state === 'deleted' || entry.state === 'invalid' ? 'badge-red' : 'badge-yellow'}">${escHtml(stateLabel[entry.state] || entry.state)}</span>
</article>`);
if (model.unknownStatusLines?.length) rows.push(`<article class="operations-detail-row has-warning"><div><strong>${model.unknownStatusLines.length} unknown status line(s)</strong><span>Preserved because the upstream format was not recognized.</span><code>${escHtml(model.unknownStatusLines.map(item => `line ${item.line}: ${item.text}`).join('\n'))}</code></div><span class="badge badge-yellow">Raw</span></article>`);
$('operations-shadow-entries').innerHTML = rows.length ? rows.join('') : '<p class="hint">manual.lst and manual.status do not contain game sources yet.</p>';
}
async function loadOperationsPackages() {
const result = await window.pork.packagesStatus();
$('operations-package-services').innerHTML = (result.services || []).map(service => `<div class="operations-service"><span class="operations-service-dot ${service.reachable ? 'online' : ''}"></span><div><strong>${escHtml(service.label)}</strong><span>Port ${service.port}${service.error ? ` · ${escHtml(service.error)}` : ''}</span></div><b>${service.reachable ? 'Online' : 'Offline'}</b></div>`).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]) => `<div class="operations-metric"><strong>${escHtml(value)}</strong><span>${escHtml(label)}</span></div>`).join('');
}
function renderKlogEntries(result) {
$('operations-klog-list').innerHTML = (result.entries || []).map(entry => `<div class="operations-run">
<div><strong>${escHtml(entry.severity.toUpperCase())}</strong><span>${operationsDate(entry.time)}</span></div>
${operationsStatusBadge(entry.severity === 'error' ? 'failed' : entry.severity === 'warn' ? 'warning' : 'completed')}
<span>${escHtml(String(entry.sessionId || '').slice(0, 8))}</span>
<span class="operations-run-message" title="${escHtml(entry.line)}">${escHtml(entry.line)}</span>
</div>`).join('') || '<p class="hint">No kernel log records match these filters.</p>';
}
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 = `<strong>Library proposal · ${proposal.additions.length} addition(s)</strong><p>${proposal.unchanged.length} unchanged · ${proposal.unresolved.length} unresolved. Nothing was written to the PS5.</p>${proposal.unresolved.length ? `<ul>${proposal.unresolved.slice(0, 20).map(item => `<li>${escHtml(item.gameId)} · ${escHtml(item.reason)}</li>`).join('')}</ul>` : ''}`;
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]) => `<div class="operations-metric"><strong>${escHtml(value)}</strong><span>${escHtml(label)}</span></div>`).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);
});