backups: show that a check happened, not just that a change did
lastseenat already recorded it and the API already returned it; nothing displayed it, so from the UI a healthy machine still looked abandoned - one revision from last spring and no sign anything had looked at it since. The history page gains a Last verified column beside Captured, and the asset panel a Verified field. Only the CURRENT revision carries one: an older revision was superseded, so saying it was verified today would be false - what was verified is the configuration the PC holds now. A current revision with no check yet says "not yet checked" rather than showing a blank or borrowing the captured date. That state is real and temporary: the column is new, so every chain reports it until its PC next posts.
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -20,6 +20,7 @@
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Captured</th>
|
||||
<th>Last verified</th>
|
||||
<th>Kind</th>
|
||||
<th>From PC</th>
|
||||
<th>Hash</th>
|
||||
@@ -34,6 +35,22 @@
|
||||
{{ formatWhen(rev.collectedat || rev.createdat) }}
|
||||
<span v-if="rev.islatest" class="bh-badge">current</span>
|
||||
</td>
|
||||
<!-- CAPTURED is when this configuration was written; LAST VERIFIED is
|
||||
when a PC last posted it and the hash still matched. They differ
|
||||
by design: an unchanged config writes no new revision, so a
|
||||
machine can be checked daily for a year and still show one
|
||||
revision from last spring. Without this column that machine
|
||||
looked abandoned. Only the current revision is verified - an
|
||||
older one was superseded, so it is not what the PC holds now. -->
|
||||
<td>
|
||||
<template v-if="rev.islatest && rev.lastseenat">
|
||||
{{ formatWhen(rev.lastseenat) }}
|
||||
</template>
|
||||
<span v-else-if="rev.islatest" class="bh-muted"
|
||||
title="No check recorded since this was introduced. The next
|
||||
collection will record one.">not yet checked</span>
|
||||
<span v-else class="bh-muted">-</span>
|
||||
</td>
|
||||
<td>{{ rev.backupkind }}</td>
|
||||
<td>{{ rev.sourcehostname || '-' }}</td>
|
||||
<td class="bh-mono">{{ rev.shorthash }}</td>
|
||||
|
||||
@@ -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},
|
||||
],
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user