Merge branch 'clientinfo'
This commit is contained in:
commit
646364dac6
|
|
@ -0,0 +1,21 @@
|
|||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>OpenWebRX+ Clients</title>
|
||||
<link rel="shortcut icon" type="image/x-icon" href="static/favicon.ico" />
|
||||
<link rel="stylesheet" href="static/css/bootstrap.min.css" />
|
||||
<link rel="stylesheet" type="text/css" href="static/css/admin.css" />
|
||||
<script src="compiled/settings.js"></script>
|
||||
<meta charset="utf-8">
|
||||
</head>
|
||||
<body>
|
||||
${header}
|
||||
<div class="container">
|
||||
<div class="row">
|
||||
<h1 class="col-12">Clients</h1>
|
||||
</div>
|
||||
<div class="row client-list">
|
||||
${clients}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
@ -171,8 +171,12 @@ UI.toggleFrame = function(on) {
|
|||
this.frame = on;
|
||||
LS.save('ui_frame', on);
|
||||
$('#openwebrx-frame-checkbox').attr('checked', on);
|
||||
$('#openwebrx-panel-receiver').css( 'border', on ? '2px solid white' : '2px solid transparent');
|
||||
$('#openwebrx-dialog-bookmark').css('border', on ? '2px solid white' : '2px solid transparent');
|
||||
|
||||
var border = on ? '2px solid white' : '2px solid transparent';
|
||||
$('#openwebrx-panel-receiver').css( 'border', border);
|
||||
$('#openwebrx-dialog-bookmark').css('border', border);
|
||||
// $('#openwebrx-digimode-canvas-container').css('border', border);
|
||||
// $('.openwebrx-message-panel').css('border', border);
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,26 @@
|
|||
$.fn.clientList = function() {
|
||||
this.each(function() {
|
||||
$(this).on('click', '.client-ban', function(e) {
|
||||
var mins = $('#ban-minutes').val();
|
||||
$.ajax("/ban", {
|
||||
data: JSON.stringify({ ip: this.value, mins: mins }),
|
||||
contentType: 'application/json',
|
||||
method: 'POST'
|
||||
}).done(function() {
|
||||
document.location.reload();
|
||||
});
|
||||
return false;
|
||||
});
|
||||
|
||||
$(this).on('click', '.client-unban', function(e) {
|
||||
$.ajax("/unban", {
|
||||
data: JSON.stringify({ ip: this.value }),
|
||||
contentType: 'application/json',
|
||||
method: 'POST'
|
||||
}).done(function() {
|
||||
document.location.reload();
|
||||
});
|
||||
return false;
|
||||
});
|
||||
});
|
||||
}
|
||||
|
|
@ -37,5 +37,11 @@ ${header}
|
|||
<a class="btn btn-secondary" href="features">Feature report</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<h1 class="col-12">Clients</h1>
|
||||
</div>
|
||||
<div class="row client-list">
|
||||
${clients}
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
|
|
|||
|
|
@ -9,4 +9,5 @@ $(function(){
|
|||
$('#scheduler').schedulerInput();
|
||||
$('.exponential-input').exponentialInput();
|
||||
$('.device-log-messages').logMessages();
|
||||
});
|
||||
$('.client-list').clientList();
|
||||
});
|
||||
|
|
|
|||
|
|
@ -181,6 +181,7 @@ class CompiledAssetsController(GzipMixin, ModificationAwareController):
|
|||
"lib/settings/SchedulerInput.js",
|
||||
"lib/settings/ExponentialInput.js",
|
||||
"lib/settings/LogMessages.js",
|
||||
"lib/settings/ClientList.js",
|
||||
"settings.js",
|
||||
],
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
from owrx.controllers.admin import AuthorizationMixin
|
||||
from owrx.controllers.template import WebpageController
|
||||
from owrx.breadcrumb import Breadcrumb, BreadcrumbItem, BreadcrumbMixin
|
||||
from owrx.websocket import WebSocketConnection
|
||||
import json
|
||||
import re
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class ClientController(AuthorizationMixin, WebpageController):
|
||||
def indexAction(self):
|
||||
self.serve_template("clients.html", **self.template_variables())
|
||||
|
||||
def template_variables(self):
|
||||
variables = super().template_variables()
|
||||
variables["clients"] = self.renderClients()
|
||||
return variables
|
||||
|
||||
@staticmethod
|
||||
def renderClients():
|
||||
return """
|
||||
<table class='table'>
|
||||
<tr>
|
||||
<th>IP Address</th>
|
||||
<th>SDR Profile</th>
|
||||
<th>Local Time</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
{clients}
|
||||
<tr>
|
||||
<td></td>
|
||||
<td></td>
|
||||
<td colspan="2">
|
||||
ban for
|
||||
<select id="ban-minutes">
|
||||
<option value="15">15 minutes</option>
|
||||
<option value="30">30 minutes</option>
|
||||
<option value="60">1 hour</option>
|
||||
<option value="180">3 hours</option>
|
||||
<option value="360">6 hours</option>
|
||||
<option value="720">12 hours</option>
|
||||
<option value="1440">1 day</option>
|
||||
</select>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
""".format(
|
||||
clients="".join(ClientController.renderClient(c) for c in WebSocketConnection.listAll())
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def renderClient(c):
|
||||
return "<tr><td>{0}</td><td>{1}</td><td>{2} {3}</td><td>{4}</td></tr>".format(
|
||||
ClientController.renderIp(c["ip"]),
|
||||
"banned" if c["ban"] else c["sdr"] + " " + c["band"] if "sdr" in c else "n/a",
|
||||
"until" if c["ban"] else "since",
|
||||
c["ts"].strftime('%H:%M:%S'),
|
||||
ClientController.renderButtons(c)
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def renderIp(ip):
|
||||
ip = re.sub("^::ffff:", "", ip)
|
||||
return """
|
||||
<a href="https://www.geolocation.com/en_us?ip={0}#ipresult" target="_blank">{1}</a>
|
||||
""".format(ip, ip)
|
||||
|
||||
@staticmethod
|
||||
def renderButtons(c):
|
||||
action = "unban" if c["ban"] else "ban"
|
||||
return """
|
||||
<button type="button" class="btn btn-sm btn-danger client-{0}" value="{1}">{2}</button>
|
||||
""".format(action, c["ip"], action)
|
||||
|
||||
def ban(self):
|
||||
try:
|
||||
data = json.loads(self.get_body().decode("utf-8"))
|
||||
mins = int(data["mins"]) if "mins" in data else 0
|
||||
if "ip" in data and mins > 0:
|
||||
logger.info("Banning {0} for {1} minutes".format(data["ip"], mins))
|
||||
WebSocketConnection.banIp(data["ip"], mins)
|
||||
self.send_response("{}", content_type="application/json", code=200)
|
||||
except:
|
||||
self.send_response("{}", content_type="application/json", code=400)
|
||||
|
||||
def unban(self):
|
||||
try:
|
||||
data = json.loads(self.get_body().decode("utf-8"))
|
||||
if "ip" in data:
|
||||
logger.info("Unbanning {0}".format(data["ip"]))
|
||||
WebSocketConnection.unbanIp(data["ip"])
|
||||
self.send_response("{}", content_type="application/json", code=200)
|
||||
except:
|
||||
self.send_response("{}", content_type="application/json", code=400)
|
||||
|
|
@ -1,7 +1,9 @@
|
|||
from owrx.config import Config
|
||||
from owrx.controllers.admin import AuthorizationMixin
|
||||
from owrx.controllers.template import WebpageController
|
||||
from owrx.controllers.clients import ClientController
|
||||
from owrx.breadcrumb import Breadcrumb, BreadcrumbItem, BreadcrumbMixin
|
||||
from owrx.websocket import WebSocketConnection
|
||||
from abc import ABCMeta, abstractmethod
|
||||
from urllib.parse import parse_qs
|
||||
|
||||
|
|
@ -14,6 +16,11 @@ class SettingsController(AuthorizationMixin, WebpageController):
|
|||
def indexAction(self):
|
||||
self.serve_template("settings.html", **self.template_variables())
|
||||
|
||||
def template_variables(self):
|
||||
variables = super().template_variables()
|
||||
variables["clients"] = ClientController.renderClients()
|
||||
return variables
|
||||
|
||||
|
||||
class SettingsFormController(AuthorizationMixin, BreadcrumbMixin, WebpageController, metaclass=ABCMeta):
|
||||
def __init__(self, handler, request, options):
|
||||
|
|
|
|||
19
owrx/http.py
19
owrx/http.py
|
|
@ -6,6 +6,7 @@ from owrx.controllers.assets import OwrxAssetsController, AprsSymbolsController,
|
|||
from owrx.controllers.websocket import WebSocketController
|
||||
from owrx.controllers.api import ApiController
|
||||
from owrx.controllers.metrics import MetricsController
|
||||
from owrx.controllers.clients import ClientController
|
||||
from owrx.controllers.settings import SettingsController
|
||||
from owrx.controllers.settings.general import GeneralSettingsController
|
||||
from owrx.controllers.settings.sdr import (
|
||||
|
|
@ -23,12 +24,14 @@ from owrx.controllers.session import SessionController
|
|||
from owrx.controllers.profile import ProfileController
|
||||
from owrx.controllers.imageupload import ImageUploadController
|
||||
from owrx.controllers.robots import RobotsController
|
||||
from owrx.websocket import WebSocketConnection
|
||||
from owrx.storage import Storage
|
||||
from http.server import BaseHTTPRequestHandler
|
||||
from urllib.parse import urlparse, parse_qs
|
||||
import re
|
||||
from abc import ABC, abstractmethod
|
||||
from http.cookies import SimpleCookie
|
||||
from datetime import datetime
|
||||
|
||||
import logging
|
||||
|
||||
|
|
@ -158,6 +161,9 @@ class Router(object):
|
|||
StaticRoute(
|
||||
"/settings/decoding", DecodingSettingsController, method="POST", options={"action": "processFormData"}
|
||||
),
|
||||
StaticRoute("/clients", ClientController),
|
||||
StaticRoute("/ban", ClientController, method="POST", options={"action": "ban"}),
|
||||
StaticRoute("/unban", ClientController, method="POST", options={"action": "unban"}),
|
||||
StaticRoute("/login", SessionController, options={"action": "loginAction"}),
|
||||
StaticRoute("/login", SessionController, method="POST", options={"action": "processLoginAction"}),
|
||||
StaticRoute("/logout", SessionController, options={"action": "logoutAction"}),
|
||||
|
|
@ -173,12 +179,15 @@ class Router(object):
|
|||
return r
|
||||
|
||||
def route(self, handler, request):
|
||||
route = self.find_route(request)
|
||||
if route is not None:
|
||||
controller = route.controller
|
||||
controller(handler, request, route.controllerOptions).handle_request()
|
||||
else:
|
||||
if WebSocketConnection.isIpBanned(handler.client_address[0]):
|
||||
handler.send_error(404, "Not Found", "The page you requested could not be found.")
|
||||
else:
|
||||
route = self.find_route(request)
|
||||
if route is None:
|
||||
handler.send_error(404, "Not Found", "The page you requested could not be found.")
|
||||
else:
|
||||
controller = route.controller
|
||||
controller(handler, request, route.controllerOptions).handle_request()
|
||||
|
||||
|
||||
class RequestHandler(BaseHTTPRequestHandler):
|
||||
|
|
|
|||
|
|
@ -280,6 +280,9 @@ class SdrSource(ABC):
|
|||
def getName(self):
|
||||
return self.props["name"]
|
||||
|
||||
def getProfileName(self):
|
||||
return self.getProfiles()[self.getProfileId()]["name"]
|
||||
|
||||
def getProps(self):
|
||||
return self.props
|
||||
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ from multiprocessing import Pipe
|
|||
import select
|
||||
import threading
|
||||
from abc import ABC, abstractmethod
|
||||
from datetime import datetime, timedelta
|
||||
|
||||
import logging
|
||||
|
||||
|
|
@ -46,6 +47,7 @@ class Handler(ABC):
|
|||
|
||||
class WebSocketConnection(object):
|
||||
connections = []
|
||||
bans = {}
|
||||
|
||||
@staticmethod
|
||||
def closeAll():
|
||||
|
|
@ -55,7 +57,61 @@ class WebSocketConnection(object):
|
|||
except:
|
||||
logger.exception("exception while shutting down websocket connections")
|
||||
|
||||
@staticmethod
|
||||
def listAll():
|
||||
result = []
|
||||
for c in WebSocketConnection.connections:
|
||||
entry = {
|
||||
"ts" : c.startTime,
|
||||
"ip" : c.handler.client_address[0],
|
||||
"ban" : False
|
||||
}
|
||||
rx = c.messageHandler
|
||||
if hasattr(rx, "sdr"):
|
||||
entry["sdr"] = rx.sdr.getName()
|
||||
entry["band"] = rx.sdr.getProfileName()
|
||||
result.append(entry)
|
||||
WebSocketConnection.cleanBans()
|
||||
for ip in WebSocketConnection.bans:
|
||||
result.append({
|
||||
"ts" : WebSocketConnection.bans[ip],
|
||||
"ip" : ip,
|
||||
"ban" : True
|
||||
})
|
||||
return result
|
||||
|
||||
@staticmethod
|
||||
def banIp(ip: str, minutes: int):
|
||||
WebSocketConnection.cleanBans()
|
||||
WebSocketConnection.bans[ip] = datetime.now() + timedelta(minutes=minutes)
|
||||
banned = []
|
||||
for c in WebSocketConnection.connections:
|
||||
if ip == c.handler.client_address[0]:
|
||||
banned.append(c)
|
||||
for c in banned:
|
||||
try:
|
||||
c.close()
|
||||
except:
|
||||
logger.exception("exception while banning %s" % ip)
|
||||
|
||||
@staticmethod
|
||||
def unbanIp(ip: str):
|
||||
if ip in WebSocketConnection.bans:
|
||||
del WebSocketConnection.bans[ip]
|
||||
|
||||
@staticmethod
|
||||
def isIpBanned(ip: str):
|
||||
return ip in WebSocketConnection.bans and datetime.now() < WebSocketConnection.bans[ip]
|
||||
|
||||
@staticmethod
|
||||
def cleanBans():
|
||||
now = datetime.now()
|
||||
old = [ip for ip in WebSocketConnection.bans if now >= WebSocketConnection.bans[ip]]
|
||||
for ip in old:
|
||||
del WebSocketConnection.bans[ip]
|
||||
|
||||
def __init__(self, handler, messageHandler: Handler):
|
||||
self.startTime = datetime.now()
|
||||
self.handler = handler
|
||||
self.handler.connection.setblocking(0)
|
||||
self.messageHandler = None
|
||||
|
|
|
|||
Loading…
Reference in New Issue