Add fleet alert notifications for shop floor PCs
Fleet alerts push server notifications to endpoint PCs via the command pipe. Operators acknowledge alerts via toast UI in the tray. Backend tracks acknowledgements per asset to prevent re-showing dismissed alerts. Backend: alertfleet/alertscope/alerttimeout columns, GET/POST /api/endpoints/alerts, NotificationAcknowledgement model with unique constraint, hostname validation. Frontend: Alert Fleet checkbox in notification form. Agent: FleetAlertClient polls alerts, FleetAlertStore tracks acknowledged IDs, FleetAlertForm displays toast (no auto-dismiss), FleetAlertWorker 60s polling. Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
52
plugins/notifications/models/acknowledgement.py
Normal file
52
plugins/notifications/models/acknowledgement.py
Normal file
@@ -0,0 +1,52 @@
|
||||
"""Notification acknowledgement tracking for fleet alerts."""
|
||||
|
||||
from datetime import datetime, timezone
|
||||
from shopdb.api import db
|
||||
|
||||
|
||||
class NotificationAcknowledgement(db.Model):
|
||||
"""
|
||||
Tracks which assets have acknowledged fleet alerts.
|
||||
Matches existing notificationacknowledgements table.
|
||||
"""
|
||||
__tablename__ = 'notificationacknowledgements'
|
||||
__table_args__ = (
|
||||
db.UniqueConstraint('notificationid', 'assetid', name='uq_notification_asset'),
|
||||
)
|
||||
|
||||
notificationacknowledgementid = db.Column(db.Integer, primary_key=True)
|
||||
notificationid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('notifications.notificationid', ondelete='CASCADE'),
|
||||
nullable=False,
|
||||
index=True
|
||||
)
|
||||
assetid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('assets.assetid', ondelete='CASCADE'),
|
||||
nullable=False,
|
||||
index=True
|
||||
)
|
||||
acknowledgedat = db.Column(
|
||||
db.DateTime,
|
||||
nullable=False,
|
||||
default=lambda: datetime.now(timezone.utc).replace(tzinfo=None)
|
||||
)
|
||||
|
||||
# Relationships
|
||||
notification = db.relationship('Notification', backref='acknowledgements')
|
||||
asset = db.relationship('Asset', backref='notification_acknowledgements')
|
||||
|
||||
def __repr__(self):
|
||||
return f"<NotificationAcknowledgement {self.notificationacknowledgementid}>"
|
||||
|
||||
def to_dict(self):
|
||||
"""Convert to dictionary following ShopDB patterns."""
|
||||
return {
|
||||
'notificationacknowledgementid': self.notificationacknowledgementid,
|
||||
'notificationid': self.notificationid,
|
||||
'assetid': self.assetid,
|
||||
'acknowledgedat': self.acknowledgedat.replace(
|
||||
tzinfo=timezone.utc
|
||||
).isoformat() if self.acknowledgedat else None
|
||||
}
|
||||
Reference in New Issue
Block a user