Web panel improvements.

- Don't return a redirect to the login page if an authentication failed
  over a JSON endpoint - instead, return a JSON payload with the error.

- Added support for additional fonts.

- Re-designed the login/registration page.

- Updated caniuse database.
This commit is contained in:
Fabio Manganiello 2022-10-07 02:24:29 +02:00
parent c0ffea681f
commit b9b7404230
Signed by: blacklight
GPG key ID: D90FBA7F76362774
11 changed files with 102 additions and 39 deletions

View file

@ -1,6 +1,7 @@
import json
from flask import Blueprint, abort, request, Response
from flask import Blueprint, abort, request
from flask.wrappers import Response
from platypush.backend.http.app import template_folder
from platypush.backend.http.app.utils import authenticate, logger, send_message
@ -14,8 +15,8 @@ __routes__ = [
@execute.route('/execute', methods=['POST'])
@authenticate()
def execute():
@authenticate(json=True)
def execute_route():
"""Endpoint to execute commands"""
try:
msg = json.loads(request.data.decode('utf-8'))

View file

@ -8,22 +8,27 @@ from platypush.backend.http.app import template_folder
img_folder = os.path.join(template_folder, 'img')
fonts_folder = os.path.join(template_folder, 'fonts')
icons_folder = os.path.join(template_folder, 'icons')
resources = Blueprint('resources', __name__, template_folder=template_folder)
favicon = Blueprint('favicon', __name__, template_folder=template_folder)
img = Blueprint('img', __name__, template_folder=template_folder)
icons = Blueprint('icons', __name__, template_folder=template_folder)
fonts = Blueprint('fonts', __name__, template_folder=template_folder)
# Declare routes list
__routes__ = [
resources,
favicon,
img,
icons,
fonts,
]
@resources.route('/resources/<path:path>', methods=['GET'])
def resources_path(path):
""" Custom static resources """
"""Custom static resources"""
path_tokens = path.split('/')
http_conf = Config.get('backend.http')
resource_dirs = http_conf.get('resource_dirs', {})
@ -42,9 +47,11 @@ def resources_path(path):
real_path = real_base_path
file_path = [
s for s in re.sub(
s
for s in re.sub(
r'^{}(.*)$'.format(base_path), '\\1', path # lgtm [py/regex-injection]
).split('/') if s
).split('/')
if s
]
for p in file_path[:-1]:
@ -61,20 +68,26 @@ def resources_path(path):
@favicon.route('/favicon.ico', methods=['GET'])
def serve_favicon():
""" favicon.ico icon """
"""favicon.ico icon"""
return send_from_directory(template_folder, 'favicon.ico')
@img.route('/img/<path:path>', methods=['GET'])
def imgpath(path):
""" Default static images """
"""Default static images"""
return send_from_directory(img_folder, path)
@img.route('/icons/<path:path>', methods=['GET'])
@icons.route('/icons/<path:path>', methods=['GET'])
def iconpath(path):
""" Default static icons """
"""Default static icons"""
return send_from_directory(icons_folder, path)
@fonts.route('/fonts/<path:path>', methods=['GET'])
def fontpath(path):
"""Default fonts"""
return send_from_directory(fonts_folder, path)
# vim:sw=4:ts=4:et:

View file

@ -3,7 +3,8 @@ import logging
import os
from functools import wraps
from flask import abort, request, redirect, Response, current_app
from flask import abort, request, redirect, jsonify, current_app
from flask.wrappers import Response
from redis import Redis
# NOTE: The HTTP service will *only* work on top of a Redis bus. The default
@ -184,7 +185,25 @@ def _authenticate_csrf_token():
)
def authenticate(redirect_page='', skip_auth_methods=None, check_csrf_token=False):
def authenticate(
redirect_page='',
skip_auth_methods=None,
check_csrf_token=False,
json=False,
):
def on_auth_fail():
if json:
return (
jsonify(
{
'message': 'Not logged in',
}
),
401,
)
return redirect('/login?redirect=' + (redirect_page or request.url), 307)
def decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
@ -213,9 +232,7 @@ def authenticate(redirect_page='', skip_auth_methods=None, check_csrf_token=Fals
if session_auth_ok:
return f(*args, **kwargs)
return redirect(
'/login?redirect=' + (redirect_page or request.url), 307
)
return on_auth_fail()
# CSRF token check
if check_csrf_token:
@ -224,9 +241,7 @@ def authenticate(redirect_page='', skip_auth_methods=None, check_csrf_token=Fals
return abort(403, 'Invalid or missing csrf_token')
if n_users == 0 and 'session' not in skip_methods:
return redirect(
'/register?redirect=' + (redirect_page or request.url), 307
)
return on_auth_fail()
if (
('http' not in skip_methods and http_auth_ok)

View file

@ -3705,9 +3705,9 @@
}
},
"node_modules/caniuse-lite": {
"version": "1.0.30001320",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz",
"integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==",
"version": "1.0.30001416",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001416.tgz",
"integrity": "sha512-06wzzdAkCPZO+Qm4e/eNghZBDfVNDsCgw33T27OwBH9unE9S478OYw//Q2L7Npf/zBzs7rjZOszIFQkwQKAEqA==",
"funding": [
{
"type": "opencollective",
@ -14544,9 +14544,9 @@
}
},
"caniuse-lite": {
"version": "1.0.30001320",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz",
"integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA=="
"version": "1.0.30001416",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001416.tgz",
"integrity": "sha512-06wzzdAkCPZO+Qm4e/eNghZBDfVNDsCgw33T27OwBH9unE9S478OYw//Q2L7Npf/zBzs7rjZOszIFQkwQKAEqA=="
},
"case-sensitive-paths-webpack-plugin": {
"version": "2.4.0",

Binary file not shown.

View file

@ -0,0 +1,7 @@
@font-face {
font-family: 'Poppins';
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(./Poppins.ttf) format('truetype');
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View file

@ -5,6 +5,7 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="stylesheet" href="/fonts/poppins.css">
<title><%= htmlWebpackPlugin.options.title %></title>
</head>
<body>

View file

@ -59,7 +59,7 @@ export default {
},
async initConfig() {
this.config = await this.request('config.get')
this.config = await this.request('config.get', {}, 60000, false)
this.userAuthenticated = true
},
},

View file

@ -4,7 +4,7 @@ import axios from 'axios'
export default {
name: "Api",
methods: {
execute(request, timeout=60000) {
execute(request, timeout=60000, showError=true) {
const opts = {};
if (!('target' in request) || !request['target']) {
@ -36,6 +36,7 @@ export default {
}
})
.catch((error) => {
if (showError)
this.notify({
text: error,
error: true,
@ -46,12 +47,12 @@ export default {
})
},
request(action, args={}, timeout=60000) {
request(action, args={}, timeout=60000, showError=true) {
return this.execute({
type: 'request',
action: action,
args: args,
}, timeout);
}, timeout, showError);
}
},
}

View file

@ -1,8 +1,9 @@
<template>
<div class="login-container">
<form class="login" method="POST">
<div class="description">
{{ _register ? 'Welcome' : 'Authenticate' }} to platypush
<div class="header">
<span class="logo" />
<span class="text">Platypush</span>
</div>
<div class="row">
@ -23,7 +24,7 @@
</label>
</div>
<div class="row pull-right">
<div class="row buttons">
<input type="submit" class="btn btn-primary" :value="_register ? 'Register' : 'Login'">
</div>
@ -60,7 +61,7 @@ export default {
}
</script>
<style lang="scss">
<style lang="scss" scoped>
body {
width: 100vw;
height: 100vh;
@ -75,9 +76,25 @@ body {
background: $default-bg-6;
}
.description {
.header {
font-size: 1.2em;
margin-bottom: 2em;
text-align: center;
display: flex;
justify-content: center;
align-items: center;
.logo {
width: 3em;
height: 3em;
display: inline-flex;
background-image: url('@/assets/img/logo.png');
background-size: cover;
}
.text {
font-family: Poppins, sans-serif;
margin-left: .5em;
}
}
form {
@ -111,6 +128,14 @@ form {
display: flex;
font-size: 0.8em;
}
.buttons {
text-align: center;
input[type=submit] {
padding: .5em .75em;
}
}
}
a {