Dashboard cards, backup last-seen

This commit is contained in:
2026-08-11 14:01:57 -04:00
parent c30195a92b
commit 0ed8be75d9
18 changed files with 1167 additions and 9 deletions

View File

@@ -934,3 +934,63 @@ def dashboard_summary():
'shopfloor': shopfloor_count,
'nonshopfloor': total - shopfloor_count
})
@computers_bp.route('/dashboard/quiet', methods=['GET'])
@jwt_required()
@require_permission('computers.view')
def dashboard_quiet():
"""PCs that have stopped reporting, and PCs that never started.
Two populations, deliberately distinguished rather than merged into one
count. A PC that reported and went quiet is probably switched off, moved or
broken. A PC that has NEVER reported is worse: it is either not enrolled, or
enrolled against the wrong pc-type, so nothing enforces anything on it and
no backup of it exists. That one hides indefinitely because nothing about it
is failing loudly - and it is exactly the shape of the bay that carried a
wrong machine number for weeks.
Silence is the only signal available here. There is no heartbeat separate
from the report, so 'stopped reporting' is measured against the collector's
own last write.
"""
from datetime import datetime, timedelta, timezone
hours = 24
setting = Setting.query.filter_by(key='computers_quietreporthours').first()
if setting and (setting.value or '').strip():
try:
hours = int(setting.value)
except (TypeError, ValueError):
pass
cutoff = datetime.now(timezone.utc).replace(tzinfo=None) - timedelta(hours=hours)
query = (db.session.query(Computer, Asset)
.join(Asset, Asset.assetid == Computer.assetid)
.filter(Asset.isactive.is_(True)))
rows = []
for comp, asset in query.all():
last = comp.lastreporteddate
if last is None:
rows.append({
'computerid': comp.computerid,
'hostname': comp.hostname,
'state': 'never reported',
'lastreported': None,
'quietdays': None,
})
elif last < cutoff:
rows.append({
'computerid': comp.computerid,
'hostname': comp.hostname,
'state': 'stopped reporting',
'lastreported': last.isoformat() + 'Z',
'quietdays': (datetime.now(timezone.utc).replace(tzinfo=None)
- last).days,
})
# Never-reported first, then longest silence: the order someone should work
# down the list, not the order the rows came out of the table.
rows.sort(key=lambda r: (r['quietdays'] is not None, -(r['quietdays'] or 0)))
return success_response(rows[:50])