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
|
### 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
|
- 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
|
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
|
moved only on a change: a machine whose settings had been stable for six
|
||||||
|
|||||||
@@ -20,6 +20,7 @@
|
|||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Captured</th>
|
<th>Captured</th>
|
||||||
|
<th>Last verified</th>
|
||||||
<th>Kind</th>
|
<th>Kind</th>
|
||||||
<th>From PC</th>
|
<th>From PC</th>
|
||||||
<th>Hash</th>
|
<th>Hash</th>
|
||||||
@@ -34,6 +35,22 @@
|
|||||||
{{ formatWhen(rev.collectedat || rev.createdat) }}
|
{{ formatWhen(rev.collectedat || rev.createdat) }}
|
||||||
<span v-if="rev.islatest" class="bh-badge">current</span>
|
<span v-if="rev.islatest" class="bh-badge">current</span>
|
||||||
</td>
|
</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.backupkind }}</td>
|
||||||
<td>{{ rev.sourcehostname || '-' }}</td>
|
<td>{{ rev.sourcehostname || '-' }}</td>
|
||||||
<td class="bh-mono">{{ rev.shorthash }}</td>
|
<td class="bh-mono">{{ rev.shorthash }}</td>
|
||||||
|
|||||||
@@ -101,6 +101,12 @@ class BackupsPlugin(BasePlugin):
|
|||||||
'title': 'label',
|
'title': 'label',
|
||||||
'meta': [
|
'meta': [
|
||||||
{'key': 'collectedat', 'label': 'Captured', 'format': 'date'},
|
{'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': 'sourcehostname', 'label': 'From'},
|
||||||
{'key': 'shorthash', 'label': 'Hash', 'mono': True},
|
{'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'})
|
{'value': '0'})
|
||||||
_db.session.commit()
|
_db.session.commit()
|
||||||
assert stalechains() == []
|
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