diff --git a/app.py b/app.py index b78a078..252954a 100644 --- a/app.py +++ b/app.py @@ -28,7 +28,7 @@ app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(hours=1) # 1. Define the version -APP_VERSION = '0.16.0' # Bumped version for modular architecture +APP_VERSION = '0.17.0' # Bumped version for modular architecture # 2. Inject it into all templates automatically @app.context_processor @@ -236,6 +236,125 @@ def restart_server(): except Exception as e: return jsonify({'success': False, 'message': f'Restart failed: {str(e)}'}) + + +""" +Add this route to app.py +""" + +@app.route('/admin/modules/upload', methods=['POST']) +@login_required +def upload_module(): + """Upload and extract a module package""" + if session.get('role') not in ['owner', 'admin']: + return jsonify({'success': False, 'message': 'Access denied'}), 403 + + if 'module_file' not in request.files: + return jsonify({'success': False, 'message': 'No file uploaded'}) + + file = request.files['module_file'] + + if file.filename == '': + return jsonify({'success': False, 'message': 'No file selected'}) + + if not file.filename.endswith('.zip'): + return jsonify({'success': False, 'message': 'File must be a ZIP archive'}) + + try: + import zipfile + import tempfile + import shutil + from pathlib import Path + import json + + # Create temp directory + with tempfile.TemporaryDirectory() as temp_dir: + # Save uploaded file + zip_path = os.path.join(temp_dir, 'module.zip') + file.save(zip_path) + + # Extract zip + with zipfile.ZipFile(zip_path, 'r') as zip_ref: + zip_ref.extractall(temp_dir) + + # Find the module folder (should contain manifest.json) + module_folder = None + manifest_path = None + + # Check if manifest.json is at root of zip + if os.path.exists(os.path.join(temp_dir, 'manifest.json')): + module_folder = temp_dir + manifest_path = os.path.join(temp_dir, 'manifest.json') + else: + # Look for manifest.json in subdirectories + for item in os.listdir(temp_dir): + item_path = os.path.join(temp_dir, item) + if os.path.isdir(item_path): + potential_manifest = os.path.join(item_path, 'manifest.json') + if os.path.exists(potential_manifest): + module_folder = item_path + manifest_path = potential_manifest + break + + if not manifest_path: + return jsonify({ + 'success': False, + 'message': 'Invalid module package: manifest.json not found' + }) + + # Read and validate manifest + with open(manifest_path, 'r') as f: + manifest = json.load(f) + + required_fields = ['module_key', 'name', 'version', 'author'] + for field in required_fields: + if field not in manifest: + return jsonify({ + 'success': False, + 'message': f'Invalid manifest.json: missing required field "{field}"' + }) + + module_key = manifest['module_key'] + + # Check if module already exists + modules_dir = os.path.join(os.path.dirname(__file__), 'modules') + target_path = os.path.join(modules_dir, module_key) + + if os.path.exists(target_path): + return jsonify({ + 'success': False, + 'message': f'Module "{module_key}" already exists. Please uninstall it first or use a different module key.' + }) + + # Required files check + required_files = ['manifest.json', '__init__.py'] + for req_file in required_files: + if not os.path.exists(os.path.join(module_folder, req_file)): + return jsonify({ + 'success': False, + 'message': f'Invalid module package: {req_file} not found' + }) + + # Copy module to /modules directory + shutil.copytree(module_folder, target_path) + + print(f"✅ Module '{manifest['name']}' uploaded successfully to {target_path}") + + return jsonify({ + 'success': True, + 'message': f"Module '{manifest['name']}' uploaded successfully! Click Install to activate it." + }) + + except zipfile.BadZipFile: + return jsonify({'success': False, 'message': 'Invalid ZIP file'}) + except json.JSONDecodeError: + return jsonify({'success': False, 'message': 'Invalid manifest.json: not valid JSON'}) + except Exception as e: + print(f"❌ Module upload error: {e}") + import traceback + traceback.print_exc() + return jsonify({'success': False, 'message': f'Upload failed: {str(e)}'}) + # ==================== PWA SUPPORT ROUTES ==================== @app.route('/manifest.json') diff --git a/templates/module_manager.html b/templates/module_manager.html index 380d8ef..a37657f 100644 --- a/templates/module_manager.html +++ b/templates/module_manager.html @@ -3,86 +3,240 @@ {% block title %}Module Manager - ScanLook{% endblock %} {% block content %} -
Install, manage, and configure ScanLook modules
+Install, uninstall, and manage ScanLook modules
- -{{ module.description }}
- -
-
- Author: {{ module.author }}
- Module Key: {{ module.module_key }}
-
-
{{ module.description }}
+ + + {% endfor %}/modules directory.
+ {% else %}
+ No modules found in the /modules directory.
Upload a module package to get started.