Import the wilm endpoint-monitor and channel-distribution work
Everything on origin/wilm that is not on the published main, applied as one
change because the published history has rewritten hashes: per-commit
cherry-pick conflicts against our main even though the squashed diff applies
clean.
Contents: endpoint monitor catalog + policy attachment + warn-only drift
reconciler, operator issue reports from the tray, agent launch-path allowlist,
and the GHCR channel-image distribution path for dev workstations.
NOT READY FOR main. Open items:
- 10 new routes are absent from docs/api-inventory.json, so
test_api_inventory_parity fails. That gate SKIPS on the published mirror
(the test ships, its data file is scrubbed), which is why wilm never saw it.
- dev-bootstrap.ps1 and Start-ShopDBDev.ps1 hardcode the ge-aero GitHub org.
- plugins/endpoints/plugin.py carries a whitespace-only re-indent.
Scope: product
This commit is contained in:
82
plugins/endpoints/models/operatorreport.py
Normal file
82
plugins/endpoints/models/operatorreport.py
Normal file
@@ -0,0 +1,82 @@
|
||||
"""Operator issue reports (screenshot + free-text notes from the tray).
|
||||
|
||||
An operator on a shop-floor PC opens the tray "Report an issue" dialog, writes a
|
||||
free-text description, and optionally attaches a screenshot of their screen. The
|
||||
agent uploads that to ShopDB, where it becomes one OperatorReport row linked to
|
||||
the reporting PC's asset. The screenshot bytes live on disk under the instance
|
||||
uploads dir (like employee photos); this row holds only metadata plus the
|
||||
relative file path so the image can be served back and purged on retention.
|
||||
|
||||
Reports are transient by design: a retention window (default 7 days) purges old
|
||||
rows and their image files, so this is a rolling operator-signal feed, not a
|
||||
permanent record.
|
||||
"""
|
||||
|
||||
from datetime import datetime
|
||||
|
||||
from shopdb.api import db
|
||||
|
||||
|
||||
def _utciso(value):
|
||||
"""Naive-UTC datetime -> ISO string marked UTC, or None."""
|
||||
return value.isoformat() + 'Z' if value else None
|
||||
|
||||
|
||||
class OperatorReport(db.Model):
|
||||
"""One operator-submitted issue report for a PC asset."""
|
||||
|
||||
__tablename__ = 'operatorreports'
|
||||
|
||||
operatorreportid = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
# The PC this report is about. ON DELETE CASCADE: when the asset goes, its
|
||||
# operator reports go with it.
|
||||
assetid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('assets.assetid', ondelete='CASCADE'),
|
||||
nullable=False,
|
||||
)
|
||||
|
||||
# The dropdown reason. Only 'Other (free text)' is functional today, but the
|
||||
# column is kept so the richer reason routing can return without a migration.
|
||||
reason = db.Column(db.String(128), nullable=False, default='Other (free text)')
|
||||
|
||||
# The operator's free-text description. Nullable: a screenshot alone is a
|
||||
# valid report.
|
||||
details = db.Column(db.Text, nullable=True)
|
||||
|
||||
# Screenshot metadata. imagefilename is the stored file name under the
|
||||
# operator-reports uploads dir; null when the operator declined a screenshot.
|
||||
imagefilename = db.Column(db.String(255), nullable=True)
|
||||
imagemimetype = db.Column(db.String(64), nullable=True)
|
||||
imagebytesize = db.Column(db.Integer, nullable=True)
|
||||
|
||||
# The hostname the agent reported at upload time, kept for traceability even
|
||||
# if the asset link is later re-pointed.
|
||||
reportedhostname = db.Column(db.String(255), nullable=True)
|
||||
|
||||
createdat = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
|
||||
|
||||
__table_args__ = (
|
||||
db.Index('idx_operatorreports_asset', 'assetid'),
|
||||
db.Index('idx_operatorreports_createdat', 'createdat'),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<OperatorReport {self.operatorreportid} asset={self.assetid}>"
|
||||
|
||||
def has_image(self):
|
||||
return bool(self.imagefilename)
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'operatorreportid': self.operatorreportid,
|
||||
'assetid': self.assetid,
|
||||
'reason': self.reason,
|
||||
'details': self.details,
|
||||
'hasimage': self.has_image(),
|
||||
'imagemimetype': self.imagemimetype,
|
||||
'imagebytesize': self.imagebytesize,
|
||||
'reportedhostname': self.reportedhostname,
|
||||
'createdat': _utciso(self.createdat),
|
||||
}
|
||||
Reference in New Issue
Block a user