CRITICAL: - frames.py: FRAMES_DIR corrected to /tmp/adacam/pics - frames.py: graceful handling when capture not started IMPORTANT: - wigle.py: added GET /api/1/wigle/config endpoint for Varroa - app.py: added GET /api/1/debug/redis-keys endpoint for GPS troubleshooting - install.sh: removed python validation that runs from wrong directory
167 lines
5.8 KiB
Python
167 lines
5.8 KiB
Python
"""WiGLE wardriving status and config endpoints."""
|
|
import os
|
|
import sqlite3
|
|
import subprocess
|
|
import time
|
|
from flask import Blueprint, jsonify, request
|
|
from ..auth import require_auth
|
|
|
|
bp = Blueprint('wigle', __name__, url_prefix='/api/1/wigle')
|
|
|
|
WIGLE_DB = '/data/adacam/wigle.db'
|
|
|
|
|
|
def _get_db():
|
|
"""Get SQLite connection to wigle.db, create tables if needed."""
|
|
os.makedirs(os.path.dirname(WIGLE_DB), exist_ok=True)
|
|
conn = sqlite3.connect(WIGLE_DB)
|
|
conn.execute('''CREATE TABLE IF NOT EXISTS wigle_records (
|
|
id INTEGER PRIMARY KEY AUTOINCREMENT, bssid TEXT NOT NULL, ssid TEXT,
|
|
auth_mode TEXT, first_seen TEXT, channel INTEGER, rssi INTEGER,
|
|
lat REAL, lon REAL, alt REAL, accuracy REAL, uploaded INTEGER DEFAULT 0, created_at INTEGER)''')
|
|
conn.execute('''CREATE TABLE IF NOT EXISTS wigle_config (key TEXT PRIMARY KEY, value TEXT)''')
|
|
return conn
|
|
|
|
|
|
def _get_config(conn):
|
|
cur = conn.execute('SELECT key, value FROM wigle_config')
|
|
return dict(cur.fetchall())
|
|
|
|
|
|
def _mask_api_name(api_name: str) -> str:
|
|
"""Mask API name showing only first 4 chars."""
|
|
if not api_name or len(api_name) < 4:
|
|
return '●●●●●●●●' if api_name else ''
|
|
return api_name[:4] + '●●●●●●●●'
|
|
|
|
|
|
@bp.route('/status')
|
|
def wigle_status():
|
|
"""Get WiGLE service status (unauthenticated)."""
|
|
try:
|
|
conn = _get_db()
|
|
cfg = _get_config(conn)
|
|
|
|
total = conn.execute('SELECT COUNT(*) FROM wigle_records').fetchone()[0]
|
|
pending = conn.execute('SELECT COUNT(*) FROM wigle_records WHERE uploaded=0').fetchone()[0]
|
|
|
|
last_scan = cfg.get('last_scan')
|
|
last_upload = cfg.get('last_upload')
|
|
|
|
conn.close()
|
|
|
|
return jsonify({
|
|
'enabled': cfg.get('enabled', '0') == '1',
|
|
'api_name': _mask_api_name(cfg.get('api_name', '')),
|
|
'total_networks': total,
|
|
'pending_upload': pending,
|
|
'last_scan': int(last_scan) if last_scan else None,
|
|
'last_upload': int(last_upload) if last_upload else None
|
|
})
|
|
except Exception as e:
|
|
return jsonify({'error': str(e), 'enabled': False, 'total_networks': 0, 'pending_upload': 0})
|
|
|
|
|
|
@bp.route('/stats')
|
|
def wigle_stats():
|
|
"""Get WiGLE statistics (unauthenticated)."""
|
|
try:
|
|
conn = _get_db()
|
|
|
|
total = conn.execute('SELECT COUNT(*) FROM wigle_records').fetchone()[0]
|
|
uploaded = conn.execute('SELECT COUNT(*) FROM wigle_records WHERE uploaded=1').fetchone()[0]
|
|
pending = conn.execute('SELECT COUNT(*) FROM wigle_records WHERE uploaded=0').fetchone()[0]
|
|
|
|
# Scans today (records created in last 24h)
|
|
day_ago = int(time.time()) - 86400
|
|
scans_today = conn.execute(
|
|
'SELECT COUNT(DISTINCT first_seen) FROM wigle_records WHERE created_at > ?',
|
|
(day_ago,)
|
|
).fetchone()[0]
|
|
|
|
conn.close()
|
|
|
|
return jsonify({
|
|
'total_networks': total,
|
|
'uploaded_networks': uploaded,
|
|
'pending_upload': pending,
|
|
'scans_today': scans_today
|
|
})
|
|
except Exception as e:
|
|
return jsonify({'error': str(e), 'total_networks': 0, 'uploaded_networks': 0, 'pending_upload': 0, 'scans_today': 0})
|
|
|
|
|
|
@bp.route('/config', methods=['GET'])
|
|
def get_wigle_config():
|
|
"""Get WiGLE configuration (unauthenticated, tokens masked)."""
|
|
try:
|
|
conn = _get_db()
|
|
cfg = _get_config(conn)
|
|
conn.close()
|
|
|
|
return jsonify({
|
|
'enabled': cfg.get('enabled', '0') == '1',
|
|
'api_name': cfg.get('api_name', ''),
|
|
'api_token_set': bool(cfg.get('api_token')),
|
|
'scan_interval_seconds': int(cfg.get('scan_interval_seconds', '30')),
|
|
'upload_interval_seconds': int(cfg.get('upload_interval_seconds', '300')),
|
|
})
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500
|
|
|
|
|
|
@bp.route('/config', methods=['POST'])
|
|
@require_auth
|
|
def set_wigle_config():
|
|
"""Set WiGLE configuration (authenticated)."""
|
|
data = request.get_json() or {}
|
|
|
|
try:
|
|
conn = _get_db()
|
|
|
|
# Update enabled status
|
|
if 'enabled' in data:
|
|
conn.execute(
|
|
'INSERT OR REPLACE INTO wigle_config (key, value) VALUES (?, ?)',
|
|
('enabled', '1' if data['enabled'] else '0')
|
|
)
|
|
|
|
# Update API credentials
|
|
if 'api_name' in data:
|
|
conn.execute(
|
|
'INSERT OR REPLACE INTO wigle_config (key, value) VALUES (?, ?)',
|
|
('api_name', data['api_name'])
|
|
)
|
|
|
|
if 'api_token' in data:
|
|
conn.execute(
|
|
'INSERT OR REPLACE INTO wigle_config (key, value) VALUES (?, ?)',
|
|
('api_token', data['api_token'])
|
|
)
|
|
|
|
# Optional: scan/upload intervals
|
|
if 'scan_interval_seconds' in data:
|
|
conn.execute(
|
|
'INSERT OR REPLACE INTO wigle_config (key, value) VALUES (?, ?)',
|
|
('scan_interval_seconds', str(data['scan_interval_seconds']))
|
|
)
|
|
|
|
if 'upload_interval_seconds' in data:
|
|
conn.execute(
|
|
'INSERT OR REPLACE INTO wigle_config (key, value) VALUES (?, ?)',
|
|
('upload_interval_seconds', str(data['upload_interval_seconds']))
|
|
)
|
|
|
|
conn.commit()
|
|
conn.close()
|
|
|
|
# Restart the wigle service to pick up config changes
|
|
try:
|
|
subprocess.run(['systemctl', 'restart', 'adacam-wigle'], timeout=10)
|
|
except Exception:
|
|
pass # Service may not exist yet
|
|
|
|
return jsonify({'ok': True, 'message': 'WiGLE configuration updated'})
|
|
|
|
except Exception as e:
|
|
return jsonify({'error': str(e)}), 500
|