import { defineStore } from 'pinia' import { ref } from 'vue' import { useApi } from '../composables/useApi.js' export const useSystemActivityStore = defineStore('systemActivity', () => { const api = useApi() // Live polled state. const queues = ref(null) // { queues: {name: depth|null}, fetched_at } const workers = ref(null) // { workers: {hostname: {...}}, fetched_at } const recentRuns = ref([]) // last-60s rows (for Overview summary) const failures = ref(null) // { recent, count_by_type, since } // Worker lanes (milestone 422): the configured slots joined to the live // pool. Lives here rather than in its own store because it is the same // domain the queues and workers above describe — a second store polling // /api/system/* would be two things to keep in step. const lanes = ref(null) // { lanes: [...], fetched_at } // Paginated runs (Activity tab "All recent activity" pane). const runs = ref([]) const runsCursor = ref(null) const runsHasMore = ref(false) const runsFilter = ref({ queue: null, status: null, task: null, limit: 50 }) const loading = ref({ queues: false, workers: false, runs: false, failures: false, lanes: false, }) const lastError = ref(null) async function loadQueues() { loading.value.queues = true lastError.value = null try { queues.value = await api.get('/api/system/activity/queues') } catch (e) { lastError.value = e.message } finally { loading.value.queues = false } } async function loadWorkers() { loading.value.workers = true lastError.value = null try { workers.value = await api.get('/api/system/activity/workers') } catch (e) { lastError.value = e.message } finally { loading.value.workers = false } } async function loadLanes() { loading.value.lanes = true lastError.value = null try { lanes.value = await api.get('/api/system/workers') } catch (e) { lastError.value = e.message } finally { loading.value.lanes = false } } // Change one lane. Returns the endpoint's reply so the caller can tell a // stored-but-not-yet-live change (`applied: false`) from a live one — the // difference between "saved, the lane is restarting" and "that failed", // which the UI must not collapse into one message. // // Deliberately NOT swallowing the error: a refused value (400) carries the // sentence explaining why, and the card shows it. Returning null on failure // would leave the operator with a control that silently did nothing. async function setLane(name, fields) { const reply = await api.post(`/api/system/workers/${name}`, { body: fields }) await loadLanes() return reply } async function loadRecentRuns() { // Used by the Overview summary card: pull last 60s of runs to compute // per-queue ok/err counts. One call covers all queues; UI groups. try { const body = await api.get('/api/system/activity/runs', { params: { limit: 200 }, }) recentRuns.value = body.runs || [] } catch (e) { lastError.value = e.message } } async function loadRuns({ reset = false } = {}) { loading.value.runs = true lastError.value = null try { const params = { limit: runsFilter.value.limit } if (runsFilter.value.queue) params.queue = runsFilter.value.queue if (runsFilter.value.status) params.status = runsFilter.value.status if (runsFilter.value.task) params.task = runsFilter.value.task if (!reset && runsCursor.value) params.before_id = runsCursor.value const body = await api.get('/api/system/activity/runs', { params }) runs.value = reset ? body.runs : [...runs.value, ...body.runs] runsCursor.value = body.next_cursor runsHasMore.value = body.next_cursor !== null } catch (e) { lastError.value = e.message } finally { loading.value.runs = false } } async function loadFailures() { loading.value.failures = true lastError.value = null try { failures.value = await api.get('/api/system/activity/failures') } catch (e) { lastError.value = e.message } finally { loading.value.failures = false } } function setFilter(filter) { runsFilter.value = { ...runsFilter.value, ...filter } } // One-call rollup for the always-on TopNav pipeline indicator: // { scheduler, queues, queued_total, running, failing }. const summary = ref(null) async function loadSummary() { try { summary.value = await api.get('/api/system/activity/summary') } catch (e) { lastError.value = e.message } return summary.value } return { queues, workers, recentRuns, failures, summary, lanes, runs, runsCursor, runsHasMore, runsFilter, loading, lastError, loadQueues, loadWorkers, loadRecentRuns, loadLanes, setLane, loadRuns, loadFailures, loadSummary, setFilter, } })