Show device country next to its IP address, resolved via ip-api.com

This commit is contained in:
ubuntu
2026-08-16 10:39:17 +00:00
parent 7a5dba7dbf
commit 10fd5106f8
2 changed files with 60 additions and 7 deletions
+47 -3
View File
@@ -31,6 +31,7 @@ import pathlib
from datetime import datetime from datetime import datetime
from bson import ObjectId from bson import ObjectId
import urllib.request import urllib.request
import ipaddress
@@ -75,6 +76,8 @@ settings_collection = db_iptv['settings']
devices_db = db_iptv['devices'] devices_db = db_iptv['devices']
devices = [] devices = []
active_xtream_imports = {} active_xtream_imports = {}
sio_session_ips = {}
ip_country_cache = {}
# Configure Socket IO # Configure Socket IO
sio = socketio.AsyncServer(logger=True, engineio_logger=True, cors_allowed_origins='*') sio = socketio.AsyncServer(logger=True, engineio_logger=True, cors_allowed_origins='*')
@@ -1417,6 +1420,34 @@ app.router.add_post('/gemini_search', gemini_search)
app.router.add_get('/channels_epg', channels_epg) app.router.add_get('/channels_epg', channels_epg)
app.router.add_get('/epg', epg_programs) app.router.add_get('/epg', epg_programs)
def resolve_ip_country(ip_address):
if not ip_address:
return ''
if ip_address in ip_country_cache:
return ip_country_cache[ip_address]
try:
ip_obj = ipaddress.ip_address(ip_address)
if ip_obj.is_private or ip_obj.is_loopback or ip_obj.is_link_local:
ip_country_cache[ip_address] = ''
return ''
except ValueError:
return ''
country = ''
try:
response = requests.get(
f'http://ip-api.com/json/{ip_address}',
params={'fields': 'status,country'},
timeout=5,
)
data = response.json()
if data.get('status') == 'success':
country = data.get('country', '') or ''
except requests.RequestException as exc:
logger.warning("IP country lookup failed for %s: %s", ip_address, exc)
ip_country_cache[ip_address] = country
return country
# SIO FUNCTIONS # SIO FUNCTIONS
def messageReceived(): def messageReceived():
@@ -1424,7 +1455,12 @@ def messageReceived():
@sio.event @sio.event
def connect(sid, environ): def connect(sid, environ):
print("connect ", sid, flush=True) # engineio's aiohttp integration hardcodes REMOTE_ADDR to 127.0.0.1;
# the real peer address is only available via the underlying aiohttp request.
aiohttp_request = environ.get('aiohttp.request')
ip_address = aiohttp_request.remote if aiohttp_request else ''
sio_session_ips[sid] = ip_address
print("connect ", sid, ip_address, flush=True)
@sio.event @sio.event
@@ -1437,6 +1473,7 @@ async def communication(fromSid, data):
@sio.event @sio.event
def disconnect(sid): def disconnect(sid):
sio_session_ips.pop(sid, None)
print('disconnect ', sid, flush=True) print('disconnect ', sid, flush=True)
@@ -1456,13 +1493,20 @@ async def polling(sid, data):
vpn_status = data['vpn_status'] vpn_status = data['vpn_status']
except: except:
vpn_status = 'unknown' vpn_status = 'unknown'
ip_address = sio_session_ips.get(sid, '')
if ip_address in ip_country_cache:
ip_country = ip_country_cache[ip_address]
else:
ip_country = await asyncio.get_event_loop().run_in_executor(None, resolve_ip_country, ip_address)
device_data = devices_db.find_one({ 'id_device':IdDevice}) device_data = devices_db.find_one({ 'id_device':IdDevice})
if device_data: if device_data:
query = { 'id_device':IdDevice} query = { 'id_device':IdDevice}
update = { "$set": { "last_datetime": device_last_datetime ,"name": device_name, "sid":sid, "mediaRunning":mediaRunning, 'vpn_status':vpn_status, 'data_running':data_running, 'auto_snapshot_enabled': auto_snapshot_enabled, 'snapshot_interval_seconds': snapshot_interval_seconds} } update = { "$set": { "last_datetime": device_last_datetime ,"name": device_name, "sid":sid, "mediaRunning":mediaRunning, 'vpn_status':vpn_status, 'data_running':data_running, 'auto_snapshot_enabled': auto_snapshot_enabled, 'snapshot_interval_seconds': snapshot_interval_seconds, 'ip_address': ip_address, 'ip_country': ip_country} }
devices_db.update_one(query, update) devices_db.update_one(query, update)
else: else:
new_device = { 'id_device': IdDevice, 'name' : device_name, 'last_datetime' : device_last_datetime, 'sid':sid ,'mediaRunning' : mediaRunning, 'vpn_status':vpn_status, 'data_running':data_running, 'auto_snapshot_enabled': auto_snapshot_enabled, 'snapshot_interval_seconds': snapshot_interval_seconds} new_device = { 'id_device': IdDevice, 'name' : device_name, 'last_datetime' : device_last_datetime, 'sid':sid ,'mediaRunning' : mediaRunning, 'vpn_status':vpn_status, 'data_running':data_running, 'auto_snapshot_enabled': auto_snapshot_enabled, 'snapshot_interval_seconds': snapshot_interval_seconds, 'ip_address': ip_address, 'ip_country': ip_country}
devices_db.insert_one(new_device) devices_db.insert_one(new_device)
if data_running: if data_running:
+10 -1
View File
@@ -180,6 +180,11 @@
font-weight: 500; font-weight: 500;
min-width: 0; min-width: 0;
} }
.device-ip {
font-size: 0.75rem;
font-weight: 400;
color: #9ca3af;
}
.device-status { .device-status {
color: var(--success); color: var(--success);
font-size: 0.95rem; font-size: 0.95rem;
@@ -2618,10 +2623,14 @@
status2 = '#ffe6e6' status2 = '#ffe6e6'
} }
var ipAddress = dataJSON[i].ip_address || '';
var ipCountry = dataJSON[i].ip_country || '';
var ipLine = ipAddress ? `<div class="device-ip text-truncate">${ipAddress}${ipCountry ? ' &middot; ' + ipCountry : ''}</div>` : '';
var activeClass = (selectedDeviceId && selectedDeviceId === dataJSON[i].id_device) ? 'active' : ''; var activeClass = (selectedDeviceId && selectedDeviceId === dataJSON[i].id_device) ? 'active' : '';
html += `<div class="device-card ${activeClass}" id="btn_select_device_${dataJSON[i].id_device}" data-param1="${dataJSON[i].id_device}" data-param2="${dataJSON[i].name}" data-param3="${dataJSON[i].sid}" data-param4="${dataJSON[i].vpn_status}"> html += `<div class="device-card ${activeClass}" id="btn_select_device_${dataJSON[i].id_device}" data-param1="${dataJSON[i].id_device}" data-param2="${dataJSON[i].name}" data-param3="${dataJSON[i].sid}" data-param4="${dataJSON[i].vpn_status}">
<div class="device-card-header"> <div class="device-card-header">
<div class="device-name text-truncate">${dataJSON[i].name}</div> <div class="device-name text-truncate">${dataJSON[i].name}${ipLine}</div>
<div class="device-actions"> <div class="device-actions">
<div class="device-status">${status}</div> <div class="device-status">${status}</div>
<div>${vpn_status_icon}</div> <div>${vpn_status_icon}</div>