from flask import Flask, render_template, jsonify, Response import logging from datetime import datetime import json from config import Config from collector import GPONCollector from rrd_manager import RRDManager logging.basicConfig( level=logging.INFO, format='%(asctime)s - %(name)s - %(levelname)s - %(message)s' ) logger = logging.getLogger(__name__) app = Flask(__name__) app.config.from_object(Config) collector = GPONCollector(Config) rrd_manager = RRDManager(Config.RRD_DIR) @app.route('/') def index(): return render_template('index.html', config=Config) @app.route('/api/current') def api_current(): data = collector.get_data() return jsonify(data) @app.route('/api/history//') def history(metric, period): valid_metrics = ['optical', 'traffic', 'fec'] valid_periods = ['1h', '6h', '12h', '24h', '3d', '7d', '14d', '30d', '60d', '90d', '120d', '1y', '2y', '5y'] if metric not in valid_metrics: return jsonify({'error': 'Invalid metric'}), 400 if period not in valid_periods: return jsonify({'error': f'Invalid period. Valid: {valid_periods}'}), 400 data = rrd_manager.fetch(metric, period) if data: return jsonify(data) return jsonify({'error': 'No data'}), 404 @app.route('/api/alerts') def api_alerts(): alerts = collector.get_alerts() return jsonify(alerts) @app.route('/metrics') def prometheus_metrics(): data = collector.get_data() metrics = [] host = Config.GPON_HOST if data.get('rx_power') is not None: metrics.append(f'# HELP gpon_rx_power_dbm GPON RX optical power in dBm') metrics.append(f'# TYPE gpon_rx_power_dbm gauge') metrics.append(f'gpon_rx_power_dbm{{host="{host}"}} {data["rx_power"]:.3f}') if data.get('tx_power') is not None: metrics.append(f'# HELP gpon_tx_power_dbm GPON TX optical power in dBm') metrics.append(f'# TYPE gpon_tx_power_dbm gauge') metrics.append(f'gpon_tx_power_dbm{{host="{host}"}} {data["tx_power"]:.3f}') if data.get('voltage') is not None: metrics.append(f'# HELP gpon_voltage_volts GPON supply voltage in volts') metrics.append(f'# TYPE gpon_voltage_volts gauge') metrics.append(f'gpon_voltage_volts{{host="{host}"}} {data["voltage"]:.3f}') if data.get('tx_bias_current') is not None: metrics.append(f'# HELP gpon_tx_bias_ma GPON TX bias current in mA') metrics.append(f'# TYPE gpon_tx_bias_ma gauge') metrics.append(f'gpon_tx_bias_ma{{host="{host}"}} {data["tx_bias_current"]:.2f}') if data.get('temperature') is not None: metrics.append(f'# HELP gpon_temperature_celsius GPON temperature in Celsius') metrics.append(f'# TYPE gpon_temperature_celsius gauge') metrics.append(f'gpon_temperature_celsius{{host="{host}"}} {data["temperature"]:.2f}') if data.get('uptime') is not None: metrics.append(f'# HELP gpon_uptime_seconds GPON uptime in seconds') metrics.append(f'# TYPE gpon_uptime_seconds gauge') metrics.append(f'gpon_uptime_seconds{{host="{host}"}} {data["uptime"]}') status_value = 1 if data.get('status') == 'online' else 0 metrics.append(f'# HELP gpon_status GPON status (1=online, 0=offline)') metrics.append(f'# TYPE gpon_status gauge') metrics.append(f'gpon_status{{host="{host}"}} {status_value}') if data.get('rx_packets') is not None: metrics.append(f'# HELP gpon_rx_packets_total Total RX packets') metrics.append(f'# TYPE gpon_rx_packets_total counter') metrics.append(f'gpon_rx_packets_total{{host="{host}"}} {data["rx_packets"]}') if data.get('tx_packets') is not None: metrics.append(f'# HELP gpon_tx_packets_total Total TX packets') metrics.append(f'# TYPE gpon_tx_packets_total counter') metrics.append(f'gpon_tx_packets_total{{host="{host}"}} {data["tx_packets"]}') if data.get('rx_bytes') is not None: metrics.append(f'# HELP gpon_rx_bytes_total Total RX bytes') metrics.append(f'# TYPE gpon_rx_bytes_total counter') metrics.append(f'gpon_rx_bytes_total{{host="{host}"}} {data["rx_bytes"]}') if data.get('tx_bytes') is not None: metrics.append(f'# HELP gpon_tx_bytes_total Total TX bytes') metrics.append(f'# TYPE gpon_tx_bytes_total counter') metrics.append(f'gpon_tx_bytes_total{{host="{host}"}} {data["tx_bytes"]}') if data.get('fec_corrected') is not None: metrics.append(f'# HELP gpon_fec_corrected_total Total FEC corrected errors') metrics.append(f'# TYPE gpon_fec_corrected_total counter') metrics.append(f'gpon_fec_corrected_total{{host="{host}"}} {data["fec_corrected"]}') if data.get('fec_uncorrected') is not None: metrics.append(f'# HELP gpon_fec_uncorrected_total Total FEC uncorrected errors') metrics.append(f'# TYPE gpon_fec_uncorrected_total counter') metrics.append(f'gpon_fec_uncorrected_total{{host="{host}"}} {data["fec_uncorrected"]}') if data.get('fec_total_codewords') is not None: metrics.append(f'# HELP gpon_fec_codewords_total Total FEC codewords') metrics.append(f'# TYPE gpon_fec_codewords_total counter') metrics.append(f'gpon_fec_codewords_total{{host="{host}"}} {data["fec_total_codewords"]}') vendor = data.get('vendor_id', 'unknown').replace('"', '\\"') serial = data.get('serial_number', 'unknown').replace('"', '\\"') version = data.get('version', 'unknown').replace('"', '\\"') mac = data.get('mac_address', 'unknown').replace('"', '\\"') metrics.append(f'# HELP gpon_device_info GPON device information') metrics.append(f'# TYPE gpon_device_info gauge') metrics.append(f'gpon_device_info{{host="{host}",vendor="{vendor}",serial="{serial}",version="{version}",mac="{mac}"}} 1') olt_vendor = data.get('olt_vendor_info', 'unknown').replace('"', '\\"') olt_version = data.get('olt_version_info', 'unknown').replace('"', '\\"') metrics.append(f'# HELP gpon_olt_info OLT information') metrics.append(f'# TYPE gpon_olt_info gauge') metrics.append(f'gpon_olt_info{{host="{host}",olt_vendor="{olt_vendor}",olt_version="{olt_version}"}} 1') return Response('\n'.join(metrics) + '\n', mimetype='text/plain') def update_rrd_loop(): import time while True: try: data = collector.get_data() if data: rrd_manager.update(data) except Exception as e: logger.error(f"RRD update error: {e}") time.sleep(Config.POLL_INTERVAL) if __name__ == '__main__': logger.info("=" * 70) logger.info("GPON Monitor - Starting") logger.info(f"Host: {Config.GPON_HOST}:{Config.GPON_PORT}") logger.info(f"Web: http://{Config.LISTEN_HOST}:{Config.LISTEN_PORT}") logger.info(f"RRD Directory: {Config.RRD_DIR}") logger.info("=" * 70) collector.start() from threading import Thread rrd_thread = Thread(target=update_rrd_loop, daemon=True) rrd_thread.start() app.run( host=Config.LISTEN_HOST, port=Config.LISTEN_PORT, debug=False, threaded=True )