""" mini_proxy.py - Reverse proxy Tailscale Funnel (porta 10000) Mavis 2026-07-21 - ripristino dopo cancellazione accidentale. Route: - / → landing HTML con link a tutte le dashboard - /webhook → http://127.0.0.1:5580 (webhook_receiver.py) - /stats → http://127.0.0.1:5503 (stats_dashboard.py) - /square → http://127.0.0.1:5504 (square_monitor.py) - /tableau → file statico G:\\AI TRADING ENGINE\\live_deploy\\tableau_de_bord.html - /healthz → 200 OK con info processi Tailscale Funnel: https://mabest.tail2b1710.ts.net → http://127.0.0.1:10000 """ import os import sys import time import json import logging import requests from pathlib import Path from flask import Flask, Response, redirect, request from datetime import datetime LIVE_DEPLOY = Path(r'G:\AI TRADING ENGINE\live_deploy') LOG_DIR = LIVE_DEPLOY / 'logs' LOG_FILE = LOG_DIR / 'mini_proxy.log' PID_FILE = Path(r'/tmp/mini_proxy.pid') LOG_DIR.mkdir(parents=True, exist_ok=True) logging.basicConfig( filename=LOG_FILE, level=logging.INFO, format='%(asctime)s [%(levelname)s] %(message)s', ) log = logging.getLogger() app = Flask(__name__) # Backend ports WEBHOOK_PORT = 5580 STATS_PORT = 5503 SQUARE_PORT = 5504 LANDING_HTML = """ Mavis Trading Engine - Hub

🤖 Mavis Trading Engine

Hub centrale — aggiornato {timestamp}

📊 Stats Dashboard ...

Equity curve, posizioni aperte, PnL live

⬜ Square Monitor ...

Strategia del quadrato, setup attivi

📋 Tableau de Bord OK

Trade journal, casistiche, statistiche

🔔 Webhook ...

Endpoint TradingView → Bybit

""" def _proxy(target_url: str, timeout: int = 30) -> Response: """Proxy generico: inoltra request al backend, ritorna Response con stessi headers/status.""" try: # Costruisci URL target con query string if request.query_string: sep = '&' if '?' in target_url else '?' target_url = f"{target_url}{sep}{request.query_string.decode('utf-8')}" # Inoltra request al backend resp = requests.request( method=request.method, url=target_url, headers={k: v for k, v in request.headers if k.lower() not in ('host', 'content-length')}, data=request.get_data(), cookies=request.cookies, allow_redirects=False, timeout=timeout, ) # Escludi header che non dobbiamo inoltrare excluded_headers = ['content-encoding', 'content-length', 'transfer-encoding', 'connection'] headers = [(k, v) for k, v in resp.headers.items() if k.lower() not in excluded_headers] return Response(resp.content, resp.status_code, headers) except requests.exceptions.RequestException as e: log.error(f"proxy error to {target_url}: {e}") return Response(f"Backend error: {e}", 502) @app.route('/') def index(): return Response(LANDING_HTML.format(timestamp=datetime.now().strftime("%Y-%m-%d %H:%M:%S")), content_type='text/html') @app.route('/webhook', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH']) @app.route('/webhook/', methods=['GET', 'POST', 'PUT', 'DELETE', 'PATCH']) def proxy_webhook(subpath=''): target = f"http://127.0.0.1:{WEBHOOK_PORT}/webhook/{subpath}" if subpath else f"http://127.0.0.1:{WEBHOOK_PORT}/webhook" return _proxy(target) @app.route('/stats', methods=['GET']) @app.route('/stats/', methods=['GET']) def proxy_stats(subpath=''): target = f"http://127.0.0.1:{STATS_PORT}/{subpath}" if subpath else f"http://127.0.0.1:{STATS_PORT}/" return _proxy(target) @app.route('/square', methods=['GET']) @app.route('/square/', methods=['GET']) def proxy_square(subpath=''): target = f"http://127.0.0.1:{SQUARE_PORT}/{subpath}" if subpath else f"http://127.0.0.1:{SQUARE_PORT}/" return _proxy(target) @app.route('/tableau') def tableau(): """File statico tableau de bord.""" path = LIVE_DEPLOY / 'tableau_de_bord.html' if not path.exists(): return Response("Tableau de bord non ancora generato (attendi 5min dall'avvio di generate_tableau.py)", 404) return Response(path.read_text(encoding='utf-8'), content_type='text/html') @app.route('/healthz') def healthz(): """Health check di tutti i backend.""" result = {'timestamp': datetime.now().isoformat(), 'stats': False, 'square': False, 'webhook': False} for name, port in [('stats', STATS_PORT), ('square', SQUARE_PORT), ('webhook', WEBHOOK_PORT)]: try: r = requests.get(f"http://127.0.0.1:{port}/", timeout=2) result[name] = r.status_code < 500 except Exception: result[name] = False return Response(json.dumps(result, indent=2), content_type='application/json') def main(): log.info("=" * 70) log.info("=== MINI PROXY STARTED (Mavis 2026-07-21 ripristino) ===") log.info("=" * 70) log.info(f"Webhook backend: 127.0.0.1:{WEBHOOK_PORT}") log.info(f"Stats backend: 127.0.0.1:{STATS_PORT}") log.info(f"Square backend: 127.0.0.1:{SQUARE_PORT}") log.info(f"Proxy in ascolto su 0.0.0.0:10000 (Tailscale Funnel entry point)") with open(PID_FILE, "w") as f: f.write(str(os.getpid())) # Disabilita banner di flask import logging as _l _l.getLogger('werkzeug').setLevel(_l.WARNING) app.run(host='0.0.0.0', port=10000, debug=False, threaded=True) if __name__ == '__main__': try: main() except KeyboardInterrupt: log.info("STOPPED (KeyboardInterrupt)") except Exception as e: log.critical(f"FATAL: {e}") import traceback log.critical(traceback.format_exc()) sys.exit(1)