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:
139
scripts/migration/run_migration.py
Normal file
139
scripts/migration/run_migration.py
Normal 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()
|
||||
Reference in New Issue
Block a user