Add settings-configurable cap on logs kept in memory (ticket 026)
Settings.max_logs (default None = unlimited) is pushed to the engine via a new EngineMessage::SetMaxLogs, enforced on every ingest and whenever the setting changes at runtime (no restart required). Eviction drops the oldest (lowest-sequence) logs; sequence numbering isn't renumbered and buckets already derived from an evicted log are left in place. Exposed via PATCH /api/settings (max_logs: number | null) and a NavMenu numeric field (blank = unlimited). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01XFUNZwCvz3kcod3352ZR2o
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
aaa5596030
commit
1d0f324c43
@@ -1,4 +1,4 @@
|
||||
import { useState } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import type { Stats } from '../lib/api'
|
||||
import type { ColorScheme } from '../lib/api'
|
||||
import { useSettingsPanel } from '../lib/useSettingsPanel'
|
||||
@@ -120,6 +120,25 @@ export function NavMenu({ stats }: { stats: Stats | null }) {
|
||||
function SettingsPanel({ panel }: { panel: ReturnType<typeof useSettingsPanel> }) {
|
||||
const [filename, setFilename] = useState('')
|
||||
const [content, setContent] = useState('')
|
||||
const [maxLogsInput, setMaxLogsInput] = useState('')
|
||||
|
||||
useEffect(() => {
|
||||
setMaxLogsInput(panel.settings?.max_logs != null ? String(panel.settings.max_logs) : '')
|
||||
}, [panel.settings?.max_logs])
|
||||
|
||||
function handleMaxLogsBlur() {
|
||||
const trimmed = maxLogsInput.trim()
|
||||
if (trimmed === '') {
|
||||
panel.setMaxLogs(null)
|
||||
return
|
||||
}
|
||||
const parsed = Number(trimmed)
|
||||
if (!Number.isFinite(parsed) || parsed <= 0) {
|
||||
setMaxLogsInput(panel.settings?.max_logs != null ? String(panel.settings.max_logs) : '')
|
||||
return
|
||||
}
|
||||
panel.setMaxLogs(Math.floor(parsed))
|
||||
}
|
||||
|
||||
async function handleFileChange(e: React.ChangeEvent<HTMLInputElement>) {
|
||||
const file = e.target.files?.[0]
|
||||
@@ -158,6 +177,22 @@ function SettingsPanel({ panel }: { panel: ReturnType<typeof useSettingsPanel> }
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 className="mb-2 text-xs font-semibold uppercase tracking-wide text-text-subtle">Log retention</h3>
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="number"
|
||||
min={1}
|
||||
placeholder="Unlimited"
|
||||
value={maxLogsInput}
|
||||
onChange={(e) => setMaxLogsInput(e.target.value)}
|
||||
onBlur={handleMaxLogsBlur}
|
||||
className="w-28 rounded border border-border-soft bg-surface-1 px-2 py-1 text-xs text-text placeholder:text-text-subtle focus:border-border-strong focus:outline-none"
|
||||
/>
|
||||
<span className="text-xs text-text-faint">max logs kept in memory (blank = unlimited)</span>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section>
|
||||
<h3 className="mb-2 text-xs font-semibold uppercase tracking-wide text-text-subtle">Import CSS</h3>
|
||||
<form onSubmit={handleImport} className="flex flex-col gap-2">
|
||||
|
||||
+10
-2
@@ -90,6 +90,8 @@ export type ColorScheme =
|
||||
export interface Settings {
|
||||
color_scheme: ColorScheme
|
||||
active_profile: string | null
|
||||
/** Maximum ingested logs kept in memory; `null` means unlimited. */
|
||||
max_logs: number | null
|
||||
}
|
||||
|
||||
export type CssEntryKind = 'imported' | 'linked'
|
||||
@@ -263,8 +265,14 @@ export function fetchSettings(): Promise<Settings> {
|
||||
return getJson('/api/settings')
|
||||
}
|
||||
|
||||
export function updateSettings(update: { colorScheme?: ColorScheme }): Promise<Settings> {
|
||||
return sendJson('/api/settings', 'PATCH', { color_scheme: update.colorScheme })
|
||||
export function updateSettings(update: {
|
||||
colorScheme?: ColorScheme
|
||||
/** Omit to leave unchanged; `null` clears the cap back to unlimited. */
|
||||
maxLogs?: number | null
|
||||
}): Promise<Settings> {
|
||||
const body: Record<string, unknown> = { color_scheme: update.colorScheme }
|
||||
if ('maxLogs' in update) body.max_logs = update.maxLogs
|
||||
return sendJson('/api/settings', 'PATCH', body)
|
||||
}
|
||||
|
||||
export function fetchCssEntries(): Promise<CssEntry[]> {
|
||||
|
||||
@@ -123,6 +123,15 @@ export function useSettingsPanel() {
|
||||
[guarded],
|
||||
)
|
||||
|
||||
const setMaxLogs = useCallback(
|
||||
(maxLogs: number | null) =>
|
||||
guarded(async () => {
|
||||
const next = await updateSettings({ maxLogs })
|
||||
setSettings(next)
|
||||
})(),
|
||||
[guarded],
|
||||
)
|
||||
|
||||
const importCssEntry = useCallback(
|
||||
(filename: string, content: string) =>
|
||||
guarded(async () => {
|
||||
@@ -174,6 +183,7 @@ export function useSettingsPanel() {
|
||||
profiles,
|
||||
error,
|
||||
setColorScheme,
|
||||
setMaxLogs,
|
||||
importCssEntry,
|
||||
removeCssEntry,
|
||||
addProfile,
|
||||
|
||||
Reference in New Issue
Block a user