diff --git a/app.py b/app.py index 7428093..c3f4585 100644 --- a/app.py +++ b/app.py @@ -41,18 +41,107 @@ def send_webhook(device, status, diagnostics): } requests.post(config['webhook_url'], json=webhook_data) +import requests +from urllib3.exceptions import InsecureRequestWarning +requests.packages.urllib3.disable_warnings(InsecureRequestWarning) + +class UnifiAPI: + def __init__(self, config): + self.base_url = config['unifi']['controller'] + self.headers = { + 'X-API-KEY': config['unifi']['api_key'], + 'Accept': 'application/json' + } + self.site_id = config['unifi']['site_id'] + self.session = requests.Session() + self.session.verify = False + + def get_device_details(self, device_id): + try: + url = f"{self.base_url}/proxy/network/integration/v1/sites/{self.site_id}/devices/{device_id}" + response = self.session.get(url, headers=self.headers, timeout=5) + response.raise_for_status() + return response.json() + except requests.exceptions.RequestException as e: + logging.error(f"Failed to get device details: {e}") + return None + + def get_device_diagnostics(self, device): + details = self.get_device_details(device['device_id']) + if not details: + return {'state': 'ERROR', 'error': 'Failed to fetch device details'} + + diagnostics = { + 'state': details['state'], + 'firmware': { + 'version': details['firmwareVersion'], + 'updatable': details.get('firmwareUpdatable', False) + }, + 'network': { + 'ip': details['ipAddress'], + 'mac': details['macAddress'] + }, + 'uptime': { + 'adopted_at': details['adoptedAt'], + 'provisioned_at': details['provisionedAt'] + }, + 'interfaces': {} + } + + # Add interface details + if 'interfaces' in details: + diagnostics['interfaces'] = self._parse_interfaces(details['interfaces']) + + return diagnostics + + def _parse_interfaces(self, interfaces): + result = { + 'ports': {}, + 'radios': {} + } + + for port in interfaces.get('ports', []): + result['ports'][f"port_{port['idx']}"] = { + 'state': port['state'], + 'type': port['connector'], + 'speed': { + 'current': port['speedMbps'], + 'max': port['maxSpeedMbps'] + } + } + + for radio in interfaces.get('radios', []): + result['radios'][f"{radio['frequencyGHz']}GHz"] = { + 'standard': radio['wlanStandard'], + 'channel': radio['channel'], + 'width': f"{radio['channelWidthMHz']}MHz" + } + + return result + + + def run_diagnostics(device): - diagnostics = {} + config = load_config() + unifi = UnifiAPI(config) + + diagnostics = unifi.get_device_diagnostics(device) + if device['connection_type'] == 'fiber': - # Add your fiber diagnostic commands here - diagnostics['optical_power'] = subprocess.getoutput('optical-power-check ' + device['ip']) - diagnostics['light_levels'] = subprocess.getoutput('light-level-check ' + device['ip']) - else: - # Add your copper diagnostic commands here - diagnostics['cable_test'] = subprocess.getoutput('ethtool ' + device['ip']) - diagnostics['signal_quality'] = subprocess.getoutput('signal-quality-check ' + device['ip']) + # Add fiber-specific diagnostics + sfp_data = get_sfp_diagnostics(device['ip']) + diagnostics['sfp'] = sfp_data + return diagnostics -@app.route('/') + +@app.route('/api/diagnostics') +def get_diagnostics(): + config = load_config() + diagnostics = {} + for device in config['devices']: + if device['device_id']: + diagnostics[device['name']] = run_diagnostics(device) + return jsonify(diagnostics)@app.route('/') def home(): return render_template('index.html') diff --git a/config.json b/config.json index 07b1942..c28fce8 100644 --- a/config.json +++ b/config.json @@ -1,32 +1,74 @@ { + "unifi": { + "controller": "https://10.10.10.1", + "api_key": "kyPfIsAVie3hwMD4Bc1MjAu8N7HVPIb8", + "site_id": "default" + }, "devices": [ { "name": "UniFi Dream Machine Pro", - "ip": "192.168.1.1", + "ip": "10.10.10.1", "type": "router", + "connection_type": "copper", + "critical": true, + "device_id": "udm-pro-id" + }, + { + "name": "USP PDU Pro", + "ip": "10.10.10.107", + "type": "pdu", + "connection_type": "copper", + "critical": true + }, + { + "name": "USW Aggregation", + "ip": "10.10.10.238", + "type": "switch", + "connection_type": "fiber", + "critical": true + }, + { + "name": "USW Flex Mini", + "ip": "10.10.10.169", + "type": "switch", "connection_type": "copper" }, { - "name": "UniFi Switch 24 PoE", - "ip": "192.168.1.2", + "name": "USW Pro 24 PoE", + "ip": "10.10.10.139", "type": "switch", - "connection_type": "fiber" + "connection_type": "fiber", + "critical": true, + "device_id": "usw-24-poe-id" }, { - "name": "AP Living Room", - "ip": "192.168.1.10", - "type": "access_point" + "name": "U6 Lite", + "ip": "10.10.10.154", + "type": "access_point", + "connection_type": "copper" }, { - "name": "AP Office", - "ip": "192.168.1.11", - "type": "access_point" + "name": "USP Strip", + "ip": "10.10.10.243", + "type": "pdu", + "connection_type": "copper" } ], "check_interval": 30, "webhook_url": "https://your-webhook-url", + "alert_thresholds": { + "fiber": { + "optical_power_min": -10, + "optical_power_max": 0, + "error_rate_threshold": 0.001 + }, + "copper": { + "signal_quality_min": 70, + "max_cable_length": 100 + } + }, "troubleshooting": { - "fiber_tests": ["optical_power", "light_levels", "error_rate"], - "copper_tests": ["cable_length", "crosstalk", "signal_quality"] + "fiber_tests": ["optical_power", "light_levels", "error_rate", "sfp_diagnostics"], + "copper_tests": ["cable_length", "crosstalk", "signal_quality", "poe_status"] } } diff --git a/index.html b/index.html index dd770fa..1644c83 100644 --- a/index.html +++ b/index.html @@ -17,49 +17,15 @@

Network Overview

- - UniFi Dream Machine Pro -
-
- - UniFi Switch 24 PoE + +
+ UniFi Dream Machine Pro + 10.10.10.1 +
+
- -
-

Network Performance

-
-
-
- -
-

Access Points

-
-
- - AP Living Room -
-
- - AP Office -
-
-
- -
-

Connected Clients

-
-
- -
-

Diagnostics

-
-
-
-
- - diff --git a/static/app.js b/static/app.js index 8f99b77..b68d081 100644 --- a/static/app.js +++ b/static/app.js @@ -6,18 +6,77 @@ function updateDiagnostics() { diagnosticsPanel.innerHTML = ''; Object.entries(data).forEach(([device, diagnostics]) => { - const diagElement = document.createElement('div'); - diagElement.className = `diagnostic-item ${diagnostics.type}-diagnostic`; - diagElement.innerHTML = ` -

${device}

-
${JSON.stringify(diagnostics.results, null, 2)}
- `; + const diagElement = createDiagnosticElement(device, diagnostics); diagnosticsPanel.appendChild(diagElement); }); }); } -// Update diagnostics every minute +function createDiagnosticElement(device, diagnostics) { + const element = document.createElement('div'); + element.className = `diagnostic-item ${diagnostics.connection_type}-diagnostic`; + + const content = ` +

${device}

+
+
+ Status: + ${diagnostics.state} +
+
+ Firmware: + ${diagnostics.firmware.version} +
+ ${createInterfaceHTML(diagnostics.interfaces)} +
+ `; + + element.innerHTML = content; + return element; +} + +function createInterfaceHTML(interfaces) { + let html = '
'; + + // Add port information + Object.entries(interfaces.ports || {}).forEach(([portName, port]) => { + html += ` +
+ ${portName}: + ${port.speed.current}/${port.speed.max} Mbps + ${port.state} +
+ `; + }); + + // Add radio information + Object.entries(interfaces.radios || {}).forEach(([radioName, radio]) => { + html += ` +
+ ${radioName}: + ${radio.standard} - Ch${radio.channel} (${radio.width}) +
+ `; + }); + + html += '
'; + return html; +} + +function updateDeviceStatus() { + fetch('/api/status') + .then(response => response.json()) + .then(data => { + Object.entries(data).forEach(([deviceName, status]) => { + updateDeviceIndicator(deviceName, status); + }); + }); +} + +// Update intervals +setInterval(updateDeviceStatus, 30000); setInterval(updateDiagnostics, 60000); -// Initial update + +// Initial updates updateDeviceStatus(); +updateDiagnostics(); diff --git a/style.css b/style.css index 844b4f0..e7e8a50 100644 --- a/style.css +++ b/style.css @@ -86,4 +86,66 @@ body { .copper-diagnostic { border-color: #F59E0B; -} \ No newline at end of file +} + +.device-info { + display: flex; + flex-direction: column; + gap: 4px; +} + +.device-details { + font-size: 0.8em; + color: #666; +} + +.diagnostic-details { + display: grid; + gap: 15px; + padding: 10px; +} + +.status-group, .firmware-group, .interfaces-group { + display: flex; + flex-direction: column; + gap: 8px; +} + +.interface-item { + display: flex; + align-items: center; + gap: 10px; +} + +.label { + font-weight: 500; + color: #666; +} + +.value { + font-family: monospace; +} + +.state { + padding: 2px 8px; + border-radius: 12px; + font-size: 0.8em; +} + +.state.up { + background-color: #10B981; + color: white; +} + +.state.down { + background-color: #EF4444; + color: white; +} + +.online { + color: #10B981; +} + +.offline { + color: #EF4444; +}