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 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 import template_folder
from platypush.backend.http.app.utils import authenticate, logger, send_message from platypush.backend.http.app.utils import authenticate, logger, send_message
@ -14,8 +15,8 @@ __routes__ = [
@execute.route('/execute', methods=['POST']) @execute.route('/execute', methods=['POST'])
@authenticate() @authenticate(json=True)
def execute(): def execute_route():
"""Endpoint to execute commands""" """Endpoint to execute commands"""
try: try:
msg = json.loads(request.data.decode('utf-8')) 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') img_folder = os.path.join(template_folder, 'img')
fonts_folder = os.path.join(template_folder, 'fonts')
icons_folder = os.path.join(template_folder, 'icons') icons_folder = os.path.join(template_folder, 'icons')
resources = Blueprint('resources', __name__, template_folder=template_folder) resources = Blueprint('resources', __name__, template_folder=template_folder)
favicon = Blueprint('favicon', __name__, template_folder=template_folder) favicon = Blueprint('favicon', __name__, template_folder=template_folder)
img = Blueprint('img', __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 # Declare routes list
__routes__ = [ __routes__ = [
resources, resources,
favicon, favicon,
img, img,
icons,
fonts,
] ]
@resources.route('/resources/<path:path>', methods=['GET']) @resources.route('/resources/<path:path>', methods=['GET'])
def resources_path(path): def resources_path(path):
""" Custom static resources """ """Custom static resources"""
path_tokens = path.split('/') path_tokens = path.split('/')
http_conf = Config.get('backend.http') http_conf = Config.get('backend.http')
resource_dirs = http_conf.get('resource_dirs', {}) resource_dirs = http_conf.get('resource_dirs', {})
@ -42,9 +47,11 @@ def resources_path(path):
real_path = real_base_path real_path = real_base_path
file_path = [ file_path = [
s for s in re.sub( s
r'^{}(.*)$'.format(base_path), '\\1', path # lgtm [py/regex-injection] for s in re.sub(
).split('/') if s r'^{}(.*)$'.format(base_path), '\\1', path # lgtm [py/regex-injection]
).split('/')
if s
] ]
for p in file_path[:-1]: for p in file_path[:-1]:
@ -61,20 +68,26 @@ def resources_path(path):
@favicon.route('/favicon.ico', methods=['GET']) @favicon.route('/favicon.ico', methods=['GET'])
def serve_favicon(): def serve_favicon():
""" favicon.ico icon """ """favicon.ico icon"""
return send_from_directory(template_folder, 'favicon.ico') return send_from_directory(template_folder, 'favicon.ico')
@img.route('/img/<path:path>', methods=['GET']) @img.route('/img/<path:path>', methods=['GET'])
def imgpath(path): def imgpath(path):
""" Default static images """ """Default static images"""
return send_from_directory(img_folder, path) return send_from_directory(img_folder, path)
@img.route('/icons/<path:path>', methods=['GET']) @icons.route('/icons/<path:path>', methods=['GET'])
def iconpath(path): def iconpath(path):
""" Default static icons """ """Default static icons"""
return send_from_directory(icons_folder, path) 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: # vim:sw=4:ts=4:et:

View File

@ -3,7 +3,8 @@ import logging
import os import os
from functools import wraps 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 from redis import Redis
# NOTE: The HTTP service will *only* work on top of a Redis bus. The default # 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): def decorator(f):
@wraps(f) @wraps(f)
def wrapper(*args, **kwargs): def wrapper(*args, **kwargs):
@ -213,9 +232,7 @@ def authenticate(redirect_page='', skip_auth_methods=None, check_csrf_token=Fals
if session_auth_ok: if session_auth_ok:
return f(*args, **kwargs) return f(*args, **kwargs)
return redirect( return on_auth_fail()
'/login?redirect=' + (redirect_page or request.url), 307
)
# CSRF token check # CSRF token check
if check_csrf_token: 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') return abort(403, 'Invalid or missing csrf_token')
if n_users == 0 and 'session' not in skip_methods: if n_users == 0 and 'session' not in skip_methods:
return redirect( return on_auth_fail()
'/register?redirect=' + (redirect_page or request.url), 307
)
if ( if (
('http' not in skip_methods and http_auth_ok) ('http' not in skip_methods and http_auth_ok)

View File

@ -3705,9 +3705,9 @@
} }
}, },
"node_modules/caniuse-lite": { "node_modules/caniuse-lite": {
"version": "1.0.30001320", "version": "1.0.30001416",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001416.tgz",
"integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==", "integrity": "sha512-06wzzdAkCPZO+Qm4e/eNghZBDfVNDsCgw33T27OwBH9unE9S478OYw//Q2L7Npf/zBzs7rjZOszIFQkwQKAEqA==",
"funding": [ "funding": [
{ {
"type": "opencollective", "type": "opencollective",
@ -14544,9 +14544,9 @@
} }
}, },
"caniuse-lite": { "caniuse-lite": {
"version": "1.0.30001320", "version": "1.0.30001416",
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001320.tgz", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001416.tgz",
"integrity": "sha512-MWPzG54AGdo3nWx7zHZTefseM5Y1ccM7hlQKHRqJkPozUaw3hNbBTMmLn16GG2FUzjR13Cr3NPfhIieX5PzXDA==" "integrity": "sha512-06wzzdAkCPZO+Qm4e/eNghZBDfVNDsCgw33T27OwBH9unE9S478OYw//Q2L7Npf/zBzs7rjZOszIFQkwQKAEqA=="
}, },
"case-sensitive-paths-webpack-plugin": { "case-sensitive-paths-webpack-plugin": {
"version": "2.4.0", "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 http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width,initial-scale=1.0"> <meta name="viewport" content="width=device-width,initial-scale=1.0">
<link rel="icon" href="<%= BASE_URL %>favicon.ico"> <link rel="icon" href="<%= BASE_URL %>favicon.ico">
<link rel="stylesheet" href="/fonts/poppins.css">
<title><%= htmlWebpackPlugin.options.title %></title> <title><%= htmlWebpackPlugin.options.title %></title>
</head> </head>
<body> <body>

View File

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

View File

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

View File

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