Add configurable history and storage retention policies
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
<script lang="ts">
|
||||
import type { FleetSettings } from '$lib/fleet-settings.js';
|
||||
let { settings }: { settings: FleetSettings } = $props();
|
||||
import Button from './primitives/Button.svelte';
|
||||
let busy = $state(false);
|
||||
let error = $state('');
|
||||
let notice = $state('');
|
||||
let preview = $state<{ policies: { category: string; label: string; eligible: number; payloadBytes: number; records: { id: string }[] }[];
|
||||
controls: { category: string; id: string; protected: number; resolvedAt: string | null }[] } | null>(null);
|
||||
let category = $state('campaign');
|
||||
let recordId = $state('');
|
||||
const categories = ['campaign','provisioning','operation','observation','maintenance','recovery','match','beta','file-content','beta-file-content','version','audit','game-file','upload'];
|
||||
async function refresh() {
|
||||
busy = true; error = '';
|
||||
try { const response = await fetch('/api/settings/retention/preview', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(settings) }); if (!response.ok) throw new Error('Could not load cleanup preview.'); preview = await response.json(); }
|
||||
catch (cause) { error = (cause as Error).message; } finally { busy = false; }
|
||||
}
|
||||
async function control(action: { protected?: boolean; resolve?: boolean }, selectedCategory = category, id = recordId) {
|
||||
busy = true; error = ''; notice = '';
|
||||
try {
|
||||
const response = await fetch('/api/settings/retention',{ method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ category: selectedCategory,id,...action }) });
|
||||
const result = await response.json(); if (!response.ok) throw new Error(result.error);
|
||||
notice = action.resolve ? 'Failure marked resolved. Retention still checks all recovery dependencies.' : action.protected ? 'Record protected from cleanup.' : 'Record protection removed.';
|
||||
await refresh();
|
||||
} catch (cause) { error = (cause as Error).message; } finally { busy = false; }
|
||||
}
|
||||
</script>
|
||||
<details>
|
||||
<summary>Cleanup preview and protected records</summary>
|
||||
<p>Preview uses the values currently entered above, without saving them. Background cleanup runs in batches. Known payload sizes are estimates; database file size may not shrink immediately. Expired data cannot be restored by increasing retention.</p>
|
||||
<Button disabled={busy} onclick={refresh}>{busy ? 'Loading…' : 'Refresh cleanup preview'}</Button>
|
||||
{#if error}<p class="ui-error" role="alert">{error}</p>{/if}
|
||||
{#if notice}<p role="status">{notice}</p>{/if}
|
||||
{#if preview}
|
||||
<div class="table-wrap"><table><thead><tr><th>Category</th><th>Eligible records</th><th>Known payload size</th></tr></thead><tbody>
|
||||
{#each preview.policies as policy}<tr><td>{policy.label}</td><td>{policy.eligible}</td><td>{policy.payloadBytes ? `${(policy.payloadBytes / 1024 / 1024).toFixed(2)} MiB` : '—'}</td></tr>{/each}
|
||||
</tbody></table></div>
|
||||
{#each preview.policies.filter(policy => policy.records.length) as policy}
|
||||
<details><summary>{policy.label}: eligible IDs (up to 20)</summary>
|
||||
{#each policy.records as record}<div class="record"><code>{record.id}</code><Button disabled={busy} onclick={() => control({ protected: true },policy.category,record.id)}>Protect</Button></div>{/each}
|
||||
</details>
|
||||
{/each}
|
||||
<h3>Protected records and resolved failures</h3>
|
||||
{#each preview.controls as record}<div class="record"><span>{record.category} · <code>{record.id}</code>{record.resolvedAt ? ' · Resolved' : ''}</span>
|
||||
{#if record.protected}<Button disabled={busy} onclick={() => control({ protected: false },record.category,record.id)}>Remove protection</Button>{/if}</div>{:else}<p>No explicit record protections or resolutions.</p>{/each}
|
||||
{/if}
|
||||
<h3>Protect a record or resolve a failure</h3>
|
||||
<p>Use the ID from its detail URL or the preview. Resolving a failed campaign stops automatic retry requests for that campaign; unfinished recovery still prevents cleanup.</p>
|
||||
<div class="record">
|
||||
<label>Category<select bind:value={category}>{#each categories as value}<option value={value}>{value}</option>{/each}</select></label>
|
||||
<label>Record ID<input bind:value={recordId} maxlength="200" /></label>
|
||||
<Button disabled={busy || !recordId} onclick={() => control({ protected: true })}>Protect record</Button>
|
||||
{#if ['campaign','provisioning','operation'].includes(category)}<Button disabled={busy || !recordId} onclick={() => control({ resolve: true })}>Mark failure resolved</Button>{/if}
|
||||
</div>
|
||||
</details>
|
||||
<style>
|
||||
details { margin-top: 20px; }
|
||||
summary { cursor: pointer; }
|
||||
p { color: var(--text-secondary); font-size: var(--text-sm); line-height: 1.65; }
|
||||
.record { display: flex; gap: 12px; flex-wrap: wrap; align-items: center; margin: 12px 0; }
|
||||
code { overflow-wrap: anywhere; }
|
||||
label { display: grid; gap: 8px; }
|
||||
select,input { font: inherit; color: var(--text-primary); background: var(--surface-sunken); border: 1px solid var(--border-control); border-radius: var(--radius-sm); padding: 8px; }
|
||||
.table-wrap { overflow-x: auto; margin: 16px 0; }
|
||||
table { width: 100%; border-collapse: collapse; text-align: left; font-size: var(--text-sm); }
|
||||
th,td { padding: 10px; border-bottom: 1px solid var(--border-strong); }
|
||||
</style>
|
||||
Reference in New Issue
Block a user