Various improvements to web interface and handling of it.

This commit is contained in:
Krytarik Raido 2020-11-11 20:13:04 +01:00 committed by Nicolas Coevoet
parent 26646c7d7f
commit 2962c3190b

367
server.py
View File

@ -1,13 +1,6 @@
import http.server
import os
import time
import base64
import re
import os, re, time, base64
import supybot.utils as utils
import sqlite3
import collections
import urllib.parse
from io import StringIO
import http.server, sqlite3
host = 'http://domain.tld'
port = 80
@ -16,11 +9,11 @@ webpath = '/bantracker'
username = 'username'
password = 'password'
filename = '/home/botaccount/data/networkname/ChanTracker.db'
channels = [] # empty to allows view of all channels recorded, otherwise restrict the views to channels
channels = [] # empty to allow view of all channels recorded, otherwise restrict the views to channels
# usage python server.py
auth = '%s:%s' % (username,password)
base64string = base64.b64encode(auth.encode('UTF-8'))
base64string = base64.b64encode(auth.encode('utf-8')).decode('utf-8')
def timeElapsed(elapsed, short=False, leadingZeroes=False, years=True,
weeks=True, days=True, hours=True, minutes=True, seconds=True):
@ -66,7 +59,6 @@ def timeElapsed(elapsed, short=False, leadingZeroes=False, years=True,
return format('%L', ret)
class MyHandler(http.server.BaseHTTPRequestHandler):
server_version= "Ircd-Seven/1.1"
if not standalone:
def log_request(self, *args):
pass # disable logging
@ -75,11 +67,26 @@ class MyHandler( http.server.BaseHTTPRequestHandler ):
self.page(self.path)
def page(self, query):
def write(subtitle, body):
page = [
'<html style="text-align:center;font-size:1.2em;">',
'<head>\n<title>BanTracker%s</title>' % (' &raquo; %s' % subtitle if subtitle else ''),
'<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />',
'<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"></link>',
'</head>\n<body style="margin:0.5em;width:98%;margin-left:auto;margin-right:auto;text-align:left;" class="container">'
] + body + ['</body>\n</html>']
self.send_response(200)
self.send_header("Content-type","text/html")
full = '\n'.join(page)
print('HTML lines %s' % len(full))
self.send_header("Content-length",len(full))
self.end_headers()
self.wfile.write(full.encode('utf-8'))
if standalone:
h = '%s:%s/' % (host,port)
else:
h = '%s/' % webpath
body = []
if not query:
return
if query.startswith('/?username='):
@ -93,287 +100,260 @@ class MyHandler( http.server.BaseHTTPRequestHandler ):
if aa[0] == 'password':
p = aa[1]
if u and p:
raw = base64.encodebytes(bytes('%s:%s' % (u,p), 'UTF-8'))[:-1]
if not raw == base64string:
raw = base64.b64encode('%s:%s'.encode('utf-8') % (u,p)).decode('utf-8')
if raw != base64string:
query = ''
else:
query = '/?hash=%s' % base64string.decode('UTF-8')
query = '/?hash=%s' % base64string
if not query.startswith('/?hash='):
body.append('<html>\n<head>\n<title>ChanTracker</title>\n')
body.append('<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n')
body.append("</head>\n<body>\n")
body.append('<form action="%s">\n' % h)
body.append('<p>Username:<input name="username" /></p>\n')
body.append('<p>Password:<input name="password" type="password"/></p>\n')
body.append('<input type="submit" value="Login" />\n')
body.append("</form>\n")
body.append("</body>\n<html>\n")
self.send_response(200)
self.send_header("Content-type","text/html")
full = ''.join(body)
self.send_header("Content-length",str(len(full)))
self.end_headers()
self.wfile.write(full.encode('UTF-8'))
subtitle = ''
body = [
'<form action="%s">' % h,
'<p>Username: <input name="username" /></p>',
'<p>Password: <input name="password" type="password"/></p>',
'<button type="submit" class="btn btn-default">Login</button>',
'</form>'
]
write(subtitle, body)
return
if query.startswith('/?hash='):
a = query.split('&')[0]
a = a.replace('/?hash=','')
query = query.replace('%3D','=')
query = query.replace('/?hash=%s' % base64string.decode('UTF-8'),'/')
q = '?hash=%s' % base64string.decode('UTF-8')
query = urllib.parse.unquote( query )
query = query.replace('/?hash=%s' % base64string,'')
query = query.lstrip('&')
q = '?hash=%s' % base64string
query = utils.web.urlunquote(query)
subtitle = ''
body = [
'<div class="row"><div class="col-xs-6">',
'<form action="%s" class="form">' % q,
'<div class="input-group">',
'<input type="hidden" name="hash" value="%s">' % base64string,
'<input name="search" class="form-control" />',
'<span class="input-group-btn"><button type="submit" class="btn btn-default">Search</button></span>',
'</div></form></div></div>',
'<div class="clearfix"></div>'
]
if not query:
write(subtitle, body)
return
print(query)
body.append('<html style="text-align:center;font-size:1.2em;">\n<head>\n<title>BanTracker - %s</title>\n' % query)
body.append('<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />\n')
body.append('<link rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css"></link>\n')
# body.append('<script src="http://www.kryogenix.org/code/browser/sorttable/sorttable.js"></script>\n')
body.append('</head>\n<body style="margin:0.5em;width:98%;margin-left:auto;margin-right:auto;text-align:left;" class="container">\n')
body.append('<div class="row"><div class="col-xs-6">\n')
body.append('<form action="%s" class="form">\n' % q)
body.append('<div class="input-group">')
body.append('<input type="hidden" name="hash" value="%s">' % base64string.decode('UTF-8'))
body.append('<input name="search" class="form-control" />\n')
body.append('<span class="input-group-btn"><button type="submit" class="btn btn-default">Search</button></span>\n')
body.append('</div></form></div></div>\n')
body.append('<div class="clearfix"></div>\n')
subtitle = query
db = self._getbandb()
c = db.cursor()
if query:
ar = []
if query.startswith('/&id='):
search = query.split('/&id=')[1]
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE id=? ORDER BY id DESC""",(search,))
if query.startswith('id='):
search = query.split('=')[1]
si = int(search)
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE id=?""",(si,))
r = c.fetchall()
if len(r):
ban = r[0]
(id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = ban
if not len(channels) or channel in channels:
body.append('<h3>#%s</h3>\n' % id)
body.append('<p>#%s by %s in %s : +%s : %s</p>\n' % (id,oper,channel,kind,mask))
body.append('<p>Begin at %s</p>\n' % time.strftime('%Y-%m-%d %H:%M:%S GMT',time.gmtime(float(begin_at))))
if not channels or channel in channels:
body.extend([
'<h3>#%d</h3>' % id,
'<p>#%d by <a href="%s%s&%s">%s</a>' % (id,h,q,utils.web.urlencode({'oper':oper}),oper),
'in <a href="%s%s&channel=%s">%s</a>:' % (h,q,channel.split('#')[1],channel),
'+%s <a href="%s%s&%s">%s</a></p>' % (kind,h,q,utils.web.urlencode({'mask':mask}),mask),
'<p>Begin at %s</p>' % time.strftime('%Y-%m-%d %H:%M:%S GMT',time.gmtime(float(begin_at)))
])
was = float(begin_at) == float(end_at)
if was:
was = 'forever'
else:
was = timeElapsed(float(end_at) - float(begin_at))
body.append('<p>Original duration : %s</p>\n' % was)
body.append('<p>Original duration: %s</p>' % was)
if not removed_at:
if was != 'forever':
body.append('<p>%s</p>\n' % 'It will expire in %s' % timeElapsed(float(end_at) - time.time()))
body.append('<p>It will expire in %s</p>' % timeElapsed(float(end_at) - time.time()))
else:
body.append('<p>%s</p>\n' % 'Removed after %s on %s by %s' % (timeElapsed(float(removed_at)-float(begin_at)),time.strftime('%Y-%m-%d %H:%M:%S GMT',time.gmtime(float(removed_at))),removed_by))
body.extend(['<p>Removed after %s' % timeElapsed(float(removed_at)-float(begin_at)),
'on %s' % time.strftime('%Y-%m-%d %H:%M:%S GMT',time.gmtime(float(removed_at))),
'by <a href="%s%s&%s">%s</a></p>' % (h,q,utils.web.urlencode({'removed_by':removed_by}),removed_by)])
c.execute("""SELECT full,log FROM nicks WHERE ban_id=?""",(id,))
r = c.fetchall()
if len(r):
users = r
body.append('<h3>Logs</h3>\n')
for u in users:
(full,log) = u
body.append('<p>for %s</p>\n' % full)
body.append('<h3>Logs</h3>')
for nick in r:
(full,log) = nick
body.append('<p>for %s</p>' % full)
if log != '':
body.append('<ul>\n')
body.append('<ul>')
for line in log.split('\n'):
if line != '':
body.append('<li>%s</li>\n' % line)
body.append('</ul>\n')
body.append('<li>%s</li>' % line)
body.append('</ul>')
c.execute("""SELECT oper,at,comment FROM comments WHERE ban_id=?""",(id,))
r = c.fetchall()
if len(r):
body.append('<h3>Comments</h3>\n')
body.append('<ul>\n')
comments = r
for com in comments:
(oper,at,comment) = com
body.extend(['<h3>Comments</h3>', '<ul>'])
for comment in r:
(oper,at,com) = comment
s = time.strftime('%Y-%m-%d %H:%M:%S GMT',time.gmtime(float(at)))
body.append('<li>%s by %s : %s</li>\n' % (s,oper,comment))
body.append('</ul>\n')
elif query.startswith('/&channel='):
search = '#'+query.split('/&channel=')[1]
body.append('<li>%s by %s: %s</li>' % (s,oper,com))
body.append('</ul>')
c.close()
write(subtitle, body)
return
elif query.startswith('channel='):
search = '#'+query.split('=')[1]
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE channel=? ORDER BY id DESC""",(search,))
r = c.fetchall()
if len(r):
bans = r
for ban in bans:
(id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = ban
ar.append([int(id),channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by])
elif query.startswith('/&removed_by='):
search = query.split('/&removed_by=')[1]
ar.extend(r)
elif query.startswith('removed_by='):
search = query.split('=')[1]
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE removed_by=? ORDER BY id DESC""",(search,))
r = c.fetchall()
if len(r):
bans = r
for ban in bans:
(id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = ban
ar.append([int(id),channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by])
elif query.startswith('/&oper='):
search = query.split('/&oper=')[1]
ar.extend(r)
elif query.startswith('oper='):
search = query.split('=')[1]
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE oper=? ORDER BY id DESC""",(search,))
r = c.fetchall()
if len(r):
bans = r
for ban in bans:
(id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = ban
ar.append([int(id),channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by])
elif query.startswith('/&mask='):
search = query.split('/&mask=')[1]
glob = '*%s*' % search
like = '%'+search+'%'
c.execute("""SELECT ban_id, full FROM nicks WHERE full GLOB ? OR full LIKE ? OR log GLOB ? OR log LIKE ? ORDER BY ban_id DESC""",(glob,like,glob,like))
ar.extend(r)
elif query.startswith('mask='):
search = query.split('=')[1]
sg = '*%s*' % search
sl = '%%%s%%' % search
c.execute("""SELECT ban_id,full FROM nicks WHERE full GLOB ? OR full LIKE ? OR log GLOB ? OR log LIKE ? ORDER BY ban_id DESC""",(sg,sl,sg,sl))
r = c.fetchall()
L = []
a = {}
r = c.fetchall()
if len(r):
bans = r
d = {}
for ban in bans:
for ban in r:
(id,full) = ban
if not id in d:
if id not in d:
d[id] = id
for id in d:
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE id=? ORDER BY id DESC""",(int(id),))
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE id=?""",(id,))
r = c.fetchall()
if len(r):
bans = r
for ban in bans:
(id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = ban
a[str(id)] = ban
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE mask GLOB ? OR mask LIKE ? ORDER BY id DESC""",(glob,like))
for ban in r:
a[ban[0]] = ban
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE mask GLOB ? OR mask LIKE ? ORDER BY id DESC""",(sg,sl))
r = c.fetchall()
if len(r):
bans = r
for ban in bans:
(id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = ban
a[str(id)] = ban
for ban in r:
a[ban[0]] = ban
if len(a):
ar = []
for ban in a:
(id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = a[ban]
ar.append([int(id),channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by])
def sort_function (item):
return item[0]
ar.sort(key=sort_function)
ar.sort(reverse=True)
elif query.startswith('/&search='):
search = query.split('/&search=')[1]
for ban in list(a.keys()):
ar.append(a[ban])
ar.sort(key=lambda x: x[0], reverse=True)
elif query.startswith('search='):
search = query.split('=')[1]
search = search.replace('+','*')
print(search)
if search:
s = '*%s*' % search
qu = '%'+search+'%'
c.execute("""SELECT ban_id, full FROM nicks WHERE full GLOB ? OR full LIKE ? OR log GLOB ? OR log LIKE ? ORDER BY ban_id DESC""",(s,qu,s,qu))
if not re.match(r'^[0-9]+$', search):
sg = '*%s*' % search
sl = '%%%s%%' % search
si = None
c.execute("""SELECT ban_id,full FROM nicks WHERE full GLOB ? OR full LIKE ? OR log GLOB ? OR log LIKE ? ORDER BY ban_id DESC""",(sg,sl,sg,sl))
r = c.fetchall()
else:
si = int(search)
r = []
L = []
a = {}
r = c.fetchall()
if len(r):
bans = r
d = {}
for ban in bans:
for ban in r:
(id,full) = ban
if not id in d:
if id not in d:
d[id] = id
for id in d:
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE id=? ORDER BY id DESC""",(int(id),))
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE id=?""",(id,))
r = c.fetchall()
if len(r):
bans = r
for ban in bans:
(id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = ban
a[id] = ban
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE mask GLOB ? OR mask LIKE ? OR channel GLOB ? OR channel LIKE ? OR oper GLOB ? OR oper LIKE ? ORDER BY id DESC""",(s,qu,s,qu,s,qu))
for ban in r:
a[ban[0]] = ban
if not si:
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE mask GLOB ? OR mask LIKE ? OR channel GLOB ? OR channel LIKE ? OR oper GLOB ? OR oper LIKE ? ORDER BY id DESC""",(sg,sl,sg,sl,sg,sl))
else:
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE id=?""",(si,))
r = c.fetchall()
if len(r):
bans = r
for ban in bans:
(id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = ban
a[id] = ban
c.execute("""SELECT ban_id, comment FROM comments WHERE comment GLOB ? OR comment LIKE ? ORDER BY ban_id DESC""",(s,qu))
for ban in r:
a[ban[0]] = ban
if not si:
c.execute("""SELECT ban_id, comment FROM comments WHERE comment GLOB ? OR comment LIKE ? ORDER BY ban_id DESC""",(sg,sl))
r = c.fetchall()
else:
r = []
d = {}
if len(r):
bans = r
for ban in bans:
for ban in r:
(id,full) = ban
d[id] = id
for id in d:
if not id in a:
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE id=? ORDER BY id DESC LIMIT 1""",(int(id),))
if id not in a:
c.execute("""SELECT id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by FROM bans WHERE id=?""",(id,))
r = c.fetchall()
if len(r):
bans = r
for ban in bans:
(id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = ban
a[id] = ban
for ban in r:
a[ban[0]] = ban
if len(a):
ar = []
for ban in a:
(id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = a[ban]
ar.append([int(id),channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by])
def sort_function (item):
return item[0]
ar.sort(key=sort_function)
ar.sort(reverse=True)
else:
body.append('<p>nothing found</p>\n')
for ban in list(a.keys()):
ar.append(a[ban])
ar.sort(key=lambda x: x[0], reverse=True)
if len(ar):
print('found %s results' % len(ar))
i = 0
body.append('<h3>results <small>%s</small></h3>' % search)
body.append('<div class="row"><div class="col-xs-12"><table class="table table-bordered sortable">\n')
body.append('<thead><tr><th>ID</th><th>Channel</th><th>Operator</th><th>Kind</th><th>Target</th><th>Begin date</th><th>End date</th><th>Removed date</th><th>Removed by</th></tr></thead>\n')
body.append('<tbody>\n')
while i < len(ar):
(id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = ar[i]
if not len(channels) or channel in channels:
body.append('<tr>\n')
body.append('<td><a href="%s%s&id=%s">%s</a></td>\n' % (h,q,id,id))
body.append('<td><a href="%s%s&channel=%s">%s</a></td>\n' % (h,q,channel.split('#')[1],channel))
body.append('<td><a href="%s%s&%s">%s</a></td>\n' % (h,q,urllib.parse.urlencode({'oper':oper}),oper))
body.append('<td>+%s</td>\n' % kind)
body.append('<td><a href="%s%s&%s">%s</a></td>\n' % (h,q,urllib.parse.urlencode({'mask':mask}),mask))
print('Found %s results' % len(ar))
body.extend([
'<h3>Results <small>%s</small></h3>' % search,
'<div class="row"><div class="col-xs-12"><table class="table table-bordered sortable">',
'<thead><tr><th>ID</th><th>Channel</th><th>Operator</th><th>Type</th><th>Mask</th><th>Begin date</th><th>End date</th><th>Removed</th><th>Removed by</th></tr></thead>',
'<tbody>'
])
for ban in ar:
(id,channel,oper,kind,mask,begin_at,end_at,removed_at,removed_by) = ban
if not channels or channel in channels:
s = time.strftime('%Y-%m-%d %H:%M:%S GMT',time.gmtime(float(begin_at)))
body.append('<td>%s</td>\n' % s)
body.extend([
'<tr>',
'<td><a href="%s%s&id=%d">%d</a></td>' % (h,q,id,id),
'<td><a href="%s%s&channel=%s">%s</a></td>' % (h,q,channel.split('#')[1],channel),
'<td><a href="%s%s&%s">%s</a></td>' % (h,q,utils.web.urlencode({'oper':oper}),oper),
'<td>+%s</td>' % kind,
'<td><a href="%s%s&%s">%s</a></td>' % (h,q,utils.web.urlencode({'mask':mask}),mask),
'<td>%s</td>' % s
])
if end_at and end_at != begin_at:
s = time.strftime('%Y-%m-%d %H:%M:%S GMT',time.gmtime(float(end_at)))
body.append( '<td>%s</td>\n' % s)
body.append('<td>%s</td>' % s)
else:
body.append('<td></td>')
if removed_at:
s = time.strftime('%Y-%m-%d %H:%M:%S GMT',time.gmtime(float(removed_at)))
body.append('<td>%s</td>' % s)
else:
body.append( '<td></td>\n' )
body.append('<td></td>')
if removed_by:
body.append( '<td><a href="%s%s&%s">%s</a></td>\n' % (h,q,urllib.parse.urlencode({'removed_by':removed_by}),removed_by))
body.append('<td><a href="%s%s&%s">%s</a></td>' % (h,q,utils.web.urlencode({'removed_by':removed_by}),removed_by))
else:
body.append( '<td></td>\n')
body.append('<td></td>')
# affected = ''
# try:
# c.execute("""SELECT full, log FROM nicks WHERE ban_id=?""",(id,))
# affected = len(c.fetchall())
# except:
# affected = ''
# body.append( '<td>%s</td>\n' % affected)
body.append( '</tr>\n')
i = i+1
body.append('</tbody>\n')
body.append('</table></div>\n')
body.append("</body></html>")
self.send_response(200)
self.send_header("Content-type","text/html")
full = ''.join(body)
print('html lines %s' % len(full))
self.send_header("Content-length",len(full))
self.end_headers()
self.wfile.write(full.encode('UTF-8'))
# body.append('<td>%s</td>' % affected)
body.append('</tr>')
body.extend(['</tbody>', '</table></div>'])
else:
body.append('<p>Nothing found</p>')
c.close()
write(subtitle, body)
def _getbandb(self):
if os.path.exists(filename):
db = sqlite3.connect(filename,timeout=10)
db.text_factory = str
return db
db = sqlite3.connect(filename)
db.text_factory = str
c = db.cursor()
c.execute("""CREATE TABLE bans (
id INTEGER PRIMARY KEY,
@ -401,10 +381,9 @@ class MyHandler( http.server.BaseHTTPRequestHandler ):
db.commit()
return db
def httpd(handler_class=MyHandler, server_address = ('', port), ):
def httpd(handler_class=MyHandler, server_address=('', port)):
srvr = http.server.HTTPServer(server_address, handler_class)
srvr.serve_forever()
if __name__ == "__main__":
httpd()