forked from platypush/platypush
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:
parent
c0ffea681f
commit
b9b7404230
11 changed files with 102 additions and 39 deletions
|
@ -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'))
|
||||||
|
|
|
@ -8,16 +8,21 @@ 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,
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
for s in re.sub(
|
||||||
r'^{}(.*)$'.format(base_path), '\\1', path # lgtm [py/regex-injection]
|
r'^{}(.*)$'.format(base_path), '\\1', path # lgtm [py/regex-injection]
|
||||||
).split('/') if s
|
).split('/')
|
||||||
|
if s
|
||||||
]
|
]
|
||||||
|
|
||||||
for p in file_path[:-1]:
|
for p in file_path[:-1]:
|
||||||
|
@ -71,10 +78,16 @@ def imgpath(path):
|
||||||
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:
|
||||||
|
|
|
@ -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)
|
||||||
|
|
12
platypush/backend/http/webapp/package-lock.json
generated
12
platypush/backend/http/webapp/package-lock.json
generated
|
@ -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",
|
||||||
|
|
BIN
platypush/backend/http/webapp/public/fonts/Poppins.ttf
Normal file
BIN
platypush/backend/http/webapp/public/fonts/Poppins.ttf
Normal file
Binary file not shown.
7
platypush/backend/http/webapp/public/fonts/poppins.css
Normal file
7
platypush/backend/http/webapp/public/fonts/poppins.css
Normal 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');
|
||||||
|
}
|
BIN
platypush/backend/http/webapp/public/img/logo.png
Normal file
BIN
platypush/backend/http/webapp/public/img/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 20 KiB |
|
@ -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>
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -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,6 +36,7 @@ export default {
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
if (showError)
|
||||||
this.notify({
|
this.notify({
|
||||||
text: error,
|
text: error,
|
||||||
error: true,
|
error: true,
|
||||||
|
@ -46,12 +47,12 @@ export default {
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
Loading…
Reference in a new issue