import os import sys import logging import platform import time import threading from datetime import datetime from flask import Flask, render_template_string, jsonify import markdown # Configure logging logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) app = Flask(__name__) # Cache storage: filepath -> (mtime, html_content) _content_cache = {} # Thread-local storage for Markdown instances to avoid re-initialization overhead _md_local = threading.local() # Constants for paths to avoid re-calculating on every request BASE_DIR = os.path.dirname(os.path.abspath(__file__)) REPO_ROOT = os.path.abspath(os.path.join(BASE_DIR, '..')) README_PATH = os.path.join(REPO_ROOT, 'README.md') VERIFICATION_PATH = os.path.join(REPO_ROOT, 'VERIFICATION.md') VERSION_PATH = os.path.join(REPO_ROOT, 'VERSION') def get_version(): """Read version from VERSION file.""" try: if os.path.exists(VERSION_PATH): with open(VERSION_PATH, 'r') as f: return f.read().strip() except Exception: pass return "1.0.0" APP_VERSION = get_version() # HTML Template DASHBOARD_HTML = """ GenX FX | Trading Automation Dashboard

GenX FX Trading Automation

v{{ version }}

System Status ONLINE

  • Platform {{ platform }}
  • Uptime Loading...
  • Fly.io App mql5-automation
  • Telegram Bot @GenX_FX_bot

Resources

  • Python Version {{ python_version }}
  • Last Refresh {{ last_refresh }}

API Endpoints

Verification Status

{{ html_verification|safe }}

Documentation

{{ html_readme|safe }}
""" # Global to store compiled template DASHBOARD_TEMPLATE = None def get_cached_markdown(filepath): """ Returns the markdown content of a file converted to HTML, using a cache that invalidates based on file modification time. """ try: stat_result = os.stat(filepath) except OSError: return None try: mtime = stat_result.st_mtime if filepath in _content_cache: cached_mtime, cached_html = _content_cache[filepath] if cached_mtime == mtime: return cached_html with open(filepath, 'r', encoding='utf-8') as f: content = f.read() if not hasattr(_md_local, 'md'): _md_local.md = markdown.Markdown(extensions=['tables', 'fenced_code']) _md_local.md.reset() html_content = _md_local.md.convert(content) _content_cache[filepath] = (mtime, html_content) return html_content except Exception as e: logger.error(f"Error reading/converting {filepath}: {e}") return None @app.route('/health') def health_check(): """Lightweight health check for load balancers.""" return jsonify({ "status": "healthy", "timestamp": time.time(), "version": APP_VERSION }) @app.route('/api/version') def api_version(): """Returns the application version.""" return jsonify({ "version": APP_VERSION, "name": "GenX FX Trading Automation" }) @app.route('/api/system_info') def system_info(): """Returns basic system information.""" return jsonify({ "platform": platform.platform(), "python_version": sys.version, "processor": platform.processor(), "hostname": platform.node(), "time": datetime.now().isoformat() }) @app.route('/api/files') def list_important_files(): """Lists important files in the repository.""" important_files = [] for root, dirs, files in os.walk(REPO_ROOT): # Skip hidden directories and common build artifacts dirs[:] = [d for d in dirs if not d.startswith('.') and d not in ['node_modules', '__pycache__', 'dist', 'build']] for file in files: if file.endswith(('.mq5', '.mqh', '.py', '.sh', '.ps1', '.md')): rel_path = os.path.relpath(os.path.join(root, file), REPO_ROOT) important_files.append(rel_path) return jsonify({ "count": len(important_files), "files": sorted(important_files)[:100] # Limit to 100 files }) @app.after_request def add_security_headers(response): """Add security headers to every response.""" csp = "default-src 'self'; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com; font-src https://fonts.gstatic.com; script-src 'self' 'unsafe-inline'" response.headers['Content-Security-Policy'] = csp response.headers['X-Content-Type-Options'] = 'nosniff' response.headers['X-Frame-Options'] = 'SAMEORIGIN' response.headers['Referrer-Policy'] = 'strict-origin-when-cross-origin' return response @app.route('/') def dashboard(): global DASHBOARD_TEMPLATE try: html_readme = get_cached_markdown(README_PATH) or "

README.md not found.

" html_verification = get_cached_markdown(VERIFICATION_PATH) or "

VERIFICATION.md not found.

" if DASHBOARD_TEMPLATE is None: DASHBOARD_TEMPLATE = app.jinja_env.from_string(DASHBOARD_HTML) return DASHBOARD_TEMPLATE.render( html_readme=html_readme, html_verification=html_verification, year=datetime.now().year, version=APP_VERSION, platform=platform.system(), python_version=platform.python_version(), last_refresh=datetime.now().strftime('%Y-%m-%d %H:%M:%S') ) except Exception as e: logger.error(f"Error rendering dashboard: {e}", exc_info=True) return "Internal Server Error", 500 if __name__ == '__main__': port = int(os.environ.get('PORT', 8080)) print(f"Starting web dashboard on port {port}...") app.run(host='0.0.0.0', port=port)