diff --git a/py_app/app/__init__.py b/py_app/app/__init__.py index 2419b3c..6f13c7e 100755 --- a/py_app/app/__init__.py +++ b/py_app/app/__init__.py @@ -20,6 +20,55 @@ def create_app(): # Add 'now' function to Jinja2 globals app.jinja_env.globals['now'] = datetime.now + # Add license check middleware + @app.before_request + def check_license_middleware(): + from flask import session, request, redirect, url_for, flash, render_template + import os + import json + from datetime import datetime + + # Skip license check for static files, login page, and superadmin users + if request.endpoint and ( + request.endpoint == 'static' or + request.endpoint == 'main.login' or + request.path.startswith('/static/') + ): + return None + + # Skip if user is not logged in (will be redirected to login by other means) + if 'user' not in session: + return None + + # Skip license check for superadmin + if session.get('role') == 'superadmin': + return None + + # Check license validity + license_path = os.path.join(app.instance_path, 'app_license.json') + + if not os.path.exists(license_path): + session.clear() + flash('⚠️ Application License Missing - Please contact your superadmin to generate a license key.', 'danger') + return redirect(url_for('main.login')) + + try: + with open(license_path, 'r') as f: + license_data = json.load(f) + + valid_until = datetime.strptime(license_data['valid_until'], '%Y-%m-%d') + + if datetime.utcnow().date() > valid_until.date(): + session.clear() + flash(f'⚠️ Application License Expired on {license_data["valid_until"]} - Please contact your superadmin to renew the license.', 'danger') + return redirect(url_for('main.login')) + except Exception as e: + session.clear() + flash('⚠️ License Validation Error - Please contact your superadmin.', 'danger') + return redirect(url_for('main.login')) + + return None + # Initialize automatic backup scheduler from app.backup_scheduler import init_backup_scheduler init_backup_scheduler(app) diff --git a/py_app/app/permissions_simple.py b/py_app/app/permissions_simple.py index 9664b2a..d300f3d 100644 --- a/py_app/app/permissions_simple.py +++ b/py_app/app/permissions_simple.py @@ -23,6 +23,12 @@ MODULES = { 'scan_pages': ['move_orders'], 'management_pages': ['create_locations', 'warehouse_reports', 'inventory_management'], 'worker_access': ['move_orders_only'] # Workers can move orders but not create locations + }, + 'daily_mirror': { + 'name': 'Daily Mirror (BI & Reports)', + 'scan_pages': [], + 'management_pages': ['daily_mirror', 'build_database', 'view_production_data'], + 'worker_access': [] # Workers typically don't need access to BI } } diff --git a/py_app/app/routes.py b/py_app/app/routes.py index 1fce075..2491c93 100755 --- a/py_app/app/routes.py +++ b/py_app/app/routes.py @@ -29,6 +29,28 @@ from .access_control import ( bp = Blueprint('main', __name__) warehouse_bp = Blueprint('warehouse', __name__) +def check_app_license(): + """Check if the application license is valid.""" + license_path = os.path.join(current_app.instance_path, 'app_license.json') + + # If no license file exists, return invalid + if not os.path.exists(license_path): + return False, '⚠️ Application License Expired\n\nThis application requires a valid license to operate.\nPlease contact your superadmin to renew the license.' + + try: + with open(license_path, 'r') as f: + license_data = json.load(f) + + valid_until = datetime.strptime(license_data['valid_until'], '%Y-%m-%d') + + # Check if license is still valid + if datetime.utcnow().date() > valid_until.date(): + return False, f'⚠️ Application License Expired\n\nThe license expired on {license_data["valid_until"]}.\nPlease contact your superadmin to renew the license.' + + return True, 'License valid' + except Exception as e: + return False, f'⚠️ License Validation Error\n\nCould not validate application license.\nPlease contact your superadmin.' + @bp.route('/main_scan') @requires_quality_module def main_scan(): @@ -97,6 +119,15 @@ def login(): user_modules = ['quality', 'warehouse', 'labels', 'daily_mirror'] session['modules'] = user_modules + + # Check app license for non-superadmin users + if user['role'] != 'superadmin': + license_valid, license_message = check_app_license() + if not license_valid: + session.clear() + flash(license_message, 'danger') + return render_template('login.html') + print("Logged in as:", session.get('user'), session.get('role'), "modules:", user_modules) return redirect(url_for('main.dashboard')) else: @@ -191,7 +222,16 @@ def dashboard(): print("Session user:", session.get('user'), session.get('role')) if 'user' not in session: return redirect(url_for('main.login')) - return render_template('dashboard.html') + + # Get user's modules and role + user_role = session.get('role') + user_modules = session.get('modules', []) + + # Superadmin and admin see all modules + if user_role in ['superadmin', 'admin']: + user_modules = ['quality', 'warehouse', 'labels', 'daily_mirror'] + + return render_template('dashboard.html', user_modules=user_modules, user_role=user_role) @bp.route('/settings') @admin_plus @@ -402,7 +442,7 @@ def quick_update_modules(): # Get current user to validate role conn = get_db_connection() cursor = conn.cursor() - cursor.execute("SELECT username, role FROM users WHERE id=%s", (user_id,)) + cursor.execute("SELECT username, role, modules FROM users WHERE id=%s", (user_id,)) user_row = cursor.fetchone() if not user_row: @@ -410,11 +450,12 @@ def quick_update_modules(): conn.close() return redirect(url_for('main.user_management_simple')) - username, role = user_row + username, role, current_modules = user_row # Validate modules for the role from app.permissions_simple import validate_user_modules is_valid, error_msg = validate_user_modules(role, modules) + if not is_valid: flash(f'Invalid module assignment: {error_msg}') conn.close() @@ -425,18 +466,25 @@ def quick_update_modules(): if modules and role in ['manager', 'worker']: import json modules_json = json.dumps(modules) + elif not modules and role in ['manager', 'worker']: + # Empty modules list for manager/worker + import json + modules_json = json.dumps([]) # Update modules only cursor.execute("UPDATE users SET modules=%s WHERE id=%s", (modules_json, user_id)) conn.commit() + conn.close() - flash(f'Modules updated successfully for user "{username}".') + flash(f'Modules updated successfully for user "{username}". New modules: {", ".join(modules) if modules else "None"}', 'success') return redirect(url_for('main.user_management_simple')) except Exception as e: - print(f"Error updating modules: {e}") - flash('Error updating modules.') + print(f"ERROR updating modules: {e}") + import traceback + traceback.print_exc() + flash(f'Error updating modules: {str(e)}', 'danger') return redirect(url_for('main.user_management_simple')) @bp.route('/reports') @@ -2398,7 +2446,64 @@ def download_extension(): keys = [] except Exception as e: keys = [] - return render_template('download_extension.html', pairing_keys=keys) + + # Load app license key + license_path = os.path.join(current_app.instance_path, 'app_license.json') + license_data = None + try: + if os.path.exists(license_path): + with open(license_path, 'r') as f: + license_data = json.load(f) + + # Calculate days remaining + valid_until = datetime.strptime(license_data['valid_until'], '%Y-%m-%d') + days_remaining = (valid_until.date() - datetime.utcnow().date()).days + license_data['days_remaining'] = days_remaining + except Exception as e: + license_data = None + + return render_template('download_extension.html', pairing_keys=keys, license_data=license_data) + +@bp.route('/generate_app_license', methods=['POST']) +@superadmin_only +def generate_app_license(): + """Generate a license key for the application.""" + validity_days = int(request.form.get('validity_days', 365)) # Default to 365 days + + # Generate a secure license key + license_key = secrets.token_urlsafe(48) + valid_until = (datetime.utcnow() + timedelta(days=validity_days)).strftime('%Y-%m-%d') + created_at = datetime.utcnow().strftime('%Y-%m-%d %H:%M:%S') + + # Create license data + license_data = { + 'license_key': license_key, + 'valid_until': valid_until, + 'created_at': created_at, + 'validity_days': validity_days + } + + # Save license key + license_path = os.path.join(current_app.instance_path, 'app_license.json') + with open(license_path, 'w') as f: + json.dump(license_data, f, indent=2) + + flash(f'App license generated successfully (valid for {validity_days} days until {valid_until}).', 'success') + return redirect(url_for('main.download_extension')) + +@bp.route('/revoke_app_license', methods=['POST']) +@superadmin_only +def revoke_app_license(): + """Revoke the application license.""" + license_path = os.path.join(current_app.instance_path, 'app_license.json') + + if os.path.exists(license_path): + os.remove(license_path) + flash('App license revoked successfully.', 'success') + else: + flash('No license key found to revoke.', 'warning') + + return redirect(url_for('main.download_extension')) @bp.route('/extension_files/') def extension_files(filename): diff --git a/py_app/app/templates/create_locations.html b/py_app/app/templates/create_locations.html index 525a1a1..9e23c72 100755 --- a/py_app/app/templates/create_locations.html +++ b/py_app/app/templates/create_locations.html @@ -702,35 +702,6 @@ function confirmDelete() { }); } -// Handle delete confirmation -function confirmDelete() { - const locationId = document.getElementById('delete-confirm-id').textContent; - - // Send delete request - fetch('/delete_location', { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ location_id: locationId }) - }) - .then(response => response.json()) - .then(result => { - if (result.success) { - showNotification('✅ Location deleted successfully!', 'success'); - closeDeleteModal(); - // Reload page to show changes - setTimeout(() => window.location.reload(), 1000); - } else { - showNotification('❌ Error deleting location: ' + result.error, 'error'); - } - }) - .catch(error => { - console.error('Error:', error); - showNotification('❌ Error deleting location: ' + error.message, 'error'); - }); -} - // Close modals when clicking outside window.addEventListener('click', function(event) { const editModal = document.getElementById('edit-modal'); diff --git a/py_app/app/templates/dashboard.html b/py_app/app/templates/dashboard.html index 8bf89dd..aad091e 100755 --- a/py_app/app/templates/dashboard.html +++ b/py_app/app/templates/dashboard.html @@ -12,6 +12,7 @@
+ {% if 'quality' in user_modules %}

Quality Module

Final scanning module for production orders and quality reports access.

@@ -20,26 +21,34 @@ Access to Quality reports
+ {% endif %} - + {% if 'warehouse' in user_modules %}

Access Warehouse Module

Access warehouse module functionalities.

Open Warehouse
+ {% endif %} + + {% if 'labels' in user_modules %}

Access Labels Module

Module for label management.

Launch Labels Module
+ {% endif %} + {% if user_role in ['superadmin', 'admin'] %}

Manage Settings

Access and manage application settings.

Access Settings Page
+ {% endif %} + {% if 'daily_mirror' in user_modules %}

📊 Daily Mirror

Business Intelligence and Production Reporting - Generate comprehensive daily reports including order quantities, production status, and delivery tracking.

@@ -50,6 +59,7 @@ Tracks: Orders quantity • Production launched • Production finished • Orders delivered
+ {% endif %} diff --git a/py_app/app/templates/download_extension.html b/py_app/app/templates/download_extension.html index ace5fa8..ecf5991 100644 --- a/py_app/app/templates/download_extension.html +++ b/py_app/app/templates/download_extension.html @@ -304,6 +304,81 @@ +
+

🔐 Application License Management

+ + {% if license_data %} +
+
+ 🔑 License Key: {{ license_data.license_key }} +
+
+ 📅 Created: {{ license_data.created_at }} +
+
+ ⏰ Valid Until: {{ license_data.valid_until }} + {% if license_data.days_remaining is defined %} + {% if license_data.days_remaining > 0 %} + + {{ license_data.days_remaining }} days remaining + + {% else %} + + ⚠️ EXPIRED + + {% endif %} + {% endif %} +
+
+
+ +
+
+
+ {% else %} +
+ ⚠️ No Active License +

+ No application license has been generated. Non-superadmin users will not be able to access the application. +

+
+ {% endif %} + +

🔑 Generate New License

+
+
+
+ + +
+ + +
+
+ +
+ ℹ️ License Information +
    +
  • The application license controls access for all non-superadmin users
  • +
  • Superadmin accounts can always access the application regardless of license status
  • +
  • When the license expires, non-superadmin users will see an expiration message
  • +
  • Generating a new license will replace any existing license
  • +
+
+
+