41652db20f
Phase 2 of #871: clean up the duplicate videos already in the library (the #859 "same video from multiple sources" clutter). Import-time dedup (Phase 1) only prevents NEW dups; this is the operator-triggered cleanup of existing ones. cleanup_service.dedup_videos(dry_run): - backfill_video_durations: re-probe NULL-duration videos (pre-#871 rows) so the existing library participates; idempotent (only NULL rows), writes a negative sentinel for un-probeable files so they're neither re-probed forever nor matched. - find_video_dup_groups: cluster same-artist videos by duration (±tol) + aspect, anchored per cluster to bound the span (no chain drift); keeper = highest pixel area then bytes. Reuses the importer's _VIDEO_DUP_* tolerances. - apply: re-point each loser's post links to the keeper (so no post loses the video) THEN delete the redundant records + files via delete_images (cascade). dry_run shares the same discovery predicate and returns the projection only (rule 93). Tags on a loser are NOT merged (noted; videos rarely hand-curated). - dedup_videos_task (maintenance queue; summary → task_run.metadata). - POST /maintenance/dedup-videos {dry_run} + GET /maintenance/task-result/<id> so the card shows the dry-run projection before the destructive apply. - VideoDedupCard: Preview → shows groups/redundant/reclaimable, then Apply behind a confirm dialog. Mounted in the Maintenance panel. Tests: dedup collapses + re-links the loser's post to the keeper + removes the file; dry-run deletes nothing; distinct durations aren't grouped; task registered. (Migration 0052 for duration_seconds already shipped with Phase 1.) Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
68 lines
2.3 KiB
Vue
68 lines
2.3 KiB
Vue
<template>
|
|
<div class="fc-maint">
|
|
<p class="text-body-2 mb-4">
|
|
Operational backfills and tagging controls. The ML backfill and centroid
|
|
recompute run nightly automatically; the allowlist auto-applies accepted
|
|
tags to new and existing images. Use the cards below to trigger a
|
|
one-off pass.
|
|
</p>
|
|
<div class="fc-maint__grid">
|
|
<MLBackfillCard />
|
|
<CentroidRecomputeCard />
|
|
<ThumbnailBackfillCard />
|
|
</div>
|
|
<MLThresholdSliders class="mt-4" />
|
|
<AllowlistTable class="mt-4" />
|
|
<AliasTable class="mt-4" />
|
|
<DbMaintenanceCard class="mt-6" />
|
|
<ArchiveReextractCard class="mt-6" />
|
|
<MissingFileRepairCard class="mt-6" />
|
|
<VideoDedupCard class="mt-6" />
|
|
<BackupCard class="mt-6" />
|
|
<!-- TagMaintenanceCard moved to Cleanup tab (v26.05.25.7) — it
|
|
operates on the existing library which fits the Cleanup-tab
|
|
theme, and clusters with the other audit cards. LegacyMigrationCard
|
|
removed once the one-and-done GS/IR migration cutover completed. -->
|
|
</div>
|
|
</template>
|
|
|
|
<script setup>
|
|
import { onMounted, onUnmounted } from 'vue'
|
|
|
|
import MLBackfillCard from './MLBackfillCard.vue'
|
|
import CentroidRecomputeCard from './CentroidRecomputeCard.vue'
|
|
import ThumbnailBackfillCard from './ThumbnailBackfillCard.vue'
|
|
import MLThresholdSliders from './MLThresholdSliders.vue'
|
|
import AllowlistTable from './AllowlistTable.vue'
|
|
import AliasTable from './AliasTable.vue'
|
|
import DbMaintenanceCard from './DbMaintenanceCard.vue'
|
|
import ArchiveReextractCard from './ArchiveReextractCard.vue'
|
|
import MissingFileRepairCard from './MissingFileRepairCard.vue'
|
|
import VideoDedupCard from './VideoDedupCard.vue'
|
|
import BackupCard from './BackupCard.vue'
|
|
import { useSystemActivityStore } from '../../stores/systemActivity.js'
|
|
|
|
// Poll queue depths so each card's QueueStatusBar shows live pending
|
|
// counts — the operator can see a backfill is already queued/running
|
|
// before re-triggering it.
|
|
const activity = useSystemActivityStore()
|
|
let qTimer = null
|
|
onMounted(() => {
|
|
activity.loadQueues()
|
|
qTimer = setInterval(() => {
|
|
if (!document.hidden) activity.loadQueues()
|
|
}, 4000)
|
|
})
|
|
onUnmounted(() => {
|
|
if (qTimer) { clearInterval(qTimer); qTimer = null }
|
|
})
|
|
</script>
|
|
|
|
<style scoped>
|
|
.fc-maint__grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
gap: 16px;
|
|
}
|
|
</style>
|