Endpoint telemetry, PC Detail rework and a checked backup/restore path

This commit is contained in:
2026-09-06 18:53:11 -04:00
parent 32b8745d10
commit 993353dfc0
97 changed files with 13340 additions and 756 deletions

View File

@@ -80,6 +80,65 @@ class EndpointState(db.Model):
interfacesjson = db.Column(db.Text(length=16777215), nullable=True)
scheduledtasksjson = db.Column(db.Text(length=16777215), nullable=True)
# Hardware and OS inventory, same newest-snapshot rule as above. These
# arrived on the wire well before this and were logged as unknown keys, so
# the shapes are already proven against real endpoint traffic.
disksjson = db.Column(db.Text(length=16777215), nullable=True)
biosjson = db.Column(db.Text(length=16777215), nullable=True)
memoryjson = db.Column(db.Text(length=16777215), nullable=True)
processorjson = db.Column(db.Text(length=16777215), nullable=True)
osinfojson = db.Column(db.Text(length=16777215), nullable=True)
# Session-scoped data collected by a probe running as the signed-in user
# (mapped drives, user-owned listeners). Null when nobody is signed in,
# which is a normal unattended state rather than a fault.
usercontextjson = db.Column(db.Text(length=16777215), nullable=True)
# Windows domain or workgroup membership. A scalar, not JSON: it is a single
# value the fleet view filters and groups on.
domain = db.Column(db.String(255), nullable=True)
# True when the endpoint is holding a reboot. Null = never reported, which
# is deliberately distinct from a confident False.
pendingreboot = db.Column(db.Boolean, nullable=True)
# Per-drive health from the agent's two disk sources. JSON because a machine
# has a variable number of drives and each carries its own verdict. Kept
# separate from disksjson: that is capacity, this is whether the hardware is
# dying. Null = never reported.
diskhealthjson = db.Column(db.Text(length=16777215), nullable=True)
# Which signal(s) drove pendingreboot. That column is a single bool OR-ed
# from three sources and so cannot say WHY, and the difference matters: a
# pendingfilerename means files are staged for replacement on next boot, so
# the box is mid-servicing, while a lone servicing key can sit set for weeks
# on a machine nobody rebooted. Empty list = checked, nothing pending.
# Null = never reported or the check failed.
rebootreasonsjson = db.Column(db.Text(length=16777215), nullable=True)
# Fast startup configuration and what the last boot actually did. Kept
# because uptime is not trustworthy under fast startup: a box can report
# days of uptime after what the operator experienced as a shutdown, and a
# "reboot" that was a hybrid resume does not clear the state a support call
# is usually trying to clear.
bootinfojson = db.Column(db.Text(length=16777215), nullable=True)
# Defender health. Persisted but deliberately NOT surfaced anywhere yet:
# this environment produces known false positives, and wiring it to a card
# now would train operators to ignore that card. Stored so the history is
# already accumulating when someone is ready to interpret it.
defenderjson = db.Column(db.Text(length=16777215), nullable=True)
# Pending Dell Command | Update items, produced by the shopdb-dellupdates
# agent utility from the log the \ShopDB-DellCommandScan task writes. Only
# the count drives the status card; the short list is stored so the panel
# can show what is actually pending without a second round trip.
#
# A null column and a stored payload with status 'unknown' both mean "never
# scanned", which stays distinct from a scan that found nothing pending. The
# status card relies on that distinction - see _dell_updates_field.
dellupdatesjson = db.Column(db.Text(length=16777215), nullable=True)
# Warn-only reconciler output, refreshed on every agent check-in in the same
# commit as servicesjson. Each entry names a policy/shipped-attached monitor
# the endpoint is expected to be running but the latest snapshot does not
@@ -116,6 +175,8 @@ class EndpointState(db.Model):
'idleseconds': self.idleseconds,
'cpupercent': self.cpupercent,
'memorypercent': self.memorypercent,
'domain': self.domain,
'pendingreboot': self.pendingreboot,
'mtconnectrunning': self.mtconnectrunning,
'mtconnectcheckedat': _utciso(self.mtconnectcheckedat),
'lastreportedat': _utciso(self.lastreportedat),
@@ -255,6 +316,16 @@ class EndpointEvent(db.Model):
eventchannel = db.Column(db.String(50), nullable=True)
eventsource = db.Column(db.String(255), nullable=True)
# The Windows event record id: monotonic and unique within its channel on a
# given machine. This is the dedup key. The agent also uses it as its
# harvest watermark, so in normal operation a record is sent once; the
# unique index below is what guarantees it even when an agent is reinstalled
# (losing its watermark) or two reports overlap. Nullable because an agent
# older than this field still reports, and MySQL permits repeated NULLs in a
# unique index, so those degrade to the previous append-only behavior rather
# than failing.
eventrecordid = db.Column(db.BigInteger, nullable=True)
# When the event happened on the endpoint (from the record), not when it
# reached ShopDB.
eventtime = db.Column(db.DateTime, nullable=True, index=True)
@@ -269,6 +340,8 @@ class EndpointEvent(db.Model):
__table_args__ = (
db.Index('idx_endpointevents_assetevent', 'assetid', 'eventtime'),
db.Index('idx_endpointevents_assetrecord',
'assetid', 'eventchannel', 'eventrecordid', unique=True),
)
def __repr__(self):
@@ -279,6 +352,7 @@ class EndpointEvent(db.Model):
'endpointeventid': self.endpointeventid,
'assetid': self.assetid,
'eventid': self.eventid,
'eventrecordid': self.eventrecordid,
'eventchannel': self.eventchannel,
'eventsource': self.eventsource,
'eventtime': _utciso(self.eventtime),