commits
tags
import re
import subprocess
import time
from flask import Blueprint, jsonify, request, abort
from auth import login_required
bp = Blueprint('wifi', __name__, url_prefix='/api/wifi')
def _wifi_iface():
try:
with open('/proc/net/wireless') as f:
for line in f:
if ':' in line:
iface = line.split(':')[0].strip()
if iface:
return iface
except Exception:
pass
return 'wlo1'
_WIFI_IFACE = _wifi_iface()
def _wpa(*args):
cmd = ['sudo', '-n', '/usr/sbin/wpa_cli', '-i', _WIFI_IFACE] + list(args)
try:
r = subprocess.run(cmd, capture_output=True, text=True, timeout=15)
return r.returncode, r.stdout.strip()
except Exception as e:
return 1, str(e)
def _wifi_status():
rc, out = _wpa('status')
d = {}
if rc == 0:
for line in out.splitlines():
if '=' in line:
k, v = line.split('=', 1)
d[k.strip()] = v.strip()
connected = d.get('wpa_state') == 'COMPLETED'
ip = ''
try:
r = subprocess.run(['ip', '-4', 'addr', 'show', _WIFI_IFACE],
capture_output=True, text=True, timeout=5)
m = re.search(r'inet\s+(\d+\.\d+\.\d+\.\d+)', r.stdout)
if m:
ip = m.group(1)
except Exception:
pass
return {
'connected': connected,
'ssid': d.get('ssid', ''),
'ip': ip,
'signal': int(d.get('signal_level', 0) or 0),
'iface': _WIFI_IFACE,
'sudo_ok': rc != 1 or 'sudo' not in out,
}
def _wifi_scan():
_wpa('scan')
time.sleep(2.5)
rc, out = _wpa('scan_results')
networks = []
seen = set()
for line in out.splitlines():
if not line or line.startswith('bssid'):
continue
parts = line.split('\t')
if len(parts) < 5:
continue
ssid = parts[4].strip()
if not ssid or ssid in seen:
continue
seen.add(ssid)
try:
dbm = int(parts[2])
except (ValueError, IndexError):
dbm = -90
flags = parts[3] if len(parts) > 3 else ''
if 'WPA2' in flags:
sec = 'WPA2'
elif 'WPA' in flags:
sec = 'WPA'
elif 'WEP' in flags:
sec = 'WEP'
else:
sec = 'open'
networks.append({
'ssid': ssid,
'signal': max(0, min(100, 2 * (dbm + 100))),
'security': sec,
})
networks.sort(key=lambda x: -x['signal'])
return networks
def _wifi_saved():
rc, out = _wpa('list_networks')
saved = []
for line in out.splitlines():
parts = line.split('\t')
if len(parts) >= 2 and parts[0].strip().isdigit():
ssid = parts[1].strip()
if ssid:
flags = parts[3].strip() if len(parts) > 3 else ''
saved.append({
'id': parts[0].strip(),
'ssid': ssid,
'current': '[CURRENT]' in flags,
})
return saved
def _wifi_connect(ssid, password):
for net in _wifi_saved():
if net['ssid'] == ssid:
_wpa('remove_network', net['id'])
rc, net_id = _wpa('add_network')
net_id = net_id.strip()
if not net_id.isdigit():
return False, 'Kunde inte lägga till nätverk'
_wpa('set_network', net_id, 'ssid', f'"{ssid}"')
if password:
_wpa('set_network', net_id, 'psk', f'"{password}"')
else:
_wpa('set_network', net_id, 'key_mgmt', 'NONE')
_wpa('select_network', net_id)
for _ in range(20):
time.sleep(1)
s = _wifi_status()
if s['connected'] and s['ssid'] == ssid:
_wpa('save_config')
return True, s.get('ip', '')
_wpa('remove_network', net_id)
return False, 'Anslutning misslyckades (fel lösenord?)'
@bp.route('/status')
@login_required
def api_wifi_status():
return jsonify(_wifi_status())
@bp.route('/networks')
@login_required
def api_wifi_networks():
try:
nets = _wifi_scan()
known = {n['ssid'] for n in _wifi_saved()}
for n in nets:
n['known'] = n['ssid'] in known
return jsonify({'networks': nets, 'iface': _WIFI_IFACE})
except Exception as e:
return jsonify({'networks': [], 'error': str(e)})
@bp.route('/connect', methods=['POST'])
@login_required
def api_wifi_connect():
data = request.get_json()
if not data or not data.get('ssid'):
abort(400)
ssid = str(data['ssid'])[:64]
password = str(data.get('password', ''))[:64]
if '"' in ssid or '"' in password:
return jsonify({'ok': False, 'message': 'Ogiltiga tecken i SSID/lösenord'}), 400
ok, msg = _wifi_connect(ssid, password)
return jsonify({'ok': ok, 'message': msg if not ok else '', 'ip': msg if ok else ''})
@bp.route('/disconnect', methods=['POST'])
@login_required
def api_wifi_disconnect():
_wpa('disconnect')
return '', 204
@bp.route('/saved')
@login_required
def api_wifi_saved():
return jsonify({'saved': _wifi_saved()})
@bp.route('/saved/<net_id>', methods=['DELETE'])
@login_required
def api_wifi_remove(net_id):
if not net_id.isdigit():
abort(400)
_wpa('remove_network', net_id)
_wpa('save_config')
return '', 204