Add computer-type cleanup commands
This commit is contained in:
@@ -1875,6 +1875,123 @@ class ComputersPlugin(BasePlugin):
|
|||||||
for t in types:
|
for t in types:
|
||||||
click.echo(f" [{t.computertypeid}] {t.computertype}")
|
click.echo(f" [{t.computertypeid}] {t.computertype}")
|
||||||
|
|
||||||
|
@computerscli.command('retype-by-role')
|
||||||
|
@click.option('--apply', 'apply_changes', is_flag=True,
|
||||||
|
help='Actually write. Without this the command only reports.')
|
||||||
|
@click.option('--typename', default='Metrology PC', show_default=True,
|
||||||
|
help='ComputerType to assign to PCs that drive a measuring tool.')
|
||||||
|
def retype_by_role(apply_changes, typename):
|
||||||
|
"""Type PCs that drive a measuring tool as metrology machines.
|
||||||
|
|
||||||
|
WHY THE RELATIONSHIP AND NOT THE NAME. A PC's role is already
|
||||||
|
recorded in what it controls: a metrology box has an active
|
||||||
|
relationship to a measuring_tool asset. Matching on hostnames or on
|
||||||
|
the tool's name would guess; this reads the fact.
|
||||||
|
|
||||||
|
The imaging scope (gea-shopfloor-keyence and friends) is the OTHER
|
||||||
|
source, and it is the authoritative one - but only 17 of ~292 PCs
|
||||||
|
have ever reported an enforcement scope, so it cannot answer for the
|
||||||
|
fleet today. Relationships can.
|
||||||
|
"""
|
||||||
|
from shopdb.api import Asset
|
||||||
|
|
||||||
|
target = ComputerType.query.filter_by(computertype=typename).first()
|
||||||
|
if not target:
|
||||||
|
if not apply_changes:
|
||||||
|
click.echo(click.style(
|
||||||
|
"Type '%s' does not exist yet; it would be created."
|
||||||
|
% typename, fg='yellow'))
|
||||||
|
else:
|
||||||
|
target = ComputerType(computertype=typename, isactive=True)
|
||||||
|
db.session.add(target)
|
||||||
|
db.session.flush()
|
||||||
|
click.echo("Created ComputerType '%s'." % typename)
|
||||||
|
|
||||||
|
rows = db.session.execute(db.text("""
|
||||||
|
SELECT DISTINCT c.computerid, a.assetnumber, c.hostname,
|
||||||
|
ct.computertype
|
||||||
|
FROM computers c
|
||||||
|
JOIN assets a ON a.assetid = c.assetid
|
||||||
|
LEFT JOIN computertypes ct ON ct.computertypeid = c.computertypeid
|
||||||
|
JOIN assetrelationships r ON r.sourceassetid = c.assetid
|
||||||
|
JOIN assets ta ON ta.assetid = r.targetassetid
|
||||||
|
JOIN assettypes at ON at.assettypeid = ta.assettypeid
|
||||||
|
WHERE at.assettype = 'measuring_tool' AND r.isactive = 1
|
||||||
|
ORDER BY ct.computertype, a.assetnumber
|
||||||
|
""")).fetchall()
|
||||||
|
|
||||||
|
moving = [r for r in rows if (r[3] or '') != typename]
|
||||||
|
click.echo('%d PC(s) drive a measuring tool; %d would change type.'
|
||||||
|
% (len(rows), len(moving)))
|
||||||
|
if not moving:
|
||||||
|
return
|
||||||
|
for r in moving:
|
||||||
|
click.echo(' %-14s %-14s %-16s -> %s'
|
||||||
|
% (r[1], r[2] or '(no hostname)', r[3] or '(none)', typename))
|
||||||
|
|
||||||
|
if not apply_changes:
|
||||||
|
click.echo(click.style(
|
||||||
|
'Dry run. Nothing was written. Re-run with --apply.', fg='yellow'))
|
||||||
|
return
|
||||||
|
|
||||||
|
for r in moving:
|
||||||
|
db.session.get(Computer, r[0]).computertypeid = target.computertypeid
|
||||||
|
db.session.commit()
|
||||||
|
click.echo(click.style('Retyped %d PC(s).' % len(moving), fg='green'))
|
||||||
|
|
||||||
|
@computerscli.command('prune-types')
|
||||||
|
@click.option('--apply', 'apply_changes', is_flag=True,
|
||||||
|
help='Actually delete. Without this the command only reports.')
|
||||||
|
def prune_types(apply_changes):
|
||||||
|
"""Delete computer types nothing uses and nothing maps to.
|
||||||
|
|
||||||
|
Two guards, because a type is cheap to keep and expensive to delete
|
||||||
|
by mistake. A type is KEPT when any computer is on it, or when the
|
||||||
|
live pctype map names it - the map is what the collector assigns
|
||||||
|
from at check-in, so deleting a mapped type would make every future
|
||||||
|
check-in log 'mapped ComputerType not found' instead of typing the
|
||||||
|
machine.
|
||||||
|
"""
|
||||||
|
from .pctypemap import pctype_mapping
|
||||||
|
|
||||||
|
mapped = {str(v).strip().lower() for v in pctype_mapping().values()}
|
||||||
|
counts = dict(db.session.execute(db.text("""
|
||||||
|
SELECT ct.computertypeid, COUNT(c.computerid)
|
||||||
|
FROM computertypes ct
|
||||||
|
LEFT JOIN computers c ON c.computertypeid = ct.computertypeid
|
||||||
|
GROUP BY ct.computertypeid
|
||||||
|
""")).fetchall())
|
||||||
|
|
||||||
|
removable, kept = [], []
|
||||||
|
for ctype in ComputerType.query.order_by(ComputerType.computertype).all():
|
||||||
|
inuse = counts.get(ctype.computertypeid, 0)
|
||||||
|
ismapped = ctype.computertype.strip().lower() in mapped
|
||||||
|
if inuse:
|
||||||
|
kept.append((ctype.computertype, '%d computer(s)' % inuse))
|
||||||
|
elif ismapped:
|
||||||
|
kept.append((ctype.computertype, 'named in the pctype map'))
|
||||||
|
else:
|
||||||
|
removable.append(ctype)
|
||||||
|
|
||||||
|
click.echo('KEEPING %d:' % len(kept))
|
||||||
|
for name, why in kept:
|
||||||
|
click.echo(' %-24s %s' % (name, why))
|
||||||
|
click.echo('')
|
||||||
|
click.echo('DELETING %d:' % len(removable))
|
||||||
|
for ctype in removable:
|
||||||
|
click.echo(' %s' % ctype.computertype)
|
||||||
|
|
||||||
|
if not removable:
|
||||||
|
return
|
||||||
|
if not apply_changes:
|
||||||
|
click.echo(click.style(
|
||||||
|
'Dry run. Nothing was deleted. Re-run with --apply.', fg='yellow'))
|
||||||
|
return
|
||||||
|
for ctype in removable:
|
||||||
|
db.session.delete(ctype)
|
||||||
|
db.session.commit()
|
||||||
|
click.echo(click.style('Deleted %d type(s).' % len(removable), fg='green'))
|
||||||
|
|
||||||
@computerscli.command('stats')
|
@computerscli.command('stats')
|
||||||
def stats():
|
def stats():
|
||||||
"""Show computer statistics."""
|
"""Show computer statistics."""
|
||||||
@@ -1888,8 +2005,10 @@ class ComputersPlugin(BasePlugin):
|
|||||||
|
|
||||||
click.echo(f"Total active computers: {total}")
|
click.echo(f"Total active computers: {total}")
|
||||||
|
|
||||||
# Shopfloor count (by the Shopfloor computer type)
|
# 'Shopfloor PC' - NOT 'Shopfloor', which is an empty
|
||||||
sf = ComputerType.query.filter_by(computertype='Shopfloor').first()
|
# duplicate this counted for as long as both existed, so the
|
||||||
|
# number reported was always 0.
|
||||||
|
sf = ComputerType.query.filter_by(computertype='Shopfloor PC').first()
|
||||||
shopfloor = db.session.query(Computer).join(Asset).filter(
|
shopfloor = db.session.query(Computer).join(Asset).filter(
|
||||||
Asset.isactive == True,
|
Asset.isactive == True,
|
||||||
Computer.computertypeid == (sf.computertypeid if sf else -1)
|
Computer.computertypeid == (sf.computertypeid if sf else -1)
|
||||||
|
|||||||
Reference in New Issue
Block a user