feat: the Backups screen says whether backups are keeping up, and the System view warns when they aren't
CI and images / lint (push) Successful in 2s
CI and images / extension-version (push) Successful in 2s
CI and images / extension-test (push) Successful in 18s
CI and images / frontend-build (push) Successful in 25s
CI and images / backend-lint-and-test (push) Successful in 45s
CI and images / integration (push) Successful in 2m31s
CI and images / sign-extension (push) Successful in 3s
CI and images / build-agent (push) Successful in 5s
CI and images / build-web (push) Successful in 1m54s
CI and images / smoke-web (push) Successful in 48s
CI and images / promote (push) Successful in 1s
CI and images / refresh-dev (push) Skipped
CI and images / unattended-failure (push) Skipped

- A Backups row on Settings → System and the nav dot turn amber when the
  newest database dump is more than a day old or failed, or the weekly
  bucket check found files missing that no media run has sent since.
  No row when backups aren't set up.
- The weekly bucket check is kept as a run (kind "reconcile") with its
  counts, trimmed to the last 12, and listed on the Backups card.
- The Backups card leads with the same verdict and the newest dump's size,
  shows bytes beside the running media job's file count, and lets each run
  open its list of failed files with why.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-11-07 17:51:39 -05:00
co-authored by Claude Opus 5.5
parent 9715143492
commit 66045794f0
11 changed files with 469 additions and 14 deletions
@@ -0,0 +1,43 @@
// @vitest-environment happy-dom
import { describe, it, expect } from 'vitest'
import BackupRunsTable from '../../src/components/settings/BackupRunsTable.vue'
import { mountComponent } from '../support/mountComponent.js'
const MEDIA_RUN = {
id: 7, kind: 'media', status: 'ok', started_at: new Date().toISOString(),
duration_seconds: 90, size_bytes: 2048,
error: "3 file(s) weren't sent; the next run tries them again.",
offsite: { bucket: 'curator', endpoint: 'http://archives.example', key: 'media/' },
manifest: {
failed: 3,
failures: [
{ path: '/images/a/one.jpg', error: 'HTTP 503: busy.' },
{ path: '/images/b/two.zip', error: 'OSError: gone' },
],
},
}
describe('BackupRunsTable', () => {
it('offers a run\'s failed files, counted in full', () => {
const w = mountComponent(BackupRunsTable, { props: { runs: [MEDIA_RUN] } })
expect(w.text()).toContain('3 files failed')
expect(w.text()).not.toContain('/images/a/one.jpg')
})
it('lists each failed file with why once opened, and says how many more', async () => {
const w = mountComponent(BackupRunsTable, { props: { runs: [MEDIA_RUN] } })
await w.find('v-btn').trigger('click')
const t = w.text()
expect(t).toContain('/images/a/one.jpg')
expect(t).toContain('HTTP 503: busy.')
expect(t).toContain('/images/b/two.zip')
expect(t).toContain('and 1 more.')
})
it('shows no failure button on a clean run', () => {
const clean = { ...MEDIA_RUN, id: 8, error: null, manifest: { failed: 0, failures: [] } }
const w = mountComponent(BackupRunsTable, { props: { runs: [clean] } })
expect(w.text()).not.toContain('failed')
})
})