harden CSV import reconciliation and preserve saved model selections
Default fuzzy reconcile threshold to 0 and refuse merges when digit runs differ so numbered labels like Bay 311 never bind to Bay 310. Keep edit forms from clearing saved model fields when category filtering hides untyped rows. Includes in-progress computers policy and endpoints command feature work.
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
"""Endpoints plugin models."""
|
||||
|
||||
from .endpoint import EndpointState, EndpointEvent
|
||||
from .endpoint import EndpointState, EndpointEvent, EndpointCommand
|
||||
|
||||
__all__ = ['EndpointState', 'EndpointEvent']
|
||||
__all__ = ['EndpointState', 'EndpointEvent', 'EndpointCommand']
|
||||
|
||||
@@ -75,6 +75,12 @@ class EndpointState(db.Model):
|
||||
interfacesjson = db.Column(db.Text(length=16777215), nullable=True)
|
||||
scheduledtasksjson = db.Column(db.Text(length=16777215), nullable=True)
|
||||
|
||||
# Last-known MTConnect agent run state, cached so the WebUI toggle can show
|
||||
# a real state between command polls. Set by the agent's mtconnect_status
|
||||
# result. Null = never reported (unknown).
|
||||
mtconnectrunning = db.Column(db.Boolean, nullable=True)
|
||||
mtconnectcheckedat = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
# When the agent last successfully reported. There is no heartbeat separate
|
||||
# from the report, so silence past the quiet-hours window is the only
|
||||
# evidence an endpoint has stopped checking in.
|
||||
@@ -97,11 +103,94 @@ class EndpointState(db.Model):
|
||||
'idleseconds': self.idleseconds,
|
||||
'cpupercent': self.cpupercent,
|
||||
'memorypercent': self.memorypercent,
|
||||
'mtconnectrunning': self.mtconnectrunning,
|
||||
'mtconnectcheckedat': _utciso(self.mtconnectcheckedat),
|
||||
'lastreportedat': _utciso(self.lastreportedat),
|
||||
'createdat': _utciso(self.createdat),
|
||||
}
|
||||
|
||||
|
||||
class EndpointCommand(db.Model):
|
||||
"""A queued operator command targeting one endpoint agent.
|
||||
|
||||
The WebUI enqueues a named action (server allowlist only, never an arbitrary
|
||||
command string) for an asset. The agent claims the next pending command on
|
||||
its long-poll, executes it locally, and reports the outcome back, which
|
||||
moves the row to a terminal status in the same request. This is a small
|
||||
at-most-one-in-flight queue, not a general job system.
|
||||
"""
|
||||
|
||||
__tablename__ = 'endpointcommands'
|
||||
|
||||
# Lifecycle: pending -> running -> succeeded|failed. A command is claimed
|
||||
# (pending -> running) by exactly one agent poll, then finalized by its
|
||||
# result post.
|
||||
STATUS_PENDING = 'pending'
|
||||
STATUS_RUNNING = 'running'
|
||||
STATUS_SUCCEEDED = 'succeeded'
|
||||
STATUS_FAILED = 'failed'
|
||||
|
||||
endpointcommandid = db.Column(db.Integer, primary_key=True)
|
||||
|
||||
assetid = db.Column(
|
||||
db.Integer,
|
||||
db.ForeignKey('assets.assetid', ondelete='CASCADE'),
|
||||
nullable=False,
|
||||
index=True,
|
||||
)
|
||||
|
||||
asset = db.relationship('Asset', lazy='joined', viewonly=True)
|
||||
|
||||
# The allowlisted action name (for example 'mtconnect_start'). Validated
|
||||
# against the server allowlist at enqueue time, so only known names land.
|
||||
action = db.Column(db.String(64), nullable=False)
|
||||
|
||||
# Generic actions (service_* / process_*) carry their subject over the wire:
|
||||
# target is a Windows service name or a process image (for example
|
||||
# 'DncMain.exe'); targetkind is 'service' or 'process'. Fixed-target actions
|
||||
# (mtconnect_*) leave both null. This is what lets a site build the agent
|
||||
# once and drive every future policy/app purely from server data.
|
||||
target = db.Column(db.String(255), nullable=True)
|
||||
targetkind = db.Column(db.String(16), nullable=True)
|
||||
|
||||
status = db.Column(db.String(20), nullable=False,
|
||||
default=STATUS_PENDING, index=True)
|
||||
|
||||
# Who queued it (userid) for audit; the agent never sees this.
|
||||
requestedby = db.Column(db.Integer, nullable=True)
|
||||
|
||||
# Agent-reported outcome. resultjson holds structured detail (for example
|
||||
# the observed MTConnect running state); resultmessage is a short summary.
|
||||
resultmessage = db.Column(db.Text, nullable=True)
|
||||
resultjson = db.Column(db.Text(length=16777215), nullable=True)
|
||||
|
||||
createdat = db.Column(db.DateTime, nullable=False, default=datetime.utcnow)
|
||||
claimedat = db.Column(db.DateTime, nullable=True)
|
||||
completedat = db.Column(db.DateTime, nullable=True)
|
||||
|
||||
__table_args__ = (
|
||||
db.Index('idx_endpointcommands_assetstatus', 'assetid', 'status'),
|
||||
)
|
||||
|
||||
def __repr__(self):
|
||||
return f"<EndpointCommand {self.action} asset={self.assetid} {self.status}>"
|
||||
|
||||
def to_dict(self):
|
||||
return {
|
||||
'endpointcommandid': self.endpointcommandid,
|
||||
'assetid': self.assetid,
|
||||
'action': self.action,
|
||||
'target': self.target,
|
||||
'targetkind': self.targetkind,
|
||||
'status': self.status,
|
||||
'requestedby': self.requestedby,
|
||||
'resultmessage': self.resultmessage,
|
||||
'createdat': _utciso(self.createdat),
|
||||
'claimedat': _utciso(self.claimedat),
|
||||
'completedat': _utciso(self.completedat),
|
||||
}
|
||||
|
||||
|
||||
class EndpointEvent(db.Model):
|
||||
"""A single curated Windows Event Log record forwarded by the agent."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user