Merge remote-tracking branch 'origin/main' into wilm
This commit is contained in:
@@ -16,16 +16,14 @@ from .api import computers_bp
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Marker stamped on PC->printer links this collector creates. The stale-link
|
||||
# archive only touches rows carrying this label, so manually-created printer
|
||||
# relationships are never removed by a collector push. Stored in the
|
||||
# assetrelationships.label column (no origin column exists; see BUILD notes).
|
||||
PRINTER_LINK_ORIGIN = 'collector:printers'
|
||||
|
||||
# Marker stamped on PC->measuringtool "controls" links this collector creates
|
||||
# for metrology PCs (CMM, Keyence, Genspect, wax-and-trace). Same stale-link
|
||||
# discipline as PRINTER_LINK_ORIGIN: only rows carrying this label are archived
|
||||
# by a collector push, so hand-made tool links survive.
|
||||
# for metrology PCs (CMM, Keyence, Genspect, wax-and-trace). Only rows carrying
|
||||
# this label are archived by a collector push, so hand-made tool links survive.
|
||||
# Stored in assetrelationships.label (no origin column exists; see BUILD notes).
|
||||
#
|
||||
# There is deliberately no printer equivalent any more. PRINTER_LINK_ORIGIN went
|
||||
# with _sync_printer_links: printer ASSIGNMENT is never collected, so there are
|
||||
# no collector-owned printer rows left to label or archive.
|
||||
MEASURINGTOOL_LINK_ORIGIN = 'collector:measuringtool'
|
||||
|
||||
# Marker stamped on the PC->machine "controls" link built from the reported
|
||||
@@ -363,26 +361,10 @@ class ComputersPlugin(BasePlugin):
|
||||
'type': 'array',
|
||||
'items': {'name': 'string', 'version': 'string'},
|
||||
},
|
||||
# Printer identifiers reported by the GE-Enforce side, which
|
||||
# runs Get-CimInstance Win32_Printer and marks the default with
|
||||
# the Default flag. An identifier is the printer's windows name,
|
||||
# share name, hostname, or port/IP; the collector resolves it
|
||||
# flexibly to a printer asset. Both optional. Presence of either
|
||||
# key drives the PC->printer relationship sync (and stale-link
|
||||
# archive); absence leaves existing printer links untouched.
|
||||
'defaultprinter': {
|
||||
'type': 'string',
|
||||
'description': ('Default printer identifier (Win32_Printer '
|
||||
'with Default=true): windows name, share '
|
||||
'name, hostname, or port IP.'),
|
||||
},
|
||||
'printers': {
|
||||
'type': 'array',
|
||||
'items': {'type': 'string'},
|
||||
'description': ('All installed network printer identifiers '
|
||||
'(Win32_Printer): windows name / share / '
|
||||
'hostname / IP. Unresolved -> warning.'),
|
||||
},
|
||||
# RETIRED: 'defaultprinter' and 'printers' used to live here and
|
||||
# wrote PC->printer assignment rows from a report. That is the
|
||||
# one thing this path must never do - see _RETIRED_PRINTER_KEYS.
|
||||
# A client still sending them is warned, not rejected.
|
||||
'accessprotocols': {
|
||||
'type': 'array',
|
||||
'items': {'type': 'string'},
|
||||
@@ -647,8 +629,9 @@ class ComputersPlugin(BasePlugin):
|
||||
else:
|
||||
machinelinks = []
|
||||
|
||||
# Printer relationship sync (only when the payload carried printer data).
|
||||
printerlinks = self._sync_printer_links(comp.asset, payload, warnings)
|
||||
# Printer assignment is NOT collected. See _warn_retired_printer_keys.
|
||||
self._warn_retired_printer_keys(payload, warnings)
|
||||
printerlinks = []
|
||||
|
||||
# Measuring-tool sync: metrology PCs (CMM/Keyence/Genspect/wax-trace)
|
||||
# get an attached MeasuringTool asset auto-created and linked.
|
||||
@@ -742,134 +725,38 @@ class ComputersPlugin(BasePlugin):
|
||||
AccessProtocol.protocolid.in_(wanted)).all()] if wanted else []
|
||||
return sorted(names)
|
||||
|
||||
def _sync_printer_links(self, pcasset, payload, warnings):
|
||||
"""Idempotently sync PC->printer relationships from collector printer data.
|
||||
def _warn_retired_printer_keys(self, payload, warnings):
|
||||
"""Refuse to turn a report into an assignment, out loud.
|
||||
|
||||
Default printer -> 'defaultprinter' (directional). Other reported
|
||||
printers -> 'connectedto' (symmetric). Resolves each identifier to a
|
||||
printer asset by windows name / share / hostname / asset number / name
|
||||
or a communications IP. Unresolved identifiers add a warning and never
|
||||
fail the push.
|
||||
RETIRED 2026-08-24. This used to be _sync_printer_links, which wrote
|
||||
'defaultprinter' and 'connectedto' rows straight from a collector
|
||||
payload. That contradicted the rule the whole printer feature rests on:
|
||||
ASSIGNED and OBSERVED never mix. A bay that installed the wrong printer
|
||||
would have made itself right simply by reporting it, and enforcement
|
||||
would stop meaning anything.
|
||||
|
||||
Stale-link archive: on each push, collector-tagged links (label ==
|
||||
PRINTER_LINK_ORIGIN) whose (target, type) pair is not in the reported
|
||||
desired set are set inactive. Only tagged rows are touched, so manual
|
||||
links survive. Runs only when the payload carried a printer key
|
||||
('defaultprinter' or 'printers'); a PC that reports without printer data
|
||||
keeps its existing links. Returns the desired-link list (created + kept).
|
||||
Worse than dormant, it was ADVERTISED: the collector schema listed both
|
||||
keys and COLLECTOR-INTEGRATION.md's example client set defaultprinter,
|
||||
while the same document promised on another page that nothing on this
|
||||
path writes an assignment row. The document contradicted itself and the
|
||||
code sided with the wrong half.
|
||||
|
||||
Observed queues have their own path (POST /api/collector/printers) and
|
||||
become assignments only when a person asks, through
|
||||
seed-from-observed after reviewing the comparison.
|
||||
|
||||
WARNS RATHER THAN REJECTS. Unknown keys are not validated here, so a
|
||||
client written against the old docs keeps pushing successfully; only its
|
||||
printer keys stop doing anything. Silently ignoring them would leave an
|
||||
integrator believing assignments were being written.
|
||||
"""
|
||||
from shopdb.api import (
|
||||
AssetRelationship, RelationshipType, Asset, Communication)
|
||||
|
||||
has_default = 'defaultprinter' in payload
|
||||
has_list = 'printers' in payload
|
||||
if not has_default and not has_list:
|
||||
return []
|
||||
|
||||
try:
|
||||
from plugins.printers.models import Printer
|
||||
except ImportError:
|
||||
warnings.append('printers plugin unavailable; printer links skipped')
|
||||
return []
|
||||
|
||||
dp_type = RelationshipType.query.filter_by(
|
||||
relationshiptype='defaultprinter').first()
|
||||
ct_type = RelationshipType.query.filter_by(
|
||||
relationshiptype='connectedto').first()
|
||||
if not dp_type or not ct_type:
|
||||
warnings.append('printer relationship types missing; '
|
||||
'run flask seed reference-data')
|
||||
return []
|
||||
|
||||
def resolve(identifier):
|
||||
# first match wins: printer text identity, then a printer IP.
|
||||
ident = (identifier or '').strip()
|
||||
if not ident:
|
||||
return None
|
||||
printer = (
|
||||
Printer.query.join(Asset, Asset.assetid == Printer.assetid)
|
||||
.filter(Asset.isactive == True)
|
||||
.filter(db.or_(
|
||||
Printer.windowsname.ilike(ident),
|
||||
Printer.hostname.ilike(ident),
|
||||
Printer.sharename.ilike(ident),
|
||||
Asset.assetnumber.ilike(ident),
|
||||
Asset.name.ilike(ident),
|
||||
)).first())
|
||||
if printer:
|
||||
return printer.asset
|
||||
comm = (
|
||||
db.session.query(Communication)
|
||||
.join(Printer, Printer.assetid == Communication.assetid)
|
||||
.filter(Communication.ipaddress == ident)
|
||||
.first())
|
||||
if comm:
|
||||
return db.session.get(Asset, comm.assetid)
|
||||
return None
|
||||
|
||||
pcid = pcasset.assetid
|
||||
desired = set() # (targetassetid, relationshiptypeid) to keep
|
||||
printerlinks = []
|
||||
|
||||
default_id = None
|
||||
default_ident = (payload.get('defaultprinter') or '').strip()
|
||||
if default_ident:
|
||||
target = resolve(default_ident)
|
||||
if target:
|
||||
default_id = target.assetid
|
||||
desired.add((default_id, dp_type.relationshiptypeid))
|
||||
self._sync_one(pcid, default_id, dp_type)
|
||||
printerlinks.append({'assetid': default_id,
|
||||
'relationshiptype': 'defaultprinter'})
|
||||
else:
|
||||
warnings.append(f'unresolved default printer: {default_ident}')
|
||||
|
||||
for ident in payload.get('printers') or []:
|
||||
target = resolve(ident)
|
||||
if not target:
|
||||
warnings.append(f'unresolved printer: {ident}')
|
||||
continue
|
||||
if target.assetid == default_id:
|
||||
continue # already the default link
|
||||
desired.add((target.assetid, ct_type.relationshiptypeid))
|
||||
self._sync_one(pcid, target.assetid, ct_type)
|
||||
printerlinks.append({'assetid': target.assetid,
|
||||
'relationshiptype': 'connectedto'})
|
||||
|
||||
# Archive collector-tagged links no longer reported (manual links, with
|
||||
# a NULL/other label, are never matched here).
|
||||
collector_rows = AssetRelationship.query.filter(
|
||||
AssetRelationship.sourceassetid == pcid,
|
||||
AssetRelationship.relationshiptypeid.in_(
|
||||
[dp_type.relationshiptypeid, ct_type.relationshiptypeid]),
|
||||
AssetRelationship.isactive == True,
|
||||
AssetRelationship.label == PRINTER_LINK_ORIGIN,
|
||||
).all()
|
||||
for rel in collector_rows:
|
||||
if (rel.targetassetid, rel.relationshiptypeid) not in desired:
|
||||
rel.isactive = False
|
||||
|
||||
return printerlinks
|
||||
|
||||
def _sync_one(self, pcid, printerid, reltype):
|
||||
"""Reactivate or create one collector PC->printer link (idempotent)."""
|
||||
from shopdb.api import AssetRelationship
|
||||
existing = AssetRelationship.query.filter_by(
|
||||
sourceassetid=pcid, targetassetid=printerid,
|
||||
relationshiptypeid=reltype.relationshiptypeid).first()
|
||||
if existing:
|
||||
# do not re-stamp label: a pre-existing manual row stays manual.
|
||||
if not existing.isactive:
|
||||
existing.isactive = True
|
||||
return existing
|
||||
rel = AssetRelationship(
|
||||
sourceassetid=pcid, targetassetid=printerid,
|
||||
relationshiptypeid=reltype.relationshiptypeid,
|
||||
label=PRINTER_LINK_ORIGIN)
|
||||
db.session.add(rel)
|
||||
return rel
|
||||
|
||||
# -- measuring-tool sync -----------------------------------------------
|
||||
sent = [key for key in ('defaultprinter', 'printers') if key in payload]
|
||||
if sent:
|
||||
warnings.append(
|
||||
'%s ignored: a collector report never sets printer assignment. '
|
||||
'Report queues to POST /api/collector/printers, then adopt them '
|
||||
'with POST /api/printers/assignments/seed-from-observed.'
|
||||
% ' and '.join(sent))
|
||||
|
||||
def _sync_machine_link(self, comp, machinenumber, warnings,
|
||||
origin_label=MACHINE_LINK_ORIGIN, preview=False):
|
||||
|
||||
Reference in New Issue
Block a user