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:
@@ -0,0 +1,84 @@
|
||||
"""endpoint commands queue + cached MTConnect state.
|
||||
|
||||
Adds the endpointcommands queue (one WebUI-issued, server-allowlisted action
|
||||
per row, claimed and finalized by the agent long-poll) and two cached MTConnect
|
||||
run-state columns on endpointstates so the WebUI toggle can show a real state
|
||||
between command polls.
|
||||
|
||||
Idempotent: create_table_if_missing guards the new table, and the column adds
|
||||
check the live schema first so a db.create_all() site (which already built the
|
||||
columns) re-runs clean on strict MySQL 8.
|
||||
"""
|
||||
from alembic import op
|
||||
|
||||
from shopdb.plugins.alembic_template import (
|
||||
create_index_if_missing,
|
||||
create_table_if_missing,
|
||||
)
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'endpoints0002commands'
|
||||
down_revision = 'endpoints0001baseline'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def _has_column(table_name, column_name):
|
||||
from sqlalchemy import inspect
|
||||
|
||||
inspector = inspect(op.get_bind())
|
||||
if not inspector.has_table(table_name):
|
||||
return False
|
||||
return any(col['name'] == column_name
|
||||
for col in inspector.get_columns(table_name))
|
||||
|
||||
|
||||
def upgrade():
|
||||
if not _has_column('endpointstates', 'mtconnectrunning'):
|
||||
op.add_column('endpointstates',
|
||||
sa.Column('mtconnectrunning', sa.Boolean(),
|
||||
nullable=True))
|
||||
if not _has_column('endpointstates', 'mtconnectcheckedat'):
|
||||
op.add_column('endpointstates',
|
||||
sa.Column('mtconnectcheckedat', sa.DateTime(),
|
||||
nullable=True))
|
||||
|
||||
create_table_if_missing(
|
||||
'endpointcommands',
|
||||
sa.Column('endpointcommandid', sa.Integer(), nullable=False),
|
||||
sa.Column('assetid', sa.Integer(), nullable=False),
|
||||
sa.Column('action', sa.String(length=64), nullable=False),
|
||||
sa.Column('status', sa.String(length=20), nullable=False),
|
||||
sa.Column('requestedby', sa.Integer(), nullable=True),
|
||||
sa.Column('resultmessage', sa.Text(), nullable=True),
|
||||
sa.Column('resultjson', sa.Text(length=16777215), nullable=True),
|
||||
sa.Column('createdat', sa.DateTime(), nullable=False),
|
||||
sa.Column('claimedat', sa.DateTime(), nullable=True),
|
||||
sa.Column('completedat', sa.DateTime(), nullable=True),
|
||||
sa.ForeignKeyConstraint(['assetid'], ['assets.assetid'],
|
||||
ondelete='CASCADE'),
|
||||
sa.PrimaryKeyConstraint('endpointcommandid'),
|
||||
)
|
||||
create_index_if_missing('ix_endpointcommands_assetid',
|
||||
'endpointcommands', ['assetid'])
|
||||
create_index_if_missing('ix_endpointcommands_status',
|
||||
'endpointcommands', ['status'])
|
||||
create_index_if_missing('idx_endpointcommands_assetstatus',
|
||||
'endpointcommands', ['assetid', 'status'])
|
||||
|
||||
|
||||
def downgrade():
|
||||
op.drop_index('idx_endpointcommands_assetstatus',
|
||||
table_name='endpointcommands')
|
||||
op.drop_index('ix_endpointcommands_status',
|
||||
table_name='endpointcommands')
|
||||
op.drop_index('ix_endpointcommands_assetid',
|
||||
table_name='endpointcommands')
|
||||
op.drop_table('endpointcommands')
|
||||
|
||||
if _has_column('endpointstates', 'mtconnectcheckedat'):
|
||||
op.drop_column('endpointstates', 'mtconnectcheckedat')
|
||||
if _has_column('endpointstates', 'mtconnectrunning'):
|
||||
op.drop_column('endpointstates', 'mtconnectrunning')
|
||||
@@ -0,0 +1,50 @@
|
||||
"""endpoint command target for generic service/process actions.
|
||||
|
||||
Adds nullable target and targetkind columns to endpointcommands so generic
|
||||
service_* / process_* actions can carry their subject (a Windows service name or
|
||||
a process image like 'DncMain.exe') over the wire. Fixed-target actions
|
||||
(mtconnect_*) leave both null. This is the schema half of making the agent
|
||||
data-driven: a site builds the agent once and every future policy/app is server
|
||||
data, no agent redistribution.
|
||||
|
||||
Idempotent: each add checks the live schema first so a db.create_all() site
|
||||
(which already built the columns) re-runs clean on strict MySQL 8.
|
||||
"""
|
||||
from alembic import op
|
||||
|
||||
import sqlalchemy as sa
|
||||
|
||||
|
||||
# revision identifiers, used by Alembic.
|
||||
revision = 'endpoints0003cmdtarget'
|
||||
down_revision = 'endpoints0002commands'
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def _has_column(table_name, column_name):
|
||||
from sqlalchemy import inspect
|
||||
|
||||
inspector = inspect(op.get_bind())
|
||||
if not inspector.has_table(table_name):
|
||||
return False
|
||||
return any(col['name'] == column_name
|
||||
for col in inspector.get_columns(table_name))
|
||||
|
||||
|
||||
def upgrade():
|
||||
if not _has_column('endpointcommands', 'target'):
|
||||
op.add_column('endpointcommands',
|
||||
sa.Column('target', sa.String(length=255),
|
||||
nullable=True))
|
||||
if not _has_column('endpointcommands', 'targetkind'):
|
||||
op.add_column('endpointcommands',
|
||||
sa.Column('targetkind', sa.String(length=16),
|
||||
nullable=True))
|
||||
|
||||
|
||||
def downgrade():
|
||||
if _has_column('endpointcommands', 'targetkind'):
|
||||
op.drop_column('endpointcommands', 'targetkind')
|
||||
if _has_column('endpointcommands', 'target'):
|
||||
op.drop_column('endpointcommands', 'target')
|
||||
Reference in New Issue
Block a user