aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--labbot/addons/dashboard/__init__.py121
-rw-r--r--labbot/addons/dashboard/templates/base.html2
-rw-r--r--labbot/addons/dashboard/templates/login.html78
-rw-r--r--labbot/addons/dashboard/templates/sidebar.html25
-rw-r--r--labbot/config.py7
-rw-r--r--requirements.txt1
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