feat(maintenance): live queue-depth bar under the backfill buttons
Each maintenance button (ML backfill, centroid recompute → ml queue; thumbnail backfill → thumbnail queue) now shows a status bar with the live pending count for its Celery queue, so the operator can see work is already queued/running before re-triggering and piling on. MaintenancePanel polls /api/system/activity/queues every 4s; QueueStatusBar reads the depth and turns warning-colored when the queue is busy. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -11,6 +11,7 @@
|
|||||||
<v-icon start>mdi-vector-triangle</v-icon> Recompute centroids
|
<v-icon start>mdi-vector-triangle</v-icon> Recompute centroids
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<span v-if="done" class="ml-3 text-caption">Enqueued.</span>
|
<span v-if="done" class="ml-3 text-caption">Enqueued.</span>
|
||||||
|
<QueueStatusBar queue="ml" queue-label="ML" />
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
import { toast } from '../../utils/toast.js'
|
import { toast } from '../../utils/toast.js'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useMLStore } from '../../stores/ml.js'
|
import { useMLStore } from '../../stores/ml.js'
|
||||||
|
import QueueStatusBar from './QueueStatusBar.vue'
|
||||||
const store = useMLStore()
|
const store = useMLStore()
|
||||||
const busy = ref(false)
|
const busy = ref(false)
|
||||||
const done = ref(false)
|
const done = ref(false)
|
||||||
|
|||||||
@@ -10,6 +10,7 @@
|
|||||||
<v-icon start>mdi-refresh</v-icon> Run backfill now
|
<v-icon start>mdi-refresh</v-icon> Run backfill now
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<span v-if="done" class="ml-3 text-caption">Enqueued.</span>
|
<span v-if="done" class="ml-3 text-caption">Enqueued.</span>
|
||||||
|
<QueueStatusBar queue="ml" queue-label="ML" />
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
@@ -18,6 +19,7 @@
|
|||||||
import { toast } from '../../utils/toast.js'
|
import { toast } from '../../utils/toast.js'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useMLStore } from '../../stores/ml.js'
|
import { useMLStore } from '../../stores/ml.js'
|
||||||
|
import QueueStatusBar from './QueueStatusBar.vue'
|
||||||
const store = useMLStore()
|
const store = useMLStore()
|
||||||
const busy = ref(false)
|
const busy = ref(false)
|
||||||
const done = ref(false)
|
const done = ref(false)
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
|
import { onMounted, onUnmounted } from 'vue'
|
||||||
|
|
||||||
import MLBackfillCard from './MLBackfillCard.vue'
|
import MLBackfillCard from './MLBackfillCard.vue'
|
||||||
import CentroidRecomputeCard from './CentroidRecomputeCard.vue'
|
import CentroidRecomputeCard from './CentroidRecomputeCard.vue'
|
||||||
import ThumbnailBackfillCard from './ThumbnailBackfillCard.vue'
|
import ThumbnailBackfillCard from './ThumbnailBackfillCard.vue'
|
||||||
@@ -31,6 +33,22 @@ 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 LegacyMigrationCard from './LegacyMigrationCard.vue'
|
import LegacyMigrationCard from './LegacyMigrationCard.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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|||||||
@@ -0,0 +1,69 @@
|
|||||||
|
<template>
|
||||||
|
<div class="fc-qbar" :class="`fc-qbar--${state}`" :title="hint">
|
||||||
|
<v-icon size="14" class="fc-qbar__icon">{{ icon }}</v-icon>
|
||||||
|
<span>{{ label }}</span>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { computed } from 'vue'
|
||||||
|
|
||||||
|
import { useSystemActivityStore } from '../../stores/systemActivity.js'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
// Celery queue these buttons feed (see celery_app.task_routes).
|
||||||
|
queue: { type: String, required: true },
|
||||||
|
queueLabel: { type: String, default: '' },
|
||||||
|
})
|
||||||
|
|
||||||
|
const store = useSystemActivityStore()
|
||||||
|
|
||||||
|
// Redis LLEN per queue from /api/system/activity/queues; null when the
|
||||||
|
// broker read failed or hasn't loaded yet.
|
||||||
|
const depth = computed(() => {
|
||||||
|
const q = store.queues?.queues
|
||||||
|
return q ? (q[props.queue] ?? null) : null
|
||||||
|
})
|
||||||
|
const state = computed(() => {
|
||||||
|
if (depth.value == null) return 'unknown'
|
||||||
|
return depth.value > 0 ? 'busy' : 'idle'
|
||||||
|
})
|
||||||
|
const icon = computed(() => ({
|
||||||
|
busy: 'mdi-progress-clock',
|
||||||
|
idle: 'mdi-check-circle-outline',
|
||||||
|
unknown: 'mdi-help-circle-outline',
|
||||||
|
}[state.value]))
|
||||||
|
const qname = computed(() => props.queueLabel || props.queue)
|
||||||
|
const label = computed(() => {
|
||||||
|
if (depth.value == null) return `${qname.value} queue · status unavailable`
|
||||||
|
if (depth.value === 0) return `${qname.value} queue idle — nothing pending`
|
||||||
|
return `${depth.value} pending in the ${qname.value} queue`
|
||||||
|
})
|
||||||
|
const hint = computed(() =>
|
||||||
|
state.value === 'busy'
|
||||||
|
? `The ${qname.value} queue already has ${depth.value} task(s) waiting — running again now just adds to that backlog.`
|
||||||
|
: '',
|
||||||
|
)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.fc-qbar {
|
||||||
|
display: flex; align-items: center; gap: 6px;
|
||||||
|
margin-top: 12px;
|
||||||
|
padding: 5px 10px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.78rem;
|
||||||
|
font-variant-numeric: tabular-nums;
|
||||||
|
}
|
||||||
|
.fc-qbar__icon { flex: 0 0 auto; }
|
||||||
|
.fc-qbar--idle,
|
||||||
|
.fc-qbar--unknown {
|
||||||
|
color: rgb(var(--v-theme-on-surface-variant));
|
||||||
|
background: rgb(var(--v-theme-on-surface) / 0.04);
|
||||||
|
}
|
||||||
|
.fc-qbar--busy {
|
||||||
|
color: rgb(var(--v-theme-warning));
|
||||||
|
background: rgb(var(--v-theme-warning) / 0.12);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
<v-icon start>mdi-image-refresh</v-icon> Run backfill now
|
<v-icon start>mdi-image-refresh</v-icon> Run backfill now
|
||||||
</v-btn>
|
</v-btn>
|
||||||
<span v-if="done" class="ml-3 text-caption">Enqueued.</span>
|
<span v-if="done" class="ml-3 text-caption">Enqueued.</span>
|
||||||
|
<QueueStatusBar queue="thumbnail" queue-label="Thumbnail" />
|
||||||
</v-card-text>
|
</v-card-text>
|
||||||
</v-card>
|
</v-card>
|
||||||
</template>
|
</template>
|
||||||
@@ -19,6 +20,7 @@
|
|||||||
import { toast } from '../../utils/toast.js'
|
import { toast } from '../../utils/toast.js'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
import { useThumbnailsStore } from '../../stores/thumbnails.js'
|
import { useThumbnailsStore } from '../../stores/thumbnails.js'
|
||||||
|
import QueueStatusBar from './QueueStatusBar.vue'
|
||||||
const store = useThumbnailsStore()
|
const store = useThumbnailsStore()
|
||||||
const busy = ref(false)
|
const busy = ref(false)
|
||||||
const done = ref(false)
|
const done = ref(false)
|
||||||
|
|||||||
Reference in New Issue
Block a user