From cb47b9087e6ce3b13c6d2610a7a027b3afe9d528 Mon Sep 17 00:00:00 2001 From: cproudlock Date: Fri, 26 Jun 2026 09:35:51 -0400 Subject: [PATCH] Move application install tracking off Machine onto Computer - applications.py installed-on + per-computer install/uninstall/update endpoints now use Computer / ComputerInstalledApp instead of Machine / InstalledApp. - ApplicationDetail "Installed On" list reads the computer shape. - Drop the unused Machine/MachineType import from the assets map endpoint. No active core endpoint uses the Machine model anymore (only the legacy /api/machines blueprint and reference-data seeder remain). Co-Authored-By: Claude Opus 4.8 --- .../views/applications/ApplicationDetail.vue | 6 +- shopdb/core/api/applications.py | 77 ++++++++++--------- shopdb/core/api/assets.py | 2 +- 3 files changed, 46 insertions(+), 39 deletions(-) diff --git a/frontend/src/views/applications/ApplicationDetail.vue b/frontend/src/views/applications/ApplicationDetail.vue index ae15a0b..78073da 100644 --- a/frontend/src/views/applications/ApplicationDetail.vue +++ b/frontend/src/views/applications/ApplicationDetail.vue @@ -100,12 +100,12 @@
- {{ install.machine?.machinenumber || `PC #${install.machineid}` }} - {{ install.machine.alias }} + {{ install.computer?.hostname || install.computer?.assetnumber || `PC #${install.computerid}` }} + {{ install.computer.assetnumber }}
v{{ install.version }} diff --git a/shopdb/core/api/applications.py b/shopdb/core/api/applications.py index 93bdaf2..ea22a3b 100644 --- a/shopdb/core/api/applications.py +++ b/shopdb/core/api/applications.py @@ -5,8 +5,9 @@ from flask_jwt_extended import jwt_required from shopdb.extensions import db from shopdb.core.models import ( - Application, AppVersion, AppOwner, SupportTeam, InstalledApp, Machine, AuditLog + Application, AppVersion, AppOwner, SupportTeam, AuditLog ) +from plugins.computers.models import Computer, ComputerInstalledApp from shopdb.utils.responses import ( success_response, error_response, @@ -249,53 +250,61 @@ def create_version(app_id: int): return success_response(version.to_dict(), message='Version created', http_code=201) -# ---- Machines with this app installed ---- +# ---- Computers with this app installed ---- @applications_bp.route('//installed', methods=['GET']) @jwt_required(optional=True) def list_installed_machines(app_id: int): - """List all machines that have this application installed.""" + """List all computers that have this application installed.""" app = Application.query.get(app_id) if not app: return error_response(ErrorCodes.NOT_FOUND, 'Application not found', http_code=404) - installed = app.installed_on.filter_by(isactive=True).all() + installed = ComputerInstalledApp.query.filter_by( + appid=app_id, isactive=True).all() data = [] for i in installed: - item = i.to_dict() - if i.machine: - item['machine'] = { - 'machineid': i.machine.machineid, - 'machinenumber': i.machine.machinenumber, - 'alias': i.machine.alias, - 'hostname': i.machine.hostname + comp = i.computer + version = i.installedversion + if not version and i.appversion: + version = i.appversion.version + item = { + 'id': i.id, + 'computerid': i.computerid, + 'version': version, + } + if comp: + item['computer'] = { + 'computerid': comp.computerid, + 'assetnumber': comp.asset.assetnumber if comp.asset else None, + 'hostname': comp.hostname, } data.append(item) return success_response(data) -# ---- Installed Apps (per machine) ---- +# ---- Installed Apps (per computer) ---- @applications_bp.route('/machines/', methods=['GET']) @jwt_required(optional=True) def list_machine_applications(machine_id: int): - """List all applications installed on a machine.""" - machine = Machine.query.get(machine_id) - if not machine: - return error_response(ErrorCodes.NOT_FOUND, 'Machine not found', http_code=404) + """List all applications installed on a computer.""" + comp = Computer.query.get(machine_id) + if not comp: + return error_response(ErrorCodes.NOT_FOUND, 'Computer not found', http_code=404) - installed = machine.installedapps.filter_by(isactive=True).all() + installed = comp.installedapps.filter_by(isactive=True).all() return success_response([i.to_dict() for i in installed]) @applications_bp.route('/machines/', methods=['POST']) @jwt_required() def install_application(machine_id: int): - """Install an application on a machine.""" - machine = Machine.query.get(machine_id) - if not machine: - return error_response(ErrorCodes.NOT_FOUND, 'Machine not found', http_code=404) + """Install an application on a computer.""" + comp = Computer.query.get(machine_id) + if not comp: + return error_response(ErrorCodes.NOT_FOUND, 'Computer not found', http_code=404) data = request.get_json() if not data or not data.get('appid'): @@ -305,9 +314,8 @@ def install_application(machine_id: int): if not app: return error_response(ErrorCodes.NOT_FOUND, 'Application not found', http_code=404) - # Check if already installed - existing = InstalledApp.query.filter_by( - machineid=machine_id, + existing = ComputerInstalledApp.query.filter_by( + computerid=machine_id, appid=data['appid'] ).first() @@ -315,18 +323,17 @@ def install_application(machine_id: int): if existing.isactive: return error_response( ErrorCodes.CONFLICT, - 'Application already installed on this machine', + 'Application already installed on this computer', http_code=409 ) - # Reactivate existing.isactive = True existing.appversionid = data.get('appversionid') existing.installeddate = db.func.now() db.session.commit() return success_response(existing.to_dict(), message='Application reinstalled') - installed = InstalledApp( - machineid=machine_id, + installed = ComputerInstalledApp( + computerid=machine_id, appid=data['appid'], appversionid=data.get('appversionid') ) @@ -340,15 +347,15 @@ def install_application(machine_id: int): @applications_bp.route('/machines//', methods=['DELETE']) @jwt_required() def uninstall_application(machine_id: int, app_id: int): - """Uninstall an application from a machine.""" - installed = InstalledApp.query.filter_by( - machineid=machine_id, + """Uninstall an application from a computer.""" + installed = ComputerInstalledApp.query.filter_by( + computerid=machine_id, appid=app_id, isactive=True ).first() if not installed: - return error_response(ErrorCodes.NOT_FOUND, 'Application not installed on this machine', http_code=404) + return error_response(ErrorCodes.NOT_FOUND, 'Application not installed on this computer', http_code=404) installed.isactive = False db.session.commit() @@ -360,14 +367,14 @@ def uninstall_application(machine_id: int, app_id: int): @jwt_required() def update_installed_app(machine_id: int, app_id: int): """Update installed application (e.g., change version).""" - installed = InstalledApp.query.filter_by( - machineid=machine_id, + installed = ComputerInstalledApp.query.filter_by( + computerid=machine_id, appid=app_id, isactive=True ).first() if not installed: - return error_response(ErrorCodes.NOT_FOUND, 'Application not installed on this machine', http_code=404) + return error_response(ErrorCodes.NOT_FOUND, 'Application not installed on this computer', http_code=404) data = request.get_json() if not data: diff --git a/shopdb/core/api/assets.py b/shopdb/core/api/assets.py index 08bd256..7455c33 100644 --- a/shopdb/core/api/assets.py +++ b/shopdb/core/api/assets.py @@ -632,7 +632,7 @@ def get_assets_map(): - locationid: Filter by location ID - search: Search by assetnumber, name, or serialnumber """ - from shopdb.core.models import Location, BusinessUnit, MachineType, Machine, Communication + from shopdb.core.models import Location, BusinessUnit, Communication # Eager-load all relationships to avoid N+1 queries. # Core relationships via joinedload, extension tables via subqueryload