"""
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}
"""
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)