diff --git a/CHANGELOG.md b/CHANGELOG.md index e24df33..e879fba 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,13 @@ ADR-007 and ADR-002. ### Added +- Backup history distinguishes CAPTURED from LAST VERIFIED. Captured is when a + configuration was written; verified is when a PC last posted it and the hash + still matched. They differ by design, because an unchanged config writes no + new revision - so a machine checked daily for a year shows one revision from + last spring, and previously read as abandoned. Shown on the history page and + on the asset panel; only the current revision carries it, since an older one + is not what the PC holds now. - A backup now records that it was CHECKED, not only that it changed. Dedup means an unchanged configuration writes no revision, so the stored timestamps moved only on a change: a machine whose settings had been stable for six diff --git a/plugins/backups/frontend/views/BackupHistory.vue b/plugins/backups/frontend/views/BackupHistory.vue index 625e689..6ab4fec 100644 --- a/plugins/backups/frontend/views/BackupHistory.vue +++ b/plugins/backups/frontend/views/BackupHistory.vue @@ -20,6 +20,7 @@ Captured + Last verified Kind From PC Hash @@ -34,6 +35,22 @@ {{ formatWhen(rev.collectedat || rev.createdat) }} current + + + + not yet checked + - + {{ rev.backupkind }} {{ rev.sourcehostname || '-' }} {{ rev.shorthash }} diff --git a/plugins/backups/plugin.py b/plugins/backups/plugin.py index 0e8b5b7..e26567e 100644 --- a/plugins/backups/plugin.py +++ b/plugins/backups/plugin.py @@ -101,6 +101,12 @@ class BackupsPlugin(BasePlugin): 'title': 'label', 'meta': [ {'key': 'collectedat', 'label': 'Captured', 'format': 'date'}, + # Captured is when this config was WRITTEN; verified is + # when a PC last posted it and the hash still matched. + # An unchanged config writes no revision, so without + # this a machine checked daily for a year shows one + # entry from last spring and reads as abandoned. + {'key': 'lastseenat', 'label': 'Verified', 'format': 'date'}, {'key': 'sourcehostname', 'label': 'From'}, {'key': 'shorthash', 'label': 'Hash', 'mono': True}, ], diff --git a/tests/test_plugins/test_backups.py b/tests/test_plugins/test_backups.py index acfa92a..6b08680 100644 --- a/tests/test_plugins/test_backups.py +++ b/tests/test_plugins/test_backups.py @@ -960,3 +960,24 @@ def test_the_threshold_is_a_setting_and_zero_disables_the_card(bk_app, bk_plugin {'value': '0'}) _db.session.commit() assert stalechains() == [] + + +def test_the_history_reports_when_a_config_was_last_verified(bk_app, bk_plugin): + """A machine checked daily for a year still shows one revision, because an + unchanged config writes none. The API has to say the check happened or that + machine reads as abandoned.""" + from plugins.backups.models import BackupRevision + with bk_app.app_context(): + result = bk_plugin.apply_collector_payload(_payload()) + revision = _db.session.get(BackupRevision, result['backuprevisionid']) + data = revision.to_dict() + assert data['lastseenat'] is not None + assert data['lastseenat'].endswith('Z') + + +def test_the_panel_shows_verified_beside_captured(bk_app, bk_plugin): + with bk_app.app_context(): + panels = bk_plugin.get_asset_panels() + listpanel = next(p for p in panels if p.get('render') == 'list') + labels = [m['label'] for m in listpanel['map']['meta']] + assert 'Captured' in labels and 'Verified' in labels