import { useState, useEffect } from 'react'; import { Input } from '../ui/input'; import { Label } from '../ui/label'; import { Button } from '../ui/button'; import { Separator } from '../ui/separator'; import { toast } from '../ui/sonner'; import { api } from '../../api'; import { formatTime } from '../../utils/messageParser'; import type { AppSettings, AppSettingsUpdate, HealthStatus } from '../../types'; export function SettingsDatabaseSection({ appSettings, health, onSaveAppSettings, onHealthRefresh, blockedKeys = [], blockedNames = [], onToggleBlockedKey, onToggleBlockedName, className, }: { appSettings: AppSettings; health: HealthStatus | null; onSaveAppSettings: (update: AppSettingsUpdate) => Promise; onHealthRefresh: () => Promise; blockedKeys?: string[]; blockedNames?: string[]; onToggleBlockedKey?: (key: string) => void; onToggleBlockedName?: (name: string) => void; className?: string; }) { const [retentionDays, setRetentionDays] = useState('14'); const [cleaning, setCleaning] = useState(false); const [purgingDecryptedRaw, setPurgingDecryptedRaw] = useState(false); const [autoDecryptOnAdvert, setAutoDecryptOnAdvert] = useState(false); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); useEffect(() => { setAutoDecryptOnAdvert(appSettings.auto_decrypt_dm_on_advert); }, [appSettings]); const handleCleanup = async () => { const days = parseInt(retentionDays, 10); if (isNaN(days) || days < 1) { toast.error('Invalid retention days', { description: 'Retention days must be at least 1', }); return; } setCleaning(true); try { const result = await api.runMaintenance({ pruneUndecryptedDays: days }); toast.success('Database cleanup complete', { description: `Deleted ${result.packets_deleted} old packet${result.packets_deleted === 1 ? '' : 's'}`, }); await onHealthRefresh(); } catch (err) { console.error('Failed to run maintenance:', err); toast.error('Database cleanup failed', { description: err instanceof Error ? err.message : 'Unknown error', }); } finally { setCleaning(false); } }; const handlePurgeDecryptedRawPackets = async () => { setPurgingDecryptedRaw(true); try { const result = await api.runMaintenance({ purgeLinkedRawPackets: true }); toast.success('Decrypted raw packets purged', { description: `Deleted ${result.packets_deleted} raw packet${result.packets_deleted === 1 ? '' : 's'}`, }); await onHealthRefresh(); } catch (err) { console.error('Failed to purge decrypted raw packets:', err); toast.error('Failed to purge decrypted raw packets', { description: err instanceof Error ? err.message : 'Unknown error', }); } finally { setPurgingDecryptedRaw(false); } }; const handleSave = async () => { setBusy(true); setError(null); try { await onSaveAppSettings({ auto_decrypt_dm_on_advert: autoDecryptOnAdvert }); toast.success('Database settings saved'); } catch (err) { console.error('Failed to save database settings:', err); setError(err instanceof Error ? err.message : 'Failed to save'); toast.error('Failed to save settings'); } finally { setBusy(false); } }; return (
Database size {health?.database_size_mb ?? '?'} MB
{health?.oldest_undecrypted_timestamp ? (
Oldest undecrypted packet {formatTime(health.oldest_undecrypted_timestamp)} ({Math.floor((Date.now() / 1000 - health.oldest_undecrypted_timestamp) / 86400)}{' '} days old)
) : (
Oldest undecrypted packet None
)}

Permanently deletes stored raw packets containing DMs and channel messages that have not yet been decrypted. These packets are retained in case you later obtain the correct key — once deleted, these messages can never be recovered or decrypted.

setRetentionDays(e.target.value)} className="w-24" />

Deletes archival copies of raw packet bytes for messages that are already decrypted and visible in your chat history.{' '} This will not affect any displayed messages or app functionality. {' '} The raw bytes are only useful for manual packet analysis.

When enabled, the server will automatically try to decrypt stored DM packets when a new contact sends an advertisement. This may cause brief delays on large packet backlogs.

Blocking only hides messages from the UI. MQTT forwarding and bot responses are not affected. Messages are still stored and will reappear if unblocked.

{blockedKeys.length === 0 && blockedNames.length === 0 ? (

No blocked contacts

) : (
{blockedKeys.length > 0 && (
Blocked Keys
{blockedKeys.map((key) => (
{key} {onToggleBlockedKey && ( )}
))}
)} {blockedNames.length > 0 && (
Blocked Names
{blockedNames.map((name) => (
{name} {onToggleBlockedName && ( )}
))}
)}
)}
{error && (
{error}
)}
); }