Files
peardock/ui/stats-settings.js
T
Raven Scott 637c386e33
Release rolling / release (push) Successful in 7m55s
Bring Back Realtime Table Stats
2026-08-04 22:37:41 -04:00

218 lines
6.6 KiB
JavaScript

/**
* Settings → Performance
* Server-side Docker stats (view-interest streams + push cadence).
*/
import { Methods } from '../shared/protocol.js'
import { manager } from '../client/manager.js'
import { presentError } from '../client/errors.js'
/** @type {object|null} */
let statsState = null
let suppressLiveApply = false
/** @type {ReturnType<typeof setTimeout>|null} */
let applyTimer = null
let applyInFlight = false
function showAlert(type, msg) {
if (typeof window.showAlert === 'function') window.showAlert(type, msg)
else if (typeof window.peardockOps?.showAlert === 'function') {
window.peardockOps.showAlert(type, msg)
}
}
async function rpc(method, args = {}) {
if (!manager.active?.connected) {
throw new Error('Connect to a PearDock server to manage stats settings')
}
return manager.request(method, args)
}
function setStatus(msg, tone = 'muted') {
const el = document.getElementById('stats-status')
if (!el) return
el.className = `small mt-2 text-${tone === 'danger' ? 'danger' : tone === 'success' ? 'success' : 'muted'}`
el.textContent = msg || ''
}
function setEngineStatus(text) {
const el = document.getElementById('stats-engine-status')
if (el) el.textContent = text || '—'
}
/**
* Load config from the active server into the form.
*/
export async function loadStatsPanel() {
const offline = document.getElementById('stats-offline-hint')
if (!manager.active?.connected) {
if (offline) offline.classList.remove('hidden')
setEngineStatus('Not connected')
const meta = document.getElementById('stats-runtime-meta')
if (meta) meta.textContent = ''
return
}
if (offline) offline.classList.add('hidden')
try {
const res = await rpc(Methods.getStatsConfig || 'getStatsConfig')
statsState = res?.config || null
renderStatsForm(statsState)
setStatus('')
} catch (err) {
setStatus(err.message || 'Failed to load stats config', 'danger')
setEngineStatus('Error loading')
presentError?.(err, 'getStatsConfig', { showAlert })
}
}
/**
* @param {object|null} config
*/
function renderStatsForm(config) {
if (!config) return
suppressLiveApply = true
try {
const broadcast = document.getElementById('stats-broadcast-sec')
if (broadcast) {
broadcast.value = String(
config.broadcastIntervalSec ?? Math.round((config.broadcastIntervalMs || 1000) / 1000)
)
}
const listSec = document.getElementById('stats-list-sec')
if (listSec) {
listSec.value = String(
config.listIntervalSec ?? Math.round((config.listIntervalMs || 10000) / 1000)
)
}
const warn = document.getElementById('stats-warn-containers')
if (warn) warn.value = String(config.warnContainers ?? 80)
// Keep hidden legacy inputs in sync if present
const collect = document.getElementById('stats-collect-sec')
if (collect) {
collect.value = String(
config.broadcastIntervalSec ?? Math.round((config.broadcastIntervalMs || 1000) / 1000)
)
}
const active = config.active
? `streaming · ${config.streams ?? '?'} streams · ${config.watchers ?? 0} watcher(s)`
: 'idle (no client on Containers/Dashboard)'
setEngineStatus(active)
const meta = document.getElementById('stats-runtime-meta')
if (meta) {
const parts = [
`mode=${config.mode || 'interest-stream'}`,
`peers=${config.peers ?? 0}`,
`watchers=${config.watchers ?? 0}`,
`streams=${config.streams ?? 0}`,
`broadcast=${config.broadcastIntervalMs}ms`,
`list=${config.listIntervalMs}ms`,
]
if (config.path) parts.push(`file=${config.path}`)
meta.textContent = parts.join(' · ')
}
} finally {
setTimeout(() => {
suppressLiveApply = false
}, 0)
}
}
function readFormPartial() {
const broadcastSec = Number(document.getElementById('stats-broadcast-sec')?.value)
const listSec = Number(document.getElementById('stats-list-sec')?.value)
const warnContainers = Number(document.getElementById('stats-warn-containers')?.value)
return {
broadcastIntervalSec: Number.isFinite(broadcastSec) ? broadcastSec : undefined,
listIntervalSec: Number.isFinite(listSec) ? listSec : undefined,
warnContainers: Number.isFinite(warnContainers) ? warnContainers : undefined,
}
}
function scheduleApply() {
if (suppressLiveApply) return
if (!manager.active?.connected) {
setStatus('Connect to a server to apply changes', 'danger')
return
}
if (applyTimer) clearTimeout(applyTimer)
setStatus('Applying…', 'muted')
applyTimer = setTimeout(() => {
applyTimer = null
applyLive().catch((err) => {
setStatus(err.message || 'Apply failed', 'danger')
presentError?.(err, 'updateStatsConfig', { showAlert })
})
}, 400)
}
async function applyLive() {
if (applyInFlight) {
scheduleApply()
return
}
applyInFlight = true
try {
const partial = readFormPartial()
const res = await rpc(Methods.updateStatsConfig || 'updateStatsConfig', {
config: partial,
})
statsState = res?.config || statsState
renderStatsForm(statsState)
setStatus('Applied on server', 'success')
} finally {
applyInFlight = false
}
}
async function resetDefaults() {
if (!manager.active?.connected) {
setStatus('Connect to a server to reset', 'danger')
return
}
try {
setStatus('Resetting…', 'muted')
const res = await rpc(Methods.updateStatsConfig || 'updateStatsConfig', {
replace: true,
config: {},
})
statsState = res?.config || null
renderStatsForm(statsState)
setStatus('Reset to server defaults', 'success')
showAlert?.('success', 'Stats settings reset to defaults')
} catch (err) {
setStatus(err.message || 'Reset failed', 'danger')
presentError?.(err, 'updateStatsConfig', { showAlert })
}
}
/**
* Wire change listeners once.
*/
export function initStatsSettings() {
const panel = document.getElementById('settings-panel-performance')
if (!panel || panel.dataset.statsWired === '1') return
panel.dataset.statsWired = '1'
panel.querySelectorAll('.stats-live-field').forEach((el) => {
// Skip hidden legacy fields from auto-apply noise
if (el.type === 'hidden') return
el.addEventListener('change', () => scheduleApply())
el.addEventListener('input', () => {
if (el.tagName === 'INPUT') scheduleApply()
})
})
document.getElementById('stats-refresh')?.addEventListener('click', () => {
loadStatsPanel().catch(() => {})
})
document.getElementById('stats-reset-defaults')?.addEventListener('click', () => {
resetDefaults().catch(() => {})
})
}
export default {
initStatsSettings,
loadStatsPanel,
}