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 <noreply@anthropic.com>
This commit is contained in:
@@ -100,12 +100,12 @@
|
|||||||
<router-link
|
<router-link
|
||||||
v-for="install in installedOn"
|
v-for="install in installedOn"
|
||||||
:key="install.id"
|
:key="install.id"
|
||||||
:to="`/pcs/${install.machineid}`"
|
:to="`/pcs/${install.computerid}`"
|
||||||
class="pc-item"
|
class="pc-item"
|
||||||
>
|
>
|
||||||
<div class="pc-info">
|
<div class="pc-info">
|
||||||
<span class="pc-name">{{ install.machine?.machinenumber || `PC #${install.machineid}` }}</span>
|
<span class="pc-name">{{ install.computer?.hostname || install.computer?.assetnumber || `PC #${install.computerid}` }}</span>
|
||||||
<span class="pc-alias" v-if="install.machine?.alias">{{ install.machine.alias }}</span>
|
<span class="pc-alias" v-if="install.computer?.assetnumber">{{ install.computer.assetnumber }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="pc-version" v-if="install.version">
|
<div class="pc-version" v-if="install.version">
|
||||||
v{{ install.version }}
|
v{{ install.version }}
|
||||||
|
|||||||
@@ -5,8 +5,9 @@ from flask_jwt_extended import jwt_required
|
|||||||
|
|
||||||
from shopdb.extensions import db
|
from shopdb.extensions import db
|
||||||
from shopdb.core.models import (
|
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 (
|
from shopdb.utils.responses import (
|
||||||
success_response,
|
success_response,
|
||||||
error_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)
|
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('/<int:app_id>/installed', methods=['GET'])
|
@applications_bp.route('/<int:app_id>/installed', methods=['GET'])
|
||||||
@jwt_required(optional=True)
|
@jwt_required(optional=True)
|
||||||
def list_installed_machines(app_id: int):
|
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)
|
app = Application.query.get(app_id)
|
||||||
if not app:
|
if not app:
|
||||||
return error_response(ErrorCodes.NOT_FOUND, 'Application not found', http_code=404)
|
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 = []
|
data = []
|
||||||
for i in installed:
|
for i in installed:
|
||||||
item = i.to_dict()
|
comp = i.computer
|
||||||
if i.machine:
|
version = i.installedversion
|
||||||
item['machine'] = {
|
if not version and i.appversion:
|
||||||
'machineid': i.machine.machineid,
|
version = i.appversion.version
|
||||||
'machinenumber': i.machine.machinenumber,
|
item = {
|
||||||
'alias': i.machine.alias,
|
'id': i.id,
|
||||||
'hostname': i.machine.hostname
|
'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)
|
data.append(item)
|
||||||
|
|
||||||
return success_response(data)
|
return success_response(data)
|
||||||
|
|
||||||
|
|
||||||
# ---- Installed Apps (per machine) ----
|
# ---- Installed Apps (per computer) ----
|
||||||
|
|
||||||
@applications_bp.route('/machines/<int:machine_id>', methods=['GET'])
|
@applications_bp.route('/machines/<int:machine_id>', methods=['GET'])
|
||||||
@jwt_required(optional=True)
|
@jwt_required(optional=True)
|
||||||
def list_machine_applications(machine_id: int):
|
def list_machine_applications(machine_id: int):
|
||||||
"""List all applications installed on a machine."""
|
"""List all applications installed on a computer."""
|
||||||
machine = Machine.query.get(machine_id)
|
comp = Computer.query.get(machine_id)
|
||||||
if not machine:
|
if not comp:
|
||||||
return error_response(ErrorCodes.NOT_FOUND, 'Machine not found', http_code=404)
|
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])
|
return success_response([i.to_dict() for i in installed])
|
||||||
|
|
||||||
|
|
||||||
@applications_bp.route('/machines/<int:machine_id>', methods=['POST'])
|
@applications_bp.route('/machines/<int:machine_id>', methods=['POST'])
|
||||||
@jwt_required()
|
@jwt_required()
|
||||||
def install_application(machine_id: int):
|
def install_application(machine_id: int):
|
||||||
"""Install an application on a machine."""
|
"""Install an application on a computer."""
|
||||||
machine = Machine.query.get(machine_id)
|
comp = Computer.query.get(machine_id)
|
||||||
if not machine:
|
if not comp:
|
||||||
return error_response(ErrorCodes.NOT_FOUND, 'Machine not found', http_code=404)
|
return error_response(ErrorCodes.NOT_FOUND, 'Computer not found', http_code=404)
|
||||||
|
|
||||||
data = request.get_json()
|
data = request.get_json()
|
||||||
if not data or not data.get('appid'):
|
if not data or not data.get('appid'):
|
||||||
@@ -305,9 +314,8 @@ def install_application(machine_id: int):
|
|||||||
if not app:
|
if not app:
|
||||||
return error_response(ErrorCodes.NOT_FOUND, 'Application not found', http_code=404)
|
return error_response(ErrorCodes.NOT_FOUND, 'Application not found', http_code=404)
|
||||||
|
|
||||||
# Check if already installed
|
existing = ComputerInstalledApp.query.filter_by(
|
||||||
existing = InstalledApp.query.filter_by(
|
computerid=machine_id,
|
||||||
machineid=machine_id,
|
|
||||||
appid=data['appid']
|
appid=data['appid']
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
@@ -315,18 +323,17 @@ def install_application(machine_id: int):
|
|||||||
if existing.isactive:
|
if existing.isactive:
|
||||||
return error_response(
|
return error_response(
|
||||||
ErrorCodes.CONFLICT,
|
ErrorCodes.CONFLICT,
|
||||||
'Application already installed on this machine',
|
'Application already installed on this computer',
|
||||||
http_code=409
|
http_code=409
|
||||||
)
|
)
|
||||||
# Reactivate
|
|
||||||
existing.isactive = True
|
existing.isactive = True
|
||||||
existing.appversionid = data.get('appversionid')
|
existing.appversionid = data.get('appversionid')
|
||||||
existing.installeddate = db.func.now()
|
existing.installeddate = db.func.now()
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
return success_response(existing.to_dict(), message='Application reinstalled')
|
return success_response(existing.to_dict(), message='Application reinstalled')
|
||||||
|
|
||||||
installed = InstalledApp(
|
installed = ComputerInstalledApp(
|
||||||
machineid=machine_id,
|
computerid=machine_id,
|
||||||
appid=data['appid'],
|
appid=data['appid'],
|
||||||
appversionid=data.get('appversionid')
|
appversionid=data.get('appversionid')
|
||||||
)
|
)
|
||||||
@@ -340,15 +347,15 @@ def install_application(machine_id: int):
|
|||||||
@applications_bp.route('/machines/<int:machine_id>/<int:app_id>', methods=['DELETE'])
|
@applications_bp.route('/machines/<int:machine_id>/<int:app_id>', methods=['DELETE'])
|
||||||
@jwt_required()
|
@jwt_required()
|
||||||
def uninstall_application(machine_id: int, app_id: int):
|
def uninstall_application(machine_id: int, app_id: int):
|
||||||
"""Uninstall an application from a machine."""
|
"""Uninstall an application from a computer."""
|
||||||
installed = InstalledApp.query.filter_by(
|
installed = ComputerInstalledApp.query.filter_by(
|
||||||
machineid=machine_id,
|
computerid=machine_id,
|
||||||
appid=app_id,
|
appid=app_id,
|
||||||
isactive=True
|
isactive=True
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if not installed:
|
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
|
installed.isactive = False
|
||||||
db.session.commit()
|
db.session.commit()
|
||||||
@@ -360,14 +367,14 @@ def uninstall_application(machine_id: int, app_id: int):
|
|||||||
@jwt_required()
|
@jwt_required()
|
||||||
def update_installed_app(machine_id: int, app_id: int):
|
def update_installed_app(machine_id: int, app_id: int):
|
||||||
"""Update installed application (e.g., change version)."""
|
"""Update installed application (e.g., change version)."""
|
||||||
installed = InstalledApp.query.filter_by(
|
installed = ComputerInstalledApp.query.filter_by(
|
||||||
machineid=machine_id,
|
computerid=machine_id,
|
||||||
appid=app_id,
|
appid=app_id,
|
||||||
isactive=True
|
isactive=True
|
||||||
).first()
|
).first()
|
||||||
|
|
||||||
if not installed:
|
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()
|
data = request.get_json()
|
||||||
if not data:
|
if not data:
|
||||||
|
|||||||
@@ -632,7 +632,7 @@ def get_assets_map():
|
|||||||
- locationid: Filter by location ID
|
- locationid: Filter by location ID
|
||||||
- search: Search by assetnumber, name, or serialnumber
|
- 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.
|
# Eager-load all relationships to avoid N+1 queries.
|
||||||
# Core relationships via joinedload, extension tables via subqueryload
|
# Core relationships via joinedload, extension tables via subqueryload
|
||||||
|
|||||||
Reference in New Issue
Block a user