Migrate frontend to plugin-based asset architecture

- Add equipmentApi and computersApi to replace legacy machinesApi
- Add controller vendor/model fields to Equipment model and forms
- Fix map marker navigation to use plugin-specific IDs (equipmentid,
  computerid, printerid, networkdeviceid) instead of assetid
- Fix search to use unified Asset table with correct plugin IDs
- Remove legacy printer search that used non-existent field names
- Enable optional JWT auth for detail endpoints (public read access)
- Clean up USB plugin models (remove unused checkout model)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-01-29 16:07:41 -05:00
parent 9c220a4194
commit c3ce69da12
28 changed files with 4123 additions and 3454 deletions

View File

@@ -156,12 +156,52 @@ class Asset(BaseModel, SoftDeleteMixin, AuditMixin):
comm = self.communications.filter_by(comtypeid=1).first()
return comm.ipaddress if comm else None
def to_dict(self, include_type_data=False):
def get_inherited_location(self):
"""
Get location data from a related asset if this asset has none.
Returns dict with locationid, location_name, mapleft, maptop, and
inherited_from (assetnumber of source asset) if location was inherited.
Returns None if no location data available.
"""
# If we have our own location data, don't inherit
if self.locationid is not None or (self.mapleft is not None and self.maptop is not None):
return None
# Check related assets for location data
# Look in both incoming and outgoing relationships
related_assets = []
if hasattr(self, 'incoming_relationships'):
for rel in self.incoming_relationships:
if rel.source_asset and rel.isactive:
related_assets.append(rel.source_asset)
if hasattr(self, 'outgoing_relationships'):
for rel in self.outgoing_relationships:
if rel.target_asset and rel.isactive:
related_assets.append(rel.target_asset)
# Find first related asset with location data
for related in related_assets:
if related.locationid is not None or (related.mapleft is not None and related.maptop is not None):
return {
'locationid': related.locationid,
'location_name': related.location.locationname if related.location else None,
'mapleft': related.mapleft,
'maptop': related.maptop,
'inherited_from': related.assetnumber
}
return None
def to_dict(self, include_type_data=False, include_inherited_location=True):
"""
Convert model to dictionary.
Args:
include_type_data: If True, include category-specific data from extension table
include_inherited_location: If True, include location from related assets when missing
"""
result = super().to_dict()
@@ -175,6 +215,30 @@ class Asset(BaseModel, SoftDeleteMixin, AuditMixin):
if self.businessunit:
result['businessunit_name'] = self.businessunit.businessunit
# Add plugin-specific ID for navigation purposes
if hasattr(self, 'equipment') and self.equipment:
result['plugin_id'] = self.equipment.equipmentid
elif hasattr(self, 'computer') and self.computer:
result['plugin_id'] = self.computer.computerid
elif hasattr(self, 'network_device') and self.network_device:
result['plugin_id'] = self.network_device.networkdeviceid
elif hasattr(self, 'printer') and self.printer:
result['plugin_id'] = self.printer.printerid
# Include inherited location if this asset has no location data
if include_inherited_location:
inherited = self.get_inherited_location()
if inherited:
result['inherited_location'] = inherited
# Also set the location fields if they're missing
if result.get('locationid') is None:
result['locationid'] = inherited['locationid']
result['location_name'] = inherited['location_name']
if result.get('mapleft') is None:
result['mapleft'] = inherited['mapleft']
if result.get('maptop') is None:
result['maptop'] = inherited['maptop']
# Include extension data if requested
if include_type_data:
ext_data = self._get_extension_data()