Files
homefeed/frontend/src/lib/components/admin/RetentionTab.svelte
T
2026-07-17 10:33:20 -04:00

190 lines
4.2 KiB
Svelte

<script lang="ts">
import type { AdminSettings } from '$lib/adminTypes';
import { updateSettings } from '$lib/adminApi';
import SaveStatus from './SaveStatus.svelte';
let { settings }: { settings: AdminSettings } = $props();
let retention = $state({ ...settings.retention });
let status = $state<'idle' | 'saving' | 'saved' | 'error'>('idle');
let saveTimer: ReturnType<typeof setTimeout>;
function scheduleSave() {
status = 'saving';
clearTimeout(saveTimer);
saveTimer = setTimeout(async () => {
try {
await updateSettings({ retention });
status = 'saved';
setTimeout(() => (status = 'idle'), 1500);
} catch {
status = 'error';
}
}, 500);
}
const usagePercent = $derived(
retention.storageCapEnabled
? Math.min(
100,
(retention.storageUsedMB / (retention.storageCapValue * (retention.storageCapUnit === 'GB' ? 1024 : 1))) * 100
)
: 0
);
const presets = [
{ label: '7 days', value: 7 },
{ label: '30 days', value: 30 },
{ label: '1 year', value: 365 },
{ label: 'Forever', value: null }
];
</script>
<div class="panel">
<div class="head">
<span class="panel-title">Published articles</span>
<SaveStatus {status} />
</div>
<p class="hint">How long merged, published stories stay available.</p>
<div class="pill-row">
{#each presets as preset}
<button
class="pill"
class:active={retention.publishedArticleMaxAgeDays === preset.value}
onclick={() => {
retention.publishedArticleMaxAgeDays = preset.value;
scheduleSave();
}}
>
{preset.label}
</button>
{/each}
</div>
</div>
<div class="panel">
<span class="panel-title">Raw source items</span>
<p class="hint">Individual RSS/API/Telegram entries used to build merged stories.</p>
<div class="pill-row">
{#each [{ label: '3 days', value: 3 }, { label: '7 days', value: 7 }, { label: '30 days', value: 30 }] as preset}
<button
class="pill"
class:active={retention.rawItemMaxAgeDays === preset.value}
onclick={() => {
retention.rawItemMaxAgeDays = preset.value;
scheduleSave();
}}
>
{preset.label}
</button>
{/each}
</div>
</div>
<div class="panel">
<div class="head">
<span class="panel-title">Storage cap</span>
<label class="checkbox">
<input
type="checkbox"
bind:checked={retention.storageCapEnabled}
onchange={scheduleSave}
/>
Enabled
</label>
</div>
<p class="hint">
When total storage exceeds this size, oldest items are deleted first (FIFO) regardless of
the age settings above.
</p>
<div class="cap-row">
<input
type="number"
bind:value={retention.storageCapValue}
oninput={scheduleSave}
style="width: 100px"
/>
<select bind:value={retention.storageCapUnit} onchange={scheduleSave} style="width: 90px">
<option value="MB">MB</option>
<option value="GB">GB</option>
</select>
<span class="usage-label">currently using {retention.storageUsedMB} MB</span>
</div>
<div class="bar">
<div class="bar-fill" style="width: {usagePercent}%"></div>
</div>
</div>
<style>
.panel {
background: var(--surface-1);
border-radius: 12px;
padding: 16px;
margin-bottom: 14px;
}
.head {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: 4px;
}
.panel-title {
font-size: 13px;
font-weight: 500;
}
.hint {
font-size: 12px;
color: var(--text-secondary);
margin: 4px 0 12px;
}
.pill-row {
display: flex;
gap: 8px;
flex-wrap: wrap;
}
.pill {
font-size: 12px;
padding: 6px 12px;
border-radius: var(--radius);
border: 0.5px solid var(--border);
background: var(--surface-2);
color: var(--text-secondary);
}
.pill.active {
background: var(--pill-bg);
color: var(--pill-text);
border-color: var(--pill-bg);
}
.checkbox {
display: flex;
align-items: center;
gap: 6px;
font-size: 12px;
color: var(--text-secondary);
}
.checkbox input {
width: auto;
}
.cap-row {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.usage-label {
font-size: 12px;
color: var(--text-muted);
}
.bar {
width: 100%;
height: 6px;
background: var(--border);
border-radius: 4px;
overflow: hidden;
}
.bar-fill {
height: 100%;
background: var(--border-accent);
}
</style>