fc3k(ui): TagMaintenanceCard — preview-then-commit prune-unused under Settings → Maintenance
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -13,6 +13,7 @@
|
|||||||
<AllowlistTable class="mt-4" />
|
<AllowlistTable class="mt-4" />
|
||||||
<AliasTable class="mt-4" />
|
<AliasTable class="mt-4" />
|
||||||
<BackupCard class="mt-6" />
|
<BackupCard class="mt-6" />
|
||||||
|
<TagMaintenanceCard class="mt-6" />
|
||||||
<BrowserExtensionCard class="mt-6" />
|
<BrowserExtensionCard class="mt-6" />
|
||||||
<LegacyMigrationCard class="mt-6" />
|
<LegacyMigrationCard class="mt-6" />
|
||||||
</div>
|
</div>
|
||||||
@@ -25,6 +26,7 @@ import MLThresholdSliders from './MLThresholdSliders.vue'
|
|||||||
import AllowlistTable from './AllowlistTable.vue'
|
import AllowlistTable from './AllowlistTable.vue'
|
||||||
import AliasTable from './AliasTable.vue'
|
import AliasTable from './AliasTable.vue'
|
||||||
import BackupCard from './BackupCard.vue'
|
import BackupCard from './BackupCard.vue'
|
||||||
|
import TagMaintenanceCard from './TagMaintenanceCard.vue'
|
||||||
import BrowserExtensionCard from './BrowserExtensionCard.vue'
|
import BrowserExtensionCard from './BrowserExtensionCard.vue'
|
||||||
import LegacyMigrationCard from './LegacyMigrationCard.vue'
|
import LegacyMigrationCard from './LegacyMigrationCard.vue'
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
<template>
|
||||||
|
<v-card class="fc-tag-maint">
|
||||||
|
<v-card-title class="d-flex align-center" style="gap: 10px;">
|
||||||
|
<v-icon icon="mdi-tag-remove" size="small" />
|
||||||
|
<span>Tag maintenance</span>
|
||||||
|
</v-card-title>
|
||||||
|
<v-card-text>
|
||||||
|
<p class="fc-muted text-body-2 mb-4">
|
||||||
|
Remove tags with zero image associations and zero series-page
|
||||||
|
references. Auto-created tag rows that never got applied get
|
||||||
|
swept here.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<v-alert
|
||||||
|
v-if="store.lastError"
|
||||||
|
type="warning" variant="tonal" density="compact" class="mb-3"
|
||||||
|
>{{ store.lastError }}</v-alert>
|
||||||
|
|
||||||
|
<v-btn
|
||||||
|
color="accent" variant="flat" rounded="pill"
|
||||||
|
prepend-icon="mdi-magnify"
|
||||||
|
:loading="loadingPreview"
|
||||||
|
class="mb-3"
|
||||||
|
@click="onPreview"
|
||||||
|
>Preview unused tags</v-btn>
|
||||||
|
|
||||||
|
<div v-if="preview">
|
||||||
|
<p class="text-body-2 mb-2">
|
||||||
|
<strong>{{ preview.count }}</strong> unused tag(s).
|
||||||
|
<span v-if="preview.count > 50" class="fc-muted">
|
||||||
|
Showing first 50 names.
|
||||||
|
</span>
|
||||||
|
</p>
|
||||||
|
<div v-if="preview.sample_names?.length" class="fc-name-grid mb-3">
|
||||||
|
<span v-for="n in preview.sample_names" :key="n" class="fc-name">
|
||||||
|
{{ n }}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<v-btn
|
||||||
|
color="error" variant="flat" rounded="pill"
|
||||||
|
prepend-icon="mdi-delete-sweep"
|
||||||
|
:disabled="!preview.count"
|
||||||
|
:loading="committing"
|
||||||
|
@click="onCommit"
|
||||||
|
>Delete {{ preview.count }} unused tag(s)</v-btn>
|
||||||
|
</div>
|
||||||
|
</v-card-text>
|
||||||
|
</v-card>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue'
|
||||||
|
|
||||||
|
import { useAdminStore } from '../../stores/admin.js'
|
||||||
|
|
||||||
|
const store = useAdminStore()
|
||||||
|
const preview = ref(null)
|
||||||
|
const loadingPreview = ref(false)
|
||||||
|
const committing = ref(false)
|
||||||
|
|
||||||
|
async function onPreview() {
|
||||||
|
loadingPreview.value = true
|
||||||
|
try {
|
||||||
|
preview.value = await store.pruneUnusedTags({ dryRun: true })
|
||||||
|
} finally {
|
||||||
|
loadingPreview.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onCommit() {
|
||||||
|
committing.value = true
|
||||||
|
try {
|
||||||
|
const result = await store.pruneUnusedTags({ dryRun: false })
|
||||||
|
preview.value = { count: 0, sample_names: result.sample_names || [] }
|
||||||
|
} finally {
|
||||||
|
committing.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.fc-tag-maint { border-radius: 8px; }
|
||||||
|
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
|
||||||
|
.fc-name-grid {
|
||||||
|
display: flex; flex-wrap: wrap; gap: 4px 8px;
|
||||||
|
max-height: 200px; overflow-y: auto;
|
||||||
|
padding: 8px; border-radius: 4px;
|
||||||
|
background: rgb(var(--v-theme-surface-light));
|
||||||
|
}
|
||||||
|
.fc-name {
|
||||||
|
font-family: 'JetBrains Mono', monospace;
|
||||||
|
font-size: 12px;
|
||||||
|
color: rgb(var(--v-theme-on-surface-variant));
|
||||||
|
}
|
||||||
|
</style>
|
||||||
Reference in New Issue
Block a user