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:
@@ -0,0 +1,103 @@
|
||||
"""Add fleet alert support to notifications plugin.
|
||||
|
||||
Adds alertfleet, alertscope, and alerttimeout columns to the notifications table
|
||||
to support fleet-wide alert broadcasting. Creates notificationacknowledgements
|
||||
table to track which assets have acknowledged fleet alerts.
|
||||
|
||||
Fleet alerts can be scoped to specific asset types, locations, or business units
|
||||
via the alertscope JSONB field. alerttimeout=0 means no auto-dismiss (manual only).
|
||||
|
||||
Idempotent: column and table creation are skipped when they already exist.
|
||||
|
||||
Revision ID: notifications0006fleetalerts
|
||||
Revises: notifications0005boardorder
|
||||
"""
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects.postgresql import JSONB
|
||||
|
||||
|
||||
revision = 'notifications0006fleetalerts'
|
||||
down_revision = 'notifications0005boardorder'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def _column_names(insp, table):
|
||||
return {c['name'] for c in insp.get_columns(table)}
|
||||
|
||||
|
||||
def upgrade():
|
||||
bind = op.get_bind()
|
||||
insp = sa.inspect(bind)
|
||||
existing_tables = set(insp.get_table_names())
|
||||
|
||||
# Add alertfleet, alertscope, alerttimeout to notifications table
|
||||
if 'notifications' in existing_tables:
|
||||
cols = _column_names(insp, 'notifications')
|
||||
|
||||
if 'alertfleet' not in cols:
|
||||
op.add_column('notifications',
|
||||
sa.Column('alertfleet', sa.Boolean(), nullable=False,
|
||||
server_default=sa.false()))
|
||||
|
||||
if 'alertscope' not in cols:
|
||||
op.add_column('notifications',
|
||||
sa.Column('alertscope', JSONB, nullable=True))
|
||||
|
||||
if 'alerttimeout' not in cols:
|
||||
op.add_column('notifications',
|
||||
sa.Column('alerttimeout', sa.Integer(), nullable=False,
|
||||
server_default='0'))
|
||||
|
||||
# Create notificationacknowledgements table
|
||||
if 'notificationacknowledgements' not in existing_tables:
|
||||
op.create_table(
|
||||
'notificationacknowledgements',
|
||||
sa.Column('notificationacknowledgementid', sa.Integer(), nullable=False),
|
||||
sa.Column('notificationid', sa.Integer(), nullable=False),
|
||||
sa.Column('assetid', sa.Integer(), nullable=False),
|
||||
sa.Column('acknowledgedat', sa.DateTime(), nullable=False,
|
||||
server_default=sa.text('NOW()')),
|
||||
sa.PrimaryKeyConstraint('notificationacknowledgementid'),
|
||||
sa.ForeignKeyConstraint(['notificationid'],
|
||||
['notifications.notificationid'],
|
||||
ondelete='CASCADE'),
|
||||
sa.ForeignKeyConstraint(['assetid'], ['assets.assetid'],
|
||||
ondelete='CASCADE'),
|
||||
sa.UniqueConstraint('notificationid', 'assetid',
|
||||
name='uq_notification_asset')
|
||||
)
|
||||
op.create_index('idx_notificationack_notification',
|
||||
'notificationacknowledgements',
|
||||
['notificationid'])
|
||||
op.create_index('idx_notificationack_asset',
|
||||
'notificationacknowledgements',
|
||||
['assetid'])
|
||||
|
||||
|
||||
def downgrade():
|
||||
bind = op.get_bind()
|
||||
insp = sa.inspect(bind)
|
||||
existing_tables = set(insp.get_table_names())
|
||||
|
||||
# Drop notificationacknowledgements table
|
||||
if 'notificationacknowledgements' in existing_tables:
|
||||
op.drop_index('idx_notificationack_asset',
|
||||
table_name='notificationacknowledgements')
|
||||
op.drop_index('idx_notificationack_notification',
|
||||
table_name='notificationacknowledgements')
|
||||
op.drop_table('notificationacknowledgements')
|
||||
|
||||
# Drop alert columns from notifications table
|
||||
if 'notifications' in existing_tables:
|
||||
cols = _column_names(insp, 'notifications')
|
||||
|
||||
if 'alerttimeout' in cols:
|
||||
op.drop_column('notifications', 'alerttimeout')
|
||||
|
||||
if 'alertscope' in cols:
|
||||
op.drop_column('notifications', 'alertscope')
|
||||
|
||||
if 'alertfleet' in cols:
|
||||
op.drop_column('notifications', 'alertfleet')
|
||||
Reference in New Issue
Block a user