Add USB, Notifications, Network plugins and reusable EmployeeSearch component

New Plugins:
- USB plugin: Device checkout/checkin with employee lookup, checkout history
- Notifications plugin: Announcements with types, scheduling, shopfloor display
- Network plugin: Network device management with subnets and VLANs
- Equipment and Computers plugins: Asset type separation

Frontend:
- EmployeeSearch component: Reusable employee lookup with autocomplete
- USB views: List, detail, checkout/checkin modals
- Notifications views: List, form with recognition mode
- Network views: Device list, detail, form
- Calendar view with FullCalendar integration
- Shopfloor and TV dashboard views
- Reports index page
- Map editor for asset positioning
- Light/dark mode fixes for map tooltips

Backend:
- Employee search API with external lookup service
- Collector API for PowerShell data collection
- Reports API endpoints
- Slides API for TV dashboard
- Fixed AppVersion model (removed BaseModel inheritance)
- Added checkout_name column to usbcheckouts table

Styling:
- Unified detail page styles
- Improved pagination (page numbers instead of prev/next)
- Dark/light mode theme improvements

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
cproudlock
2026-01-21 16:37:49 -05:00
parent 02d83335ee
commit 9c220a4194
110 changed files with 17693 additions and 600 deletions

View File

@@ -1,6 +1,18 @@
"""Machines API endpoints."""
"""
Machines API endpoints.
from flask import Blueprint, request
DEPRECATED: This API is deprecated and will be removed in a future version.
Please migrate to the new asset-based APIs:
- /api/assets - Unified asset queries
- /api/equipment - Equipment CRUD
- /api/computers - Computers CRUD
- /api/network - Network devices CRUD
- /api/printers - Printers CRUD
"""
import logging
from functools import wraps
from flask import Blueprint, request, g
from flask_jwt_extended import jwt_required, current_user
from shopdb.extensions import db
@@ -14,11 +26,40 @@ from shopdb.utils.responses import (
)
from shopdb.utils.pagination import get_pagination_params, paginate_query
logger = logging.getLogger(__name__)
machines_bp = Blueprint('machines', __name__)
def add_deprecation_headers(f):
"""Decorator to add deprecation headers to responses."""
@wraps(f)
def decorated_function(*args, **kwargs):
response = f(*args, **kwargs)
# Add deprecation headers
if hasattr(response, 'headers'):
response.headers['X-Deprecated'] = 'true'
response.headers['X-Deprecated-Message'] = (
'This endpoint is deprecated. '
'Please migrate to /api/assets, /api/equipment, /api/computers, /api/network, or /api/printers.'
)
response.headers['Sunset'] = '2026-12-31' # Target sunset date
# Log deprecation warning (once per request)
if not getattr(g, '_deprecation_logged', False):
logger.warning(
f"Deprecated /api/machines endpoint called: {request.method} {request.path}"
)
g._deprecation_logged = True
return response
return decorated_function
@machines_bp.route('', methods=['GET'])
@jwt_required(optional=True)
@add_deprecation_headers
def list_machines():
"""
List all machines with filtering and pagination.
@@ -149,6 +190,7 @@ def list_machines():
@machines_bp.route('/<int:machine_id>', methods=['GET'])
@jwt_required(optional=True)
@add_deprecation_headers
def get_machine(machine_id: int):
"""Get a single machine by ID."""
machine = Machine.query.get(machine_id)
@@ -180,6 +222,7 @@ def get_machine(machine_id: int):
@machines_bp.route('', methods=['POST'])
@jwt_required()
@add_deprecation_headers
def create_machine():
"""Create a new machine."""
data = request.get_json()
@@ -227,6 +270,7 @@ def create_machine():
@machines_bp.route('/<int:machine_id>', methods=['PUT'])
@jwt_required()
@add_deprecation_headers
def update_machine(machine_id: int):
"""Update an existing machine."""
machine = Machine.query.get(machine_id)
@@ -274,6 +318,7 @@ def update_machine(machine_id: int):
@machines_bp.route('/<int:machine_id>', methods=['DELETE'])
@jwt_required()
@add_deprecation_headers
def delete_machine(machine_id: int):
"""Soft delete a machine."""
machine = Machine.query.get(machine_id)
@@ -293,6 +338,7 @@ def delete_machine(machine_id: int):
@machines_bp.route('/<int:machine_id>/communications', methods=['GET'])
@jwt_required()
@add_deprecation_headers
def get_machine_communications(machine_id: int):
"""Get all communications for a machine."""
machine = Machine.query.get(machine_id)
@@ -310,6 +356,7 @@ def get_machine_communications(machine_id: int):
@machines_bp.route('/<int:machine_id>/communication', methods=['PUT'])
@jwt_required()
@add_deprecation_headers
def update_machine_communication(machine_id: int):
"""Update machine communication (IP address)."""
from shopdb.core.models.communication import Communication, CommunicationType
@@ -364,6 +411,7 @@ def update_machine_communication(machine_id: int):
@machines_bp.route('/<int:machine_id>/relationships', methods=['GET'])
@jwt_required(optional=True)
@add_deprecation_headers
def get_machine_relationships(machine_id: int):
"""Get all relationships for a machine (both parent and child)."""
machine = Machine.query.get(machine_id)
@@ -429,6 +477,7 @@ def get_machine_relationships(machine_id: int):
@machines_bp.route('/<int:machine_id>/relationships', methods=['POST'])
@jwt_required()
@add_deprecation_headers
def create_machine_relationship(machine_id: int):
"""Create a relationship for a machine."""
machine = Machine.query.get(machine_id)
@@ -504,6 +553,7 @@ def create_machine_relationship(machine_id: int):
@machines_bp.route('/relationships/<int:relationship_id>', methods=['DELETE'])
@jwt_required()
@add_deprecation_headers
def delete_machine_relationship(relationship_id: int):
"""Delete a machine relationship."""
relationship = MachineRelationship.query.get(relationship_id)
@@ -523,6 +573,7 @@ def delete_machine_relationship(relationship_id: int):
@machines_bp.route('/relationshiptypes', methods=['GET'])
@jwt_required(optional=True)
@add_deprecation_headers
def list_relationship_types():
"""List all relationship types."""
types = RelationshipType.query.order_by(RelationshipType.relationshiptype).all()
@@ -535,6 +586,7 @@ def list_relationship_types():
@machines_bp.route('/relationshiptypes', methods=['POST'])
@jwt_required()
@add_deprecation_headers
def create_relationship_type():
"""Create a new relationship type."""
data = request.get_json()