|
|
@@ -5,6 +5,8 @@ import base64 |
|
|
|
import collections |
|
|
|
import datetime |
|
|
|
import functools |
|
|
|
import hashlib |
|
|
|
import html |
|
|
|
import importlib.util |
|
|
|
import inspect |
|
|
|
import ircstates |
|
|
@@ -741,19 +743,42 @@ class WebServer: |
|
|
|
lines.append(f'{"(PW) " if auth else ""}<a href="/{path}/today">{channel}</a>') |
|
|
|
return aiohttp.web.Response(text = f'<html><body>{"<br />".join(lines)}</body></html>', content_type = 'text/html') |
|
|
|
|
|
|
|
def _raw_to_lines(self, f, filter = lambda dt, command, content: True): |
|
|
|
# f: iterable producing str lines (e.g. file-like) on iteration or bytes |
|
|
|
# filter: function taking the line fields (ts: float, command: str, content: str) and returning whether to include the line |
|
|
|
if isinstance(f, bytes): |
|
|
|
f = f.decode('utf-8').splitlines() |
|
|
|
for line in f: |
|
|
|
ts, command, content = line.strip().split(' ', 2) |
|
|
|
ts = float(ts) |
|
|
|
if not filter(ts, command, content): |
|
|
|
continue |
|
|
|
yield ts, command, content |
|
|
|
|
|
|
|
def _render_log(self, lines, path, withDate = False): |
|
|
|
# lines: iterable of (timestamp: float, command: str, content: str) |
|
|
|
# withDate: whether to include the date with the time of the log line |
|
|
|
ret = [] |
|
|
|
for ts, command, content in lines: |
|
|
|
d = datetime.datetime.utcfromtimestamp(ts).replace(tzinfo = datetime.timezone.utc) |
|
|
|
date = f'{d:%Y-%m-%d }' if withDate else '' |
|
|
|
lineId = hashlib.md5(f'{ts} {command} {content}'.encode('utf-8')).hexdigest()[:8] |
|
|
|
ret.append(f'<tr id="l{lineId}" class="command_{html.escape(command)}"><td><a href="/{html.escape(path)}/{d:%Y-%m-%d}#l{lineId}">{date}{d:%H:%M:%S}</a></td><td>{html.escape(content)}</td></tr>') |
|
|
|
return '<table>\n' + '\n'.join(ret) + '\n</table>' |
|
|
|
|
|
|
|
async def get_log(self, request): |
|
|
|
self.logger.info(f'Received request {id(request)} from {request.remote!r} for {request.path!r}') |
|
|
|
if request.match_info['date'] == 'today': |
|
|
|
date = datetime.datetime.now(tz = datetime.timezone.utc).replace(hour = 0, minute = 0, second = 0, microsecond = 0) |
|
|
|
else: |
|
|
|
date = datetime.datetime.strptime(request.match_info['date'], '%Y-%m-%d').replace(tzinfo = datetime.timezone.utc) |
|
|
|
dateStart = date.timestamp() |
|
|
|
dateEnd = (date + datetime.timedelta(days = 1)).timestamp() |
|
|
|
#TODO Implement this in a better way... |
|
|
|
fn = date.strftime('%Y-%m.log') |
|
|
|
with open(os.path.join(self.config['storage']['path'], request.match_info["path"], fn), 'r') as fp: |
|
|
|
lines = [l.strip().split(' ', 2) for l in fp] |
|
|
|
dateStart = date.timestamp() |
|
|
|
dateEnd = (date + datetime.timedelta(days = 1)).timestamp() |
|
|
|
return aiohttp.web.Response(text = f'<html><body><a href="/{request.match_info["path"]}/{(date - datetime.timedelta(days = 1)).strftime("%Y-%m-%d")}">Previous day</a> <a href="/{request.match_info["path"]}/{(date + datetime.timedelta(days = 1)).strftime("%Y-%m-%d")}">Next day</a><br /><br />' + '<br />'.join(' '.join(l) for l in lines if dateStart <= float(l[0]) < dateEnd) + '</body></html>', content_type = 'text/html') |
|
|
|
lines = list(self._raw_to_lines(fp, filter = lambda ts, command, content: dateStart <= ts <= dateEnd)) |
|
|
|
return aiohttp.web.Response(text = f'<html><body><a href="/{request.match_info["path"]}/{(date - datetime.timedelta(days = 1)).strftime("%Y-%m-%d")}">Previous day</a> <a href="/{request.match_info["path"]}/{(date + datetime.timedelta(days = 1)).strftime("%Y-%m-%d")}">Next day</a><br /><br />' + self._render_log(lines, request.match_info['path']) + '</body></html>', content_type = 'text/html') |
|
|
|
|
|
|
|
async def search(self, request): |
|
|
|
self.logger.info(f'Received request {id(request)} from {request.remote!r} for {request.path!r}') |
|
|
@@ -761,10 +786,10 @@ class WebServer: |
|
|
|
if 'q' not in request.query: |
|
|
|
return aiohttp.web.Response(text = '<html><body><form><input name="q" /><input type="submit" value="Search!" /></form></body></html>', content_type = 'text/html') |
|
|
|
|
|
|
|
proc = await asyncio.create_subprocess_exec('grep', '--fixed-strings', '--recursive', request.query['q'], os.path.join(self.config['storage']['path'], request.match_info["path"], ''), stdout = asyncio.subprocess.PIPE) |
|
|
|
proc = await asyncio.create_subprocess_exec('grep', '--fixed-strings', '--recursive', '--no-filename', request.query['q'], os.path.join(self.config['storage']['path'], request.match_info['path'], ''), stdout = asyncio.subprocess.PIPE) |
|
|
|
#TODO Limit size and runtime |
|
|
|
stdout, _ = await proc.communicate() |
|
|
|
return aiohttp.web.Response(text = '<html><body>' + '<br />'.join(stdout.decode('utf-8').splitlines()) + '</body></html>', content_type = 'text/html') |
|
|
|
return aiohttp.web.Response(text = '<html><body>' + self._render_log(self._raw_to_lines(stdout), request.match_info['path'], withDate = True) + '</body></html>', content_type = 'text/html') |
|
|
|
|
|
|
|
|
|
|
|
def configure_logging(config): |
|
|
|