feat: add scheduled metadata refresh for libraries

Add metadata_refresh_mode (manual/hourly/daily/weekly) to libraries,
with automatic scheduling via the indexer. Includes API support,
backoffice UI controls, i18n translations, and DB migration.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-20 10:51:52 +01:00
parent 5a224c48c0
commit fe5de3d5c1
8 changed files with 143 additions and 10 deletions

View File

@@ -12,6 +12,7 @@ interface LibraryActionsProps {
watcherEnabled: boolean;
metadataProvider: string | null;
fallbackMetadataProvider: string | null;
metadataRefreshMode: string;
onUpdate?: () => void;
}
@@ -22,6 +23,7 @@ export function LibraryActions({
watcherEnabled,
metadataProvider,
fallbackMetadataProvider,
metadataRefreshMode,
onUpdate
}: LibraryActionsProps) {
const { t } = useTranslation();
@@ -48,6 +50,7 @@ export function LibraryActions({
const scanMode = formData.get("scan_mode") as string;
const newMetadataProvider = (formData.get("metadata_provider") as string) || null;
const newFallbackProvider = (formData.get("fallback_metadata_provider") as string) || null;
const newMetadataRefreshMode = formData.get("metadata_refresh_mode") as string;
try {
const [response] = await Promise.all([
@@ -58,6 +61,7 @@ export function LibraryActions({
monitor_enabled: monitorEnabled,
scan_mode: scanMode,
watcher_enabled: watcherEnabled,
metadata_refresh_mode: newMetadataRefreshMode,
}),
}),
fetch(`/api/libraries/${libraryId}/metadata-provider`, {
@@ -181,6 +185,20 @@ export function LibraryActions({
</select>
</div>
<div className="flex items-center justify-between">
<label className="text-sm font-medium text-foreground">{t("libraryActions.metadataRefreshSchedule")}</label>
<select
name="metadata_refresh_mode"
defaultValue={metadataRefreshMode}
className="text-sm border border-border rounded-lg px-2 py-1 bg-background"
>
<option value="manual">{t("monitoring.manual")}</option>
<option value="hourly">{t("monitoring.hourly")}</option>
<option value="daily">{t("monitoring.daily")}</option>
<option value="weekly">{t("monitoring.weekly")}</option>
</select>
</div>
{saveError && (
<p className="text-xs text-destructive bg-destructive/10 px-2 py-1.5 rounded-lg break-all">
{saveError}

View File

@@ -131,6 +131,7 @@ export default async function LibrariesPage() {
watcherEnabled={lib.watcher_enabled}
metadataProvider={lib.metadata_provider}
fallbackMetadataProvider={lib.fallback_metadata_provider}
metadataRefreshMode={lib.metadata_refresh_mode}
/>
</div>
</CardHeader>
@@ -169,6 +170,11 @@ export default async function LibrariesPage() {
{t("libraries.nextScan", { time: formatNextScan(lib.next_scan_at, t("libraries.imminent")) })}
</span>
)}
{lib.metadata_refresh_mode !== "manual" && lib.next_metadata_refresh_at && (
<span className="text-xs text-muted-foreground ml-auto" title={t("libraries.nextMetadataRefresh", { time: formatNextScan(lib.next_metadata_refresh_at, t("libraries.imminent")) })}>
{t("libraries.nextMetadataRefreshShort", { time: formatNextScan(lib.next_metadata_refresh_at, t("libraries.imminent")) })}
</span>
)}
</div>
{/* Actions */}

View File

@@ -124,6 +124,8 @@ const en: Record<TranslationKey, string> = {
"libraries.manual": "Manual",
"libraries.nextScan": "Next: {{time}}",
"libraries.imminent": "Imminent",
"libraries.nextMetadataRefresh": "Next metadata refresh: {{time}}",
"libraries.nextMetadataRefreshShort": "Meta.: {{time}}",
"libraries.index": "Index",
"libraries.fullIndex": "Full",
"libraries.batchMetadata": "Batch metadata",
@@ -148,6 +150,7 @@ const en: Record<TranslationKey, string> = {
"libraryActions.fallback": "Fallback",
"libraryActions.default": "Default",
"libraryActions.none": "None",
"libraryActions.metadataRefreshSchedule": "Refresh meta.",
"libraryActions.saving": "Saving...",
// Library sub-page header

View File

@@ -122,6 +122,8 @@ const fr = {
"libraries.manual": "Manuel",
"libraries.nextScan": "Prochain : {{time}}",
"libraries.imminent": "Imminent",
"libraries.nextMetadataRefresh": "Prochain rafraîchissement méta. : {{time}}",
"libraries.nextMetadataRefreshShort": "Méta. : {{time}}",
"libraries.index": "Indexer",
"libraries.fullIndex": "Complet",
"libraries.batchMetadata": "Métadonnées en lot",
@@ -146,6 +148,7 @@ const fr = {
"libraryActions.fallback": "Secours",
"libraryActions.default": "Par défaut",
"libraryActions.none": "Aucun",
"libraryActions.metadataRefreshSchedule": "Rafraîchir méta.",
"libraryActions.saving": "Enregistrement...",
// Library sub-page header