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

@@ -0,0 +1,139 @@
"""
Migration orchestrator - runs all migration steps in order.
This script coordinates the full migration from VBScript ShopDB to Flask.
Usage:
python -m scripts.migration.run_migration --source <connection_string>
"""
import argparse
import logging
import sys
from datetime import datetime
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)
def run_full_migration(source_conn_str, target_conn_str, dry_run=False, steps=None):
"""
Run the full migration process.
Args:
source_conn_str: Source database connection string
target_conn_str: Target database connection string
dry_run: If True, don't commit changes
steps: List of specific steps to run, or None for all
"""
from . import migrate_assets
from . import migrate_communications
from . import migrate_notifications
from . import migrate_usb
from . import verify_migration
all_steps = [
('assets', 'Migrate machines to assets', migrate_assets.run_migration),
('communications', 'Update communications FKs', migrate_communications.run_migration),
('notifications', 'Migrate notifications', migrate_notifications.run_migration),
('usb', 'Migrate USB devices', migrate_usb.run_migration),
]
logger.info("=" * 60)
logger.info("SHOPDB MIGRATION")
logger.info(f"Started: {datetime.utcnow().isoformat()}")
logger.info(f"Dry Run: {dry_run}")
logger.info("=" * 60)
results = {}
for step_name, description, migration_func in all_steps:
if steps and step_name not in steps:
logger.info(f"\nSkipping: {description}")
continue
logger.info(f"\n{'=' * 40}")
logger.info(f"Step: {description}")
logger.info('=' * 40)
try:
# Different migrations have different signatures
if step_name == 'communications':
migration_func(target_conn_str, dry_run)
else:
migration_func(source_conn_str, target_conn_str, dry_run)
results[step_name] = 'SUCCESS'
logger.info(f"Step completed: {step_name}")
except Exception as e:
results[step_name] = f'FAILED: {e}'
logger.error(f"Step failed: {step_name} - {e}")
# Ask to continue
if not dry_run:
response = input("Continue with next step? (y/n): ")
if response.lower() != 'y':
logger.info("Migration aborted by user")
break
# Run verification
logger.info(f"\n{'=' * 40}")
logger.info("Running verification...")
logger.info('=' * 40)
try:
verify_migration.run_verification(source_conn_str, target_conn_str)
results['verification'] = 'SUCCESS'
except Exception as e:
results['verification'] = f'FAILED: {e}'
logger.error(f"Verification failed: {e}")
# Summary
logger.info("\n" + "=" * 60)
logger.info("MIGRATION SUMMARY")
logger.info("=" * 60)
for step, result in results.items():
status = "OK" if result == 'SUCCESS' else "FAILED"
logger.info(f" {step}: {status}")
if result != 'SUCCESS':
logger.info(f" {result}")
logger.info("=" * 60)
logger.info(f"Completed: {datetime.utcnow().isoformat()}")
return results
def main():
parser = argparse.ArgumentParser(description='Run full ShopDB migration')
parser.add_argument('--source', required=True, help='Source database connection string')
parser.add_argument('--target', help='Target database connection string')
parser.add_argument('--dry-run', action='store_true', help='Dry run without committing')
parser.add_argument('--steps', nargs='+',
choices=['assets', 'communications', 'notifications', 'usb'],
help='Specific steps to run')
args = parser.parse_args()
target = args.target
if not target:
import os
sys.path.insert(0, os.path.dirname(os.path.dirname(os.path.dirname(__file__))))
from shopdb import create_app
app = create_app()
target = app.config['SQLALCHEMY_DATABASE_URI']
results = run_full_migration(args.source, target, args.dry_run, args.steps)
# Exit with error if any step failed
if any(r != 'SUCCESS' for r in results.values()):
sys.exit(1)
if __name__ == '__main__':
main()