Two fixes to the same table, in the same regions of the same files. ASSET LINK POINTED AT THE WRONG RECORD. The Asset chip linked /machines/<assetid>, but /machines/:id keys on machineid - the plugin extension id - as MachineDetail itself does everywhere. So the link landed on whichever machine happened to carry that number: a wrong page that looks right, which is worse than a 404. Same for /measuringtools/. The API now returns machinepluginid / toolpluginid beside the asset ids and the view links on those. Both lookups are import-guarded, and with no plugin id the number renders as plain text rather than a link that misleads. AssetRelationships already resolved this correctly; this brings the reports table in line. BACKUP COLUMN READ AS NEGLECT. It showed a raw date, and a revision is only written when the config CHANGES - dedup means a machine stable for months has a months-old newest revision and is perfectly healthy. The column already used lastseenat, the last time the collector CONFIRMED the config, but a bare timestamp says "nothing has happened since", which at the default 24h collection interval IS the healthy steady state. It made a working system look stalled and made the reader do arithmetic against a setting they would have to go and find. It now returns backupok and shows a badge naming the kind, green when confirmed recently, red when not, with the date and an explanation in the hover. backupok is tri-state on purpose: null means no revision at all, and renders as NO badge rather than a green one, because "never seen" must not read as healthy. The threshold is the backups plugin's own backups_staledays, read through its service so there is one definition of stale rather than a second drifting here. Nothing in the badge is kind-specific, so a backup kind added later inherits it by existing. docs/BACKUP-KINDS.md records that, the BackupKind contract, and why the rule is time-based rather than per-kind.
119 lines
5.7 KiB
Markdown
119 lines
5.7 KiB
Markdown
# Fleet config backups: adding a kind, and how health is judged
|
|
|
|
This is about the `backups` plugin - the per-PC CONFIG backups the collector
|
|
takes off shopfloor machines (NTLARS settings, part-marker configs). It is not
|
|
about backing up the ShopDB database itself; that is `docs/BACKUP-RESTORE.md`.
|
|
|
|
Two things this covers:
|
|
|
|
1. [Adding a new backup kind](#adding-a-new-backup-kind)
|
|
2. [How the Backup badge decides good vs stale](#how-the-backup-badge-decides-good-vs-stale)
|
|
|
|
---
|
|
|
|
## Adding a new backup kind
|
|
|
|
A kind is one class in `plugins/backups/services/registry.py`, added to
|
|
`REGISTRY` at the bottom of that file:
|
|
|
|
```python
|
|
REGISTRY = {k.key: k for k in (NtlarsKind(), PartMarkerKind())}
|
|
```
|
|
|
|
Subclass `BackupKind` and override what applies. `NtlarsKind` is the fullest
|
|
example (parseable, renderable, has an info panel); `PartMarkerKind` is the
|
|
lean one.
|
|
|
|
| Member | What it is |
|
|
|--------|------------|
|
|
| `key` | Wire value. This is what lands in `backuprevisions.backupkind` and what the fleet table shows on the badge. Keep it short and lowercase. |
|
|
| `displayname` | Human label for the UI. |
|
|
| `storagebackend` | `'shopdb'` (bytes live in the DB, deduped on a semantic hash) or `'share'` (file lives on the SMB share, deduped on a raw byte hash). |
|
|
| `assettypes` | Which core asset types this kind can attach to; `['*']` for any. |
|
|
| `emptytext` | Text when an asset has no revisions of this kind. `None` HIDES the panel, which is the right default - a kind applies to an asset TYPE, but whether a given machine ever has that backup is per-machine. A part-marker panel on all 144 machines is noise. |
|
|
| `parse(raw)` | Opaque kinds return `None`; parseable kinds return a projection dict. |
|
|
| `formats()` / `render(...)` | Download formats, for `shopdb` kinds. |
|
|
| `resolveassetid(payload)` | Map a collector payload to the asset the backup belongs to. |
|
|
| `infopanel()` / `buildinfo(...)` | Optional at-a-glance card (ADR-010). Declared by the KIND, not hardcoded in the plugin, so a successor technology ships its own card by adding a class. |
|
|
| `sharedir(...)` | Conventional UNC directory for a `share` kind. Advisory - the authoritative path is whatever the collector reported, because the PC is what actually wrote the file. |
|
|
|
|
### What you do NOT have to touch
|
|
|
|
The GE-Enforce **Enforcement Reports** Backup column needs no change. It reads
|
|
`backuprevisions` generically: newest revision per host, whatever the kind, and
|
|
shows `backupkind` on the badge. A new kind inherits the badge, the colour and
|
|
the tooltip with no work.
|
|
|
|
Same for the staleness rule below - it is time-based and kind-agnostic.
|
|
|
|
---
|
|
|
|
## How the Backup badge decides good vs stale
|
|
|
|
### The trap this is built around
|
|
|
|
**A revision is only written when the config CHANGES.** Dedup compares against
|
|
the latest revision for the chain `(asset, kind, sourcehostname)`, so a machine
|
|
whose config has been stable for six months has a six-month-old newest revision
|
|
and is perfectly healthy. The question worth answering is not "when was the last
|
|
backup taken" - it is "is this still being checked".
|
|
|
|
So the timestamp the fleet table uses is `backuprevisions.lastseenat`: the last
|
|
time the collector CONFIRMED this config, whether or not anything changed. It
|
|
moves on every successful collection; the revision does not.
|
|
|
|
That was shown as a raw date at first, and it read as neglect. At the default
|
|
`backups_intervalhours` of **24**, the collector only attempts once a day, so a
|
|
day-old confirmation IS the healthy steady state. The date made a working system
|
|
look like a stalled one, and made the reader do arithmetic against a setting
|
|
they would have to go and look up.
|
|
|
|
### What it does now
|
|
|
|
`GET /api/geenforce/reports` returns, per host:
|
|
|
|
| Field | Meaning |
|
|
|-------|---------|
|
|
| `backupkind` | Which kind was most recently confirmed. `null` = no backup at all. |
|
|
| `backuplastseen` | When it was last CONFIRMED (ISO). Tooltip only. |
|
|
| `backupok` | `true` good, `false` stale, `null` nothing to judge. |
|
|
| `backupstaleafterdays` | The threshold in force, so the UI can explain itself. |
|
|
|
|
`backupok` is deliberately **tri-state**. `null` means there is no revision for
|
|
that host, or the check is disabled - and it renders as no badge, never green.
|
|
"Never seen" must not read as healthy.
|
|
|
|
The threshold is the backups plugin's own `backups_staledays` setting (default
|
|
**3**), read through `plugins.backups.services.staleness.staledays()` rather
|
|
than re-derived, so there is ONE definition of stale. `0` disables the check.
|
|
The import is guarded, so a lean site build without the backups plugin returns
|
|
`null` instead of failing.
|
|
|
|
### Why time-based rather than per-kind
|
|
|
|
Every kind answers the same question the same way: something confirmed this
|
|
recently, or it did not. Making the rule per-kind would mean each new kind has
|
|
to define health before it can show a badge, for no gain. If a kind ever needs
|
|
its own window - a weekly backup that should not be judged on a 3-day rule -
|
|
add a threshold override on `BackupKind` and have `_backup_stale_cutoff` prefer
|
|
it; the tri-state contract stays as it is.
|
|
|
|
### Where the code lives
|
|
|
|
- `plugins/geenforce/api/routes.py` - `_attach_backup_state` (newest revision
|
|
per host), `_backup_stale_cutoff` (threshold), `_backup_ok` (verdict)
|
|
- `plugins/geenforce/frontend/views/EnforcementReports.vue` - `backupClass`
|
|
(green / red / none), `backupTitle` (the hover text)
|
|
- `plugins/backups/services/staleness.py` - the shared threshold, also behind
|
|
the dashboard's stale-backups card
|
|
|
|
Tests: `tests/test_plugins/test_geenforce_reporting.py`, the backup-verdict
|
|
block - recent-is-good, older-than-threshold-is-stale, and none-is-not-green.
|
|
|
|
---
|
|
|
|
## See also
|
|
|
|
- `docs/BACKUP-RESTORE.md` - backing up the ShopDB database itself
|
|
- `docs/geenforce-api-cutover.md` - the fleet reporting path these fields ride on
|