fc3k(ui): Pinia admin store — six endpoints + task_run polling for Tier-C dispatched ops
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,153 @@
|
||||
import { defineStore } from 'pinia'
|
||||
import { ref } from 'vue'
|
||||
|
||||
import { useApi } from '../composables/useApi.js'
|
||||
|
||||
export const useAdminStore = defineStore('admin', () => {
|
||||
const api = useApi()
|
||||
const lastError = ref(null)
|
||||
|
||||
// --- Tier-C: artist cascade ---------------------------------------
|
||||
|
||||
async function projectArtistCascade(slug) {
|
||||
lastError.value = null
|
||||
try {
|
||||
return await api.post(
|
||||
`/api/admin/artists/${encodeURIComponent(slug)}/cascade-delete`,
|
||||
{ body: { dry_run: true } },
|
||||
)
|
||||
} catch (e) {
|
||||
lastError.value = e.message
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async function dispatchArtistCascade(slug, confirm) {
|
||||
lastError.value = null
|
||||
try {
|
||||
return await api.post(
|
||||
`/api/admin/artists/${encodeURIComponent(slug)}/cascade-delete`,
|
||||
{ body: { dry_run: false, confirm } },
|
||||
)
|
||||
} catch (e) {
|
||||
lastError.value = e.message
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tier-C: bulk image delete ------------------------------------
|
||||
|
||||
async function projectBulkImageDelete(imageIds) {
|
||||
lastError.value = null
|
||||
try {
|
||||
return await api.post(
|
||||
'/api/admin/images/bulk-delete',
|
||||
{ body: { image_ids: imageIds, dry_run: true } },
|
||||
)
|
||||
} catch (e) {
|
||||
lastError.value = e.message
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async function dispatchBulkImageDelete(imageIds, confirm) {
|
||||
lastError.value = null
|
||||
try {
|
||||
return await api.post(
|
||||
'/api/admin/images/bulk-delete',
|
||||
{ body: { image_ids: imageIds, dry_run: false, confirm } },
|
||||
)
|
||||
} catch (e) {
|
||||
lastError.value = e.message
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tier-B: tag delete + merge -----------------------------------
|
||||
|
||||
async function deleteTag(tagId) {
|
||||
lastError.value = null
|
||||
try {
|
||||
return await api.delete(`/api/admin/tags/${tagId}`)
|
||||
} catch (e) {
|
||||
lastError.value = e.message
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async function mergeTags(destId, sourceId) {
|
||||
lastError.value = null
|
||||
try {
|
||||
return await api.post(
|
||||
`/api/admin/tags/${destId}/merge`,
|
||||
{ body: { source_id: sourceId } },
|
||||
)
|
||||
} catch (e) {
|
||||
lastError.value = e.message
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
async function tagUsageCount(tagId) {
|
||||
lastError.value = null
|
||||
try {
|
||||
const body = await api.get(`/api/admin/tags/${tagId}/usage-count`)
|
||||
return body.count
|
||||
} catch (e) {
|
||||
lastError.value = e.message
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
// --- Tier-A: prune unused -----------------------------------------
|
||||
|
||||
async function pruneUnusedTags({ dryRun = true } = {}) {
|
||||
lastError.value = null
|
||||
try {
|
||||
return await api.post(
|
||||
'/api/admin/tags/prune-unused',
|
||||
{ body: { dry_run: dryRun } },
|
||||
)
|
||||
} catch (e) {
|
||||
lastError.value = e.message
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
// --- Task progress polling (taps FC-3i activity dashboard) --------
|
||||
|
||||
/**
|
||||
* Polls /api/system/activity/runs?queue=maintenance every 3s,
|
||||
* resolves when a task_run row with the given celery task_id
|
||||
* reaches a terminal status (ok / error / timeout). Returns the
|
||||
* row. Times out after 30 min by default.
|
||||
*/
|
||||
async function pollTaskUntilDone(taskId, { timeoutMs = 1_800_000 } = {}) {
|
||||
const deadline = Date.now() + timeoutMs
|
||||
while (Date.now() < deadline) {
|
||||
const body = await api.get(
|
||||
'/api/system/activity/runs',
|
||||
{ params: { queue: 'maintenance', limit: 20 } },
|
||||
)
|
||||
const row = (body.runs || []).find(r => r.celery_task_id === taskId)
|
||||
if (row && ['ok', 'error', 'timeout'].includes(row.status)) {
|
||||
return row
|
||||
}
|
||||
await new Promise((r) => setTimeout(r, 3000))
|
||||
}
|
||||
throw new Error(`task ${taskId} did not finish within ${timeoutMs}ms`)
|
||||
}
|
||||
|
||||
return {
|
||||
lastError,
|
||||
projectArtistCascade,
|
||||
dispatchArtistCascade,
|
||||
projectBulkImageDelete,
|
||||
dispatchBulkImageDelete,
|
||||
deleteTag,
|
||||
mergeTags,
|
||||
tagUsageCount,
|
||||
pruneUnusedTags,
|
||||
pollTaskUntilDone,
|
||||
}
|
||||
})
|
||||
Reference in New Issue
Block a user