diff options
-rw-r--r-- | labbot/addons/dashboard/__init__.py | 121 | ||||
-rw-r--r-- | labbot/addons/dashboard/templates/base.html | 2 | ||||
-rw-r--r-- | labbot/addons/dashboard/templates/login.html | 78 | ||||
-rw-r--r-- | labbot/addons/dashboard/templates/sidebar.html | 25 | ||||
-rw-r--r-- | labbot/config.py | 7 | ||||
-rw-r--r-- | requirements.txt | 1 |
6 files changed, 202 insertions, 32 deletions
diff --git a/labbot/addons/dashboard/__init__.py b/labbot/addons/dashboard/__init__.py index 9e63cbc..d1c08c2 100644 --- a/labbot/addons/dashboard/__init__.py +++ b/labbot/addons/dashboard/__init__.py @@ -4,48 +4,124 @@ a web dashboard for lab-bot import os import logging import json +import time +from functools import wraps import aiohttp import jinja2 import aiohttp_jinja2 # type: ignore +from itsdangerous.url_safe import URLSafeSerializer +from itsdangerous.exc import BadSignature from labbot import __version__ as labbot_version from labbot.config import Config log = logging.getLogger(__name__) +config = Config(__name__) +config.set_global_data( + accounts={ + "admin":"admin" + }, + secret="secret", + salt="salt123" +) + class BufferHandler(logging.Handler): - def __init__(self, dashboard, lines=-1): + def __init__(self, lines=-1): super().__init__(level=logging.DEBUG) - self.dashboard = dashboard + self.log_buffer = [] self.lines = lines def emit(self, record): try: msg = self.format(record) - return self.dashboard.log_buffer.append(msg) + return self.log_buffer.append(msg) finally: if self.lines > 0: - while len(self.dashboard.log_buffer) > self.lines: - self.dashboard.log_buffer.remove(0) + while len(self.log_buffer) > self.lines: + self.log_buffer.remove(0) + +auth_s = URLSafeSerializer(config["secret"], config["salt"]) +def check_auth(): + def decorator(func): + @wraps(func) + async def wrapper(*args, **kwargs): + full_kwargs = kwargs.copy() + full_kwargs.update(zip(func.__code__.co_varnames, args)) + try: + request = full_kwargs["coro"] + except KeyError: + try: + request = full_kwargs["args"] + except KeyError: + request = full_kwargs["request"] + + try: + session = request.cookies["session"] + data = auth_s.loads(session) + return await func(*args, **kwargs) + except (KeyError, BadSignature): + resp = aiohttp.web.HTTPFound( + location='/login', + headers=request.headers + ) + resp.del_cookie("session") + raise resp + + + return wrapper + return decorator + +def set_auth(): + def decorator(func): + @wraps(func) + async def wrapper(*args, **kwargs): + full_kwargs = kwargs.copy() + full_kwargs.update(zip(func.__code__.co_varnames, args)) + try: + request = full_kwargs["coro"] + except KeyError: + try: + request = full_kwargs["args"] + except KeyError: + request = full_kwargs["request"] + + if request.method == "POST": + data = await request.post() + try: + username = data["username"] + password = data["password"] + if config["accounts"].get(username, "") == password: + resp = aiohttp.web.HTTPFound( + location='/', + headers=request.headers + ) + resp.set_cookie("session", auth_s.dumps({"time": time.time(), "username": username}), max_age=300) + raise resp + except KeyError: + pass + return await func(*args, **kwargs) + + return wrapper + return decorator class Dashboard: def __init__(self, bot): self.bot = bot self.app = self.bot.instance.app - self.log_buffer = [] formatter = logging.Formatter( "[{asctime}] [{levelname}] {name}: {message}", datefmt="%Y-%m-%d %H:%M:%S", style="{" ) - buffer_handler = BufferHandler(self) - buffer_handler.setFormatter(formatter) + self.buffer_handler = BufferHandler() + self.buffer_handler.setFormatter(formatter) root_logger = logging.getLogger() - root_logger.addHandler(buffer_handler) + root_logger.addHandler(self.buffer_handler) del root_logger dashboard_dir = os.path.join(os.path.dirname(__file__), "templates") @@ -58,7 +134,6 @@ class Dashboard: ["/", self.dashboard], ["/log", self.log], ["/settings", self.settings], - ["/settings/dashboard", self.addon_settings("dashboard")], ] for addon in self.bot.addons: @@ -67,21 +142,27 @@ class Dashboard: for page in self.pages: endpoint, func = page self.app.router.add_get(endpoint, func) + self.app.router.add_get("/login", self.login) + self.app.router.add_post("/login", self.login) + self.app.router.add_get("/logout", self.logout) + @check_auth() @aiohttp_jinja2.template('index.html') async def dashboard(self, request: aiohttp.web.Request) -> dict: return {} + @check_auth() @aiohttp_jinja2.template('log.html') async def log(self, request: aiohttp.web.Request) -> dict: return {} + @check_auth() @aiohttp_jinja2.template('settings.html') async def settings(self, request: aiohttp.web.Request) -> dict: return {} def addon_settings(self, addon): - + @check_auth() @aiohttp_jinja2.template('addon_settings.html') async def _settings(request: aiohttp.web.Request) -> dict: c = Config(addon, self.bot.name) @@ -91,6 +172,20 @@ class Dashboard: return _settings + @set_auth() + @aiohttp_jinja2.template('login.html') + async def login(self, request: aiohttp.web.Request) -> dict: + return {} + + @check_auth() + async def logout(self, request: aiohttp.web.Request): + resp = aiohttp.web.HTTPFound( + location='/login', + headers=request.headers + ) + resp.del_cookie("session") + return resp + async def processor(self, request) -> dict: return { "bot": { @@ -99,7 +194,7 @@ class Dashboard: "addons": self.bot.addons, "config": self.bot.config, - "log": "\n".join(self.log_buffer) + "log": "\n".join(self.buffer_handler.log_buffer) } } @@ -107,4 +202,6 @@ class Dashboard: self.event_counter += 1 def setup(bot): + config.setup(__name__, bot.name) + config.save() Dashboard(bot) diff --git a/labbot/addons/dashboard/templates/base.html b/labbot/addons/dashboard/templates/base.html index 03a5532..8fa7d76 100644 --- a/labbot/addons/dashboard/templates/base.html +++ b/labbot/addons/dashboard/templates/base.html @@ -129,7 +129,7 @@ </button> <div class="navbar-nav"> <div class="nav-item text-nowrap"> - <a class="nav-link px-3" href="#">Sign out</a> + <a class="nav-link px-3" href="/logout">Sign out</a> </div> </div> </header> diff --git a/labbot/addons/dashboard/templates/login.html b/labbot/addons/dashboard/templates/login.html new file mode 100644 index 0000000..82995c4 --- /dev/null +++ b/labbot/addons/dashboard/templates/login.html @@ -0,0 +1,78 @@ +<!doctype html> +<html lang="en"> + <head> + <meta charset="utf-8"> + <meta name="viewport" content="width=device-width, initial-scale=1"> + <meta name="description" content=""> + <meta name="author" content="Mark Otto, Jacob Thornton, and Bootstrap contributors"> + <meta name="generator" content="Hugo 0.101.0"> + <title>Signin Template ยท Bootstrap v5.2</title> + + <link rel="canonical" href="https://getbootstrap.com/docs/5.2/examples/sign-in/"> + + + + + + <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-gH2yIJqKdNHPEq0n4Mqa/HGKIhSkIHeL5AyhkYV8i59U5AR6csBvApHHNl/vI1Bx" crossorigin="anonymous"> + <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.0/dist/js/bootstrap.bundle.min.js" integrity="sha384-A3rJD856KowSb7dwlZdYEkO39Gagi7vIsF0jrRAoQmDKKtQBHUuLZ9AsSv4jD4Xa" crossorigin="anonymous"></script> + + <style> + html, body { + height: 100%; + } + + body { + display: flex; + align-items: center; + padding-top: 40px; + padding-bottom: 40px; + background-color: #f5f5f5; + } + + .form-signin { + max-width: 330px; + padding: 15px; + } + + .form-signin .form-floating:focus-within { + z-index: 2; + } + + .form-signin input[type="email"] { + margin-bottom: -1px; + border-bottom-right-radius: 0; + border-bottom-left-radius: 0; + } + + .form-signin input[type="password"] { + margin-bottom: 10px; + border-top-left-radius: 0; + border-top-right-radius: 0; + } + + </style> + </head> + <body class="text-center"> + +<main class="form-signin w-100 m-auto"> + <form method="POST"> + <h1 class="h3 mb-3 fw-normal">Login - {{ bot.name }}</h1> + + <div class="form-floating"> + <input type="input" class="form-control" id="username" name="username"> + <label for="username">username</label> + </div> + <div class="form-floating"> + <input type="password" class="form-control" id="password" name="password"> + <label for="password">password</label> + </div> + + <button class="w-100 btn btn-lg btn-primary" type="submit">Sign in</button> + </form> +</main> + + + + </body> +</html> diff --git a/labbot/addons/dashboard/templates/sidebar.html b/labbot/addons/dashboard/templates/sidebar.html index f234f54..f90e999 100644 --- a/labbot/addons/dashboard/templates/sidebar.html +++ b/labbot/addons/dashboard/templates/sidebar.html @@ -26,12 +26,14 @@ </h6> <ul class="nav flex-column mb-2"> {% for addon in bot.addons %} - <li class="nav-item"> - <a class="nav-link" href="/settings/{{ addon }}"> - <span data-feather="file-plus" class="align-text-bottom"></span> - {{ addon }} - </a> - </li> + {% if addon != "dashboard" %} + <li class="nav-item"> + <a class="nav-link" href="/settings/{{ addon }}"> + <span data-feather="file-plus" class="align-text-bottom"></span> + {{ addon }} + </a> + </li> + {% endif %} {% endfor %} </ul> @@ -54,16 +56,7 @@ <script> function getHealth() { - var href = window.location.href; - var osi = -2; - var nsi = -1; - - while (nsi > osi) - { - osi = nsi; - nsi = href.indexOf("/", nsi+1); - } - href = href.slice(0, osi+1) + "health" + var href = window.location.origin + "/health" var xhttp = new XMLHttpRequest(); xhttp.onload = function() { diff --git a/labbot/config.py b/labbot/config.py index 3574760..993d25a 100644 --- a/labbot/config.py +++ b/labbot/config.py @@ -42,9 +42,10 @@ class Config: # write the hardcoded config data ontop of the loaded data self.settings["GLOBAL"].update(global_data) - repo = self.settings.pop("REPO") - if repo: - self.settings["PROJECT"] = repo + try: + self.settings["PROJECT"] = self.settings.pop("REPO") + except KeyError: + pass except (IOError, ValueError): pass diff --git a/requirements.txt b/requirements.txt index 8f10d20..d639b6c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -5,3 +5,4 @@ click # dashboard jinja2 aiohttp_jinja2 +itsdangerous |