Commit 6e9a9e18 authored by Daniele Venzano's avatar Daniele Venzano

Start work on improved web interface

parent ce5d0687
Pipeline #4444 failed with stages
in 56 seconds
......@@ -28,8 +28,9 @@ def web_init() -> List[tornado.web.URLSpec]:
"""Tornado init for the web interface."""
web_routes = [
tornado.web.url(r'/', zoe_api.web.start.RootWeb, name='root'),
tornado.web.url(r'/user', zoe_api.web.start.HomeWeb, name='home_user'),
tornado.web.url(r'/home', zoe_api.web.start.HomeWeb, name='home'),
tornado.web.url(r'/login', zoe_api.web.start.LoginWeb, name='login'),
tornado.web.url(r'/logout', zoe_api.web.start.LogoutWeb, name='logout'),
tornado.web.url(r'/executions/new', zoe_api.web.executions.ExecutionDefineWeb, name='execution_define'),
tornado.web.url(r'/executions/start', zoe_api.web.executions.ExecutionStartWeb, name='execution_start'),
......
......@@ -23,7 +23,7 @@ from tornado.escape import json_decode
from zoe_lib.config import get_conf
import zoe_api.exceptions
from zoe_api.api_endpoint import APIEndpoint # pylint: disable=unused-import
from zoe_api.web.utils import get_auth, catch_exceptions
from zoe_api.web.utils import catch_exceptions
from zoe_api.web.custom_request_handler import ZoeRequestHandler
......
......@@ -21,6 +21,8 @@
import json
import datetime
from typing import Union
import traceback
from jinja2 import Environment, FileSystemLoader, Markup
......@@ -29,6 +31,8 @@ import tornado.web
import zoe_lib.version
import zoe_api.web.utils
from zoe_api.api_endpoint import APIEndpoint
from zoe_lib.state import User
class JinjaApp(object):
......@@ -84,14 +88,20 @@ def tojson_filter(obj, **kwargs):
class ZoeRequestHandler(tornado.web.RequestHandler):
"""Custom Zoe Tornado handler."""
def get_current_user(self):
def get_current_user(self) -> Union[User, None]:
"""Implement cookie-auth the Tornado way."""
user_id = self.get_secure_cookie("zoeweb_user")
user_id = self.get_secure_cookie("zoe_web_user").decode('utf-8')
if not user_id:
return None
user = self.application.api_endpoint.user_get(user_id)
self.redirect("/login")
return
user = self.application.api_endpoint.user_identify(user_id)
if user is None or not user.enabled:
return None
self.clear_cookie("zoe_web_user")
if not user.enabled:
self.redirect("/login?why=disabled")
else:
self.redirect("/login")
return
return user
def initialize(self, *args_, **kwargs_):
......@@ -101,6 +111,8 @@ class ZoeRequestHandler(tornado.web.RequestHandler):
raise RuntimeError("Needs jinja2 Environment. Initialize with JinjaApp.init_app first")
else:
self._jinja_env = self.application.settings['jinja_environment']
self.api_endpoint = self.application.api_endpoint # type: APIEndpoint
assert isinstance(self.api_endpoint, APIEndpoint)
def _render(self, template, **kwargs):
""" todo: support multiple template preprocessors """
......@@ -128,7 +140,7 @@ class ZoeRequestHandler(tornado.web.RequestHandler):
try:
html = self._render(template, **kwargs)
except Exception:
zoe_api.web.utils.error_page(self, 'Jinja2 template exception', 500)
zoe_api.web.utils.error_page(self, traceback.format_exc(), 500)
return
self.finish(html)
......
......@@ -18,7 +18,7 @@
import json
import zoe_api.exceptions
from zoe_api.web.utils import get_auth, catch_exceptions
from zoe_api.web.utils import catch_exceptions
from zoe_api.api_endpoint import APIEndpoint # pylint: disable=unused-import
from zoe_api.web.custom_request_handler import ZoeRequestHandler
......
......@@ -15,77 +15,79 @@
"""Main points of entry for the Zoe web interface."""
import re
import tornado.web
from zoe_api.api_endpoint import APIEndpoint # pylint: disable=unused-import
from zoe_api.web.utils import get_auth_login, get_auth, catch_exceptions
from zoe_api.web.utils import get_auth_login, catch_exceptions
from zoe_api.web.custom_request_handler import ZoeRequestHandler
from zoe_api.exceptions import ZoeAuthException
class RootWeb(ZoeRequestHandler):
"""Handler class"""
def initialize(self, **kwargs):
"""Initializes the request handler."""
super().initialize(**kwargs)
self.api_endpoint = self.application.api_endpoint # type: APIEndpoint
@tornado.web.authenticated
def get(self):
"""Home page."""
self.render('index.html')
self.redirect('/home')
class LoginWeb(ZoeRequestHandler):
"""The login web page."""
def initialize(self, **kwargs):
"""Initializes the request handler."""
super().initialize(**kwargs)
self.api_endpoint = self.application.api_endpoint # type: APIEndpoint
@catch_exceptions
def get(self):
"""Login page."""
self.render('login.html')
why = self.get_argument('why', None)
self.render('login.html', **{'why': why})
@catch_exceptions
def post(self):
"""Try to authenticate."""
username = self.get_argument("username", "")
password = self.get_argument("password", "")
uid, role = get_auth_login(username, password)
if not re.match(r'[a-zA-Z0-9]+', username) or len(password) == 0:
self.redirect('/login?why=invalid')
try:
uid, role = get_auth_login(self.application.api_endpoint, username, password)
except ZoeAuthException as e:
self.redirect('/login?why={}'.format(e.message))
return
self.set_secure_cookie('zoe_web_user', uid)
self.redirect('/home')
if not self.get_secure_cookie('zoe'):
cookie_val = uid + '.' + role
self.set_secure_cookie('zoe', cookie_val)
self.redirect(self.get_argument("next", u"/user"))
class LogoutWeb(ZoeRequestHandler):
"""The logout web page."""
def get(self):
"""Logout and redirect to login page."""
self.clear_cookie('zoe_web_user')
self.redirect('/login')
class HomeWeb(ZoeRequestHandler):
"""Handler class"""
def initialize(self, **kwargs):
"""Initializes the request handler."""
super().initialize(**kwargs)
self.api_endpoint = kwargs['api_endpoint'] # type: APIEndpoint
@catch_exceptions
@tornado.web.authenticated
def get(self):
"""Home page with authentication."""
uid, role = get_auth(self)
if role == 'guest':
return self._aml_homepage(uid)
executions = self.api_endpoint.execution_list(uid, role)
filters = {
'user_id': self.current_user.username,
'status': 'running'
}
executions = self.api_endpoint.execution_list(self.current_user, **filters)
filters['status'] = 'starting'
executions += self.api_endpoint.execution_list(self.current_user, **filters)
filters['status'] = 'scheduled'
executions += self.api_endpoint.execution_list(self.current_user, **filters)
filters['status'] = 'terminated'
filters['limit'] = 5
executions += self.api_endpoint.execution_list(self.current_user, **filters)
template_vars = {
'executions': sorted(executions, key=lambda e: e.id),
'is_admin': role == 'admin',
'user': self.current_user
}
self.render('home_user.html', **template_vars)
def _aml_homepage(self, uid):
"""Home page for students of the AML course."""
template_vars = {
'uid': uid
}
return self.render('home_guest.html', **template_vars)
/**
* Created by venzano on 13/07/2017.
*/
/// Knockout Mapping plugin v2.4.1
/// (c) 2013 Steven Sanderson, Roy Jacobs - http://knockoutjs.com/
/// License: MIT (http://www.opensource.org/licenses/mit-license.php)
(function(e){"function"===typeof require&&"object"===typeof exports&&"object"===typeof module?e(require("knockout"),exports):"function"===typeof define&&define.amd?define(["knockout","exports"],e):e(ko,ko.mapping={})})(function(e,f){function y(b,c){var a,d;for(d in c)if(c.hasOwnProperty(d)&&c[d])if(a=f.getType(b[d]),d&&b[d]&&"array"!==a&&"string"!==a)y(b[d],c[d]);else if("array"===f.getType(b[d])&&"array"===f.getType(c[d])){a=b;for(var e=d,l=b[d],n=c[d],t={},g=l.length-1;0<=g;--g)t[l[g]]=l[g];for(g=
n.length-1;0<=g;--g)t[n[g]]=n[g];l=[];n=void 0;for(n in t)l.push(t[n]);a[e]=l}else b[d]=c[d]}function E(b,c){var a={};y(a,b);y(a,c);return a}function z(b,c){for(var a=E({},b),e=L.length-1;0<=e;e--){var f=L[e];a[f]&&(a[""]instanceof Object||(a[""]={}),a[""][f]=a[f],delete a[f])}c&&(a.ignore=h(c.ignore,a.ignore),a.include=h(c.include,a.include),a.copy=h(c.copy,a.copy),a.observe=h(c.observe,a.observe));a.ignore=h(a.ignore,j.ignore);a.include=h(a.include,j.include);a.copy=h(a.copy,j.copy);a.observe=h(a.observe,
j.observe);a.mappedProperties=a.mappedProperties||{};a.copiedProperties=a.copiedProperties||{};return a}function h(b,c){"array"!==f.getType(b)&&(b="undefined"===f.getType(b)?[]:[b]);"array"!==f.getType(c)&&(c="undefined"===f.getType(c)?[]:[c]);return e.utils.arrayGetDistinctValues(b.concat(c))}function F(b,c,a,d,k,l,n){var t="array"===f.getType(e.utils.unwrapObservable(c));l=l||"";if(f.isMapped(b)){var g=e.utils.unwrapObservable(b)[p];a=E(g,a)}var j=n||k,h=function(){return a[d]&&a[d].create instanceof
Function},x=function(b){var f=G,g=e.dependentObservable;e.dependentObservable=function(a,b,c){c=c||{};a&&"object"==typeof a&&(c=a);var d=c.deferEvaluation,M=!1;c.deferEvaluation=!0;a=new H(a,b,c);if(!d){var g=a,d=e.dependentObservable;e.dependentObservable=H;a=e.isWriteableObservable(g);e.dependentObservable=d;d=H({read:function(){M||(e.utils.arrayRemoveItem(f,g),M=!0);return g.apply(g,arguments)},write:a&&function(a){return g(a)},deferEvaluation:!0});d.__DO=g;a=d;f.push(a)}return a};e.dependentObservable.fn=
H.fn;e.computed=e.dependentObservable;b=e.utils.unwrapObservable(k)instanceof Array?a[d].create({data:b||c,parent:j,skip:N}):a[d].create({data:b||c,parent:j});e.dependentObservable=g;e.computed=e.dependentObservable;return b},u=function(){return a[d]&&a[d].update instanceof Function},v=function(b,f){var g={data:f||c,parent:j,target:e.utils.unwrapObservable(b)};e.isWriteableObservable(b)&&(g.observable=b);return a[d].update(g)};if(n=I.get(c))return n;d=d||"";if(t){var t=[],s=!1,m=function(a){return a};
a[d]&&a[d].key&&(m=a[d].key,s=!0);e.isObservable(b)||(b=e.observableArray([]),b.mappedRemove=function(a){var c="function"==typeof a?a:function(b){return b===m(a)};return b.remove(function(a){return c(m(a))})},b.mappedRemoveAll=function(a){var c=C(a,m);return b.remove(function(a){return-1!=e.utils.arrayIndexOf(c,m(a))})},b.mappedDestroy=function(a){var c="function"==typeof a?a:function(b){return b===m(a)};return b.destroy(function(a){return c(m(a))})},b.mappedDestroyAll=function(a){var c=C(a,m);return b.destroy(function(a){return-1!=
e.utils.arrayIndexOf(c,m(a))})},b.mappedIndexOf=function(a){var c=C(b(),m);a=m(a);return e.utils.arrayIndexOf(c,a)},b.mappedGet=function(a){return b()[b.mappedIndexOf(a)]},b.mappedCreate=function(a){if(-1!==b.mappedIndexOf(a))throw Error("There already is an object with the key that you specified.");var c=h()?x(a):a;u()&&(a=v(c,a),e.isWriteableObservable(c)?c(a):c=a);b.push(c);return c});n=C(e.utils.unwrapObservable(b),m).sort();g=C(c,m);s&&g.sort();s=e.utils.compareArrays(n,g);n={};var J,A=e.utils.unwrapObservable(c),
y={},z=!0,g=0;for(J=A.length;g<J;g++){var r=m(A[g]);if(void 0===r||r instanceof Object){z=!1;break}y[r]=A[g]}var A=[],B=0,g=0;for(J=s.length;g<J;g++){var r=s[g],q,w=l+"["+g+"]";switch(r.status){case "added":var D=z?y[r.value]:K(e.utils.unwrapObservable(c),r.value,m);q=F(void 0,D,a,d,b,w,k);h()||(q=e.utils.unwrapObservable(q));w=O(e.utils.unwrapObservable(c),D,n);q===N?B++:A[w-B]=q;n[w]=!0;break;case "retained":D=z?y[r.value]:K(e.utils.unwrapObservable(c),r.value,m);q=K(b,r.value,m);F(q,D,a,d,b,w,
k);w=O(e.utils.unwrapObservable(c),D,n);A[w]=q;n[w]=!0;break;case "deleted":q=K(b,r.value,m)}t.push({event:r.status,item:q})}b(A);a[d]&&a[d].arrayChanged&&e.utils.arrayForEach(t,function(b){a[d].arrayChanged(b.event,b.item)})}else if(P(c)){b=e.utils.unwrapObservable(b);if(!b){if(h())return s=x(),u()&&(s=v(s)),s;if(u())return v(s);b={}}u()&&(b=v(b));I.save(c,b);if(u())return b;Q(c,function(d){var f=l.length?l+"."+d:d;if(-1==e.utils.arrayIndexOf(a.ignore,f))if(-1!=e.utils.arrayIndexOf(a.copy,f))b[d]=
c[d];else if("object"!=typeof c[d]&&"array"!=typeof c[d]&&0<a.observe.length&&-1==e.utils.arrayIndexOf(a.observe,f))b[d]=c[d],a.copiedProperties[f]=!0;else{var g=I.get(c[d]),k=F(b[d],c[d],a,d,b,f,b),g=g||k;if(0<a.observe.length&&-1==e.utils.arrayIndexOf(a.observe,f))b[d]=g(),a.copiedProperties[f]=!0;else{if(e.isWriteableObservable(b[d])){if(g=e.utils.unwrapObservable(g),b[d]()!==g)b[d](g)}else g=void 0===b[d]?g:e.utils.unwrapObservable(g),b[d]=g;a.mappedProperties[f]=!0}}})}else switch(f.getType(c)){case "function":u()?
e.isWriteableObservable(c)?(c(v(c)),b=c):b=v(c):b=c;break;default:if(e.isWriteableObservable(b))return q=u()?v(b):e.utils.unwrapObservable(c),b(q),q;h()||u();b=h()?x():e.observable(e.utils.unwrapObservable(c));u()&&b(v(b))}return b}function O(b,c,a){for(var d=0,e=b.length;d<e;d++)if(!0!==a[d]&&b[d]===c)return d;return null}function R(b,c){var a;c&&(a=c(b));"undefined"===f.getType(a)&&(a=b);return e.utils.unwrapObservable(a)}function K(b,c,a){b=e.utils.unwrapObservable(b);for(var d=0,f=b.length;d<
f;d++){var l=b[d];if(R(l,a)===c)return l}throw Error("When calling ko.update*, the key '"+c+"' was not found!");}function C(b,c){return e.utils.arrayMap(e.utils.unwrapObservable(b),function(a){return c?R(a,c):a})}function Q(b,c){if("array"===f.getType(b))for(var a=0;a<b.length;a++)c(a);else for(a in b)c(a)}function P(b){var c=f.getType(b);return("object"===c||"array"===c)&&null!==b}function T(){var b=[],c=[];this.save=function(a,d){var f=e.utils.arrayIndexOf(b,a);0<=f?c[f]=d:(b.push(a),c.push(d))};
this.get=function(a){a=e.utils.arrayIndexOf(b,a);return 0<=a?c[a]:void 0}}function S(){var b={},c=function(a){var c;try{c=a}catch(e){c="$$$"}a=b[c];void 0===a&&(a=new T,b[c]=a);return a};this.save=function(a,b){c(a).save(a,b)};this.get=function(a){return c(a).get(a)}}var p="__ko_mapping__",H=e.dependentObservable,B=0,G,I,L=["create","update","key","arrayChanged"],N={},x={include:["_destroy"],ignore:[],copy:[],observe:[]},j=x;f.isMapped=function(b){return(b=e.utils.unwrapObservable(b))&&b[p]};f.fromJS=
function(b){if(0==arguments.length)throw Error("When calling ko.fromJS, pass the object you want to convert.");try{B++||(G=[],I=new S);var c,a;2==arguments.length&&(arguments[1][p]?a=arguments[1]:c=arguments[1]);3==arguments.length&&(c=arguments[1],a=arguments[2]);a&&(c=E(c,a[p]));c=z(c);var d=F(a,b,c);a&&(d=a);if(!--B)for(;G.length;){var e=G.pop();e&&(e(),e.__DO.throttleEvaluation=e.throttleEvaluation)}d[p]=E(d[p],c);return d}catch(f){throw B=0,f;}};f.fromJSON=function(b){var c=e.utils.parseJson(b);
arguments[0]=c;return f.fromJS.apply(this,arguments)};f.updateFromJS=function(){throw Error("ko.mapping.updateFromJS, use ko.mapping.fromJS instead. Please note that the order of parameters is different!");};f.updateFromJSON=function(){throw Error("ko.mapping.updateFromJSON, use ko.mapping.fromJSON instead. Please note that the order of parameters is different!");};f.toJS=function(b,c){j||f.resetDefaultOptions();if(0==arguments.length)throw Error("When calling ko.mapping.toJS, pass the object you want to convert.");
if("array"!==f.getType(j.ignore))throw Error("ko.mapping.defaultOptions().ignore should be an array.");if("array"!==f.getType(j.include))throw Error("ko.mapping.defaultOptions().include should be an array.");if("array"!==f.getType(j.copy))throw Error("ko.mapping.defaultOptions().copy should be an array.");c=z(c,b[p]);return f.visitModel(b,function(a){return e.utils.unwrapObservable(a)},c)};f.toJSON=function(b,c){var a=f.toJS(b,c);return e.utils.stringifyJson(a)};f.defaultOptions=function(){if(0<arguments.length)j=
arguments[0];else return j};f.resetDefaultOptions=function(){j={include:x.include.slice(0),ignore:x.ignore.slice(0),copy:x.copy.slice(0)}};f.getType=function(b){if(b&&"object"===typeof b){if(b.constructor===Date)return"date";if(b.constructor===Array)return"array"}return typeof b};f.visitModel=function(b,c,a){a=a||{};a.visitedObjects=a.visitedObjects||new S;var d,k=e.utils.unwrapObservable(b);if(P(k))a=z(a,k[p]),c(b,a.parentName),d="array"===f.getType(k)?[]:{};else return c(b,a.parentName);a.visitedObjects.save(b,
d);var l=a.parentName;Q(k,function(b){if(!(a.ignore&&-1!=e.utils.arrayIndexOf(a.ignore,b))){var j=k[b],g=a,h=l||"";"array"===f.getType(k)?l&&(h+="["+b+"]"):(l&&(h+="."),h+=b);g.parentName=h;if(!(-1===e.utils.arrayIndexOf(a.copy,b)&&-1===e.utils.arrayIndexOf(a.include,b)&&k[p]&&k[p].mappedProperties&&!k[p].mappedProperties[b]&&k[p].copiedProperties&&!k[p].copiedProperties[b]&&"array"!==f.getType(k)))switch(f.getType(e.utils.unwrapObservable(j))){case "object":case "array":case "undefined":g=a.visitedObjects.get(j);
d[b]="undefined"!==f.getType(g)?g:f.visitModel(j,c,a);break;default:d[b]=c(j,a.parentName)}}});return d}});
body {
font-family: sans-serif;
max-width: 90%;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
margin-left: 20px;
}
a:link {
color: rgba(79, 140, 30, 1);
color: #00a9e1;
text-decoration: none;
}
a:visited {
color: rgba(79, 140, 30, 1);
color: #00a9e1;
text-decoration: none;
}
div.user_info {
font-size: smaller;
clear: both;
padding-top: 20px;
/* Header on top of almost all pages */
div.top-header {
top: 0;
left: 0;
width: 100%;
height: 5em;
background-color: white;
border-bottom: solid 1px black;
}
table.app_list {
border-collapse: collapse;
}
table.app_list tr {
border-top: 1px black solid;
}
table.app_list td {
padding-right: 1.5em;
}
div.status_line {
div.top-header img.logo {
height: 100%;
display: block;
float: left;
}
span#app_name {
font-family: cursive;
font-size: larger;
}
span#status {
font-size: smaller;
}
div.copyright {
div.top-header ul.header-menu {
float: left;
padding-left: 3em;
}
textarea#log {
width: 90%;
height: 40em;
}
span.fakelink {
color: rgba(79, 140, 30, 1);
text-decoration: none;
cursor: pointer;
}
#wrapper {
width: 800px;
list-style: none;
margin: 0;
height: 5em;
}
#navigation {
background-color: #fff;
border: #ddd 1px solid;
border-radius: 10px;
margin: 10px;
padding: 10px;
div.top-header ul.header-menu li {
float: left;
padding-left: 0.5em;
padding-right: 0.5em;
display: flex;
align-items: center;
height: 100%;
border: 1px solid transparent;
transition: border-color ease-in-out 0.15s;
font-size: 1.3em;
}
#navigation li {
margin: 2px 0;
div.top-header ul.header-menu li:hover {
color: #353390;
}
label.error {
color: #ff0000;
margin-left: 10px;
position: relative;
div.top-header span.current-user {
float: right;
display: flex;
align-items: center;
height: 100%;
}
.wizard {
background-color: #fff;
border: #ddd 1px solid;
border-radius: 10px;
margin: 10px;
padding: 10px;
/* Main content of the page */
#content {
width: 90%;
}
.wizard .wizard-header {
background-color: #f4f4f4;
border-bottom: #ddd 1px solid;
border-top-left-radius: 10px;
border-top-right-radius: 10px;
padding: 5px 10px;
margin: 0 0 10px 0;
/* Login form */
div.login-form {
position: absolute;
top: 80px;
left: 50%;
margin: 0 0 0 -195px;
width: 390px;
border: 1px solid #ddd;
max-height: none;
border-radius: 6px;
box-shadow: 0 3px 7px rgba(0, 0, 0, 0.3);
background-clip: border-box;
}
.wizard .wizard-step {
margin: 10px 0;
div.login-form img.logo {
width: 95%;
margin: 0 auto;
display: block;
}
.wizard .wizard-step p {
padding: 5px;
div.login-form input {
width: 90%;
transition: border-color ease-in-out 0.15s, box-shadow ease-in-out 0.15s;
border: 1px solid #ccc;
border-radius: 4px;
display: block;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075);
padding: 6px 12px;
}
.navigation {
border-top: #ddd 1px solid;
margin-top: 10px;
padding-top: 10px;
div.login-form input:focus {
border-color: #33c0c4;
outline: 0;
box-shadow: inset 0 1px 1px rgba(0, 0, 0, 0.075), 0 0 8px rgba(51, 192, 196, 0.6);
}
.navigation ul {
margin: 0;
padding: 0;
list-style: none;
div.login-form label {
margin-bottom: 5px;
text-align: left;
color: #555555;
font-weight: bold;
display: inline-block;
}
.navigation li {
float: left;
margin-right: 10px;
div.login-form div.form-btn {
float: right;
}
.clearfix:before, .clearfix:after {
content: "\0020";
display: block;
height: 0;
visibility: hidden;
div.login-form fieldset {
border: 0;
}
.clearfix:after {
clear: both;
div.login-form button {
white-space: nowrap;
padding: 6px 12px;
border-radius: 4px;
margin-bottom: 8px;
margin-right: 8px;
border: 1px solid transparent;
}
input {
margin-top: 5px;
div.login-form button:hover {
color: #fff;
background-color: #33c0c4;
border-color: #00a9e1;
}
section {
padding-bottom: 10px;
div.login-form span.error-message {
color: red;
}
/**
* Created by venzano on 13/07/2017.
*/
function get_executions() {
}
var data = get_executions();
var viewModel = ko.mapping.fromJS(data);
function updateViewModel()
{
var data = get_executions();
ko.mapping.fromJS(data, viewModel);
}
var exec_update_interval = setInterval(updateViewModel, 5000);
{% extends "base.html" %}
{% block header %}
<div class="top-header">
<img class="logo" alt="Zoe Analytics logo" src="{{ static_url("img/logo.png") }}">
<ul class="header-menu">
<li><a href="/zapps">ZApps</a></li>
<li><a href="/executions">Executions</a></li>
<li><a href="https://github.com/DistributedSystemsGroup/zoe/issues">Issue tracker</a></li>
<li><a href="{{ reverse_url("logout") }}">Logout</a></li>
</ul>
<span class="current-user">Logged in as: {{ user.username }} ({{ user.role }})</span>
</div>
{% endblock %}
{% block footer %}
<p><a href="{{ reverse_url("home_user") }}">Home</a></p>
{{ super() }}
<div id="footer">
<div class="copyright">
<h6><span id="app_name"><a href="http://zoe-analytics.eu">Zoe</a> v. {{ zoe_version }}</span>
&copy; Copyright 2017 by <a href="http://distsysgroup.wordpress.com/">DSG</a></h6>
</div>
</div>
{% endblock %}
......@@ -4,6 +4,6 @@
<h1 style="color: red">Error!</h1>
<p>{{ error }}</p>
<pre>{{ error }}</pre>
{% endblock %}
{% extends "base.html" %}
{% extends "base_user.html" %}
{% block title %}Home{% endblock %}
{% block content %}
<div id="my_executions">
<h3>Executions</h3>
<p><a href="{{ reverse_url('execution_define') }}">New execution</a></p>
<table id="exec_list" class="app_list sortable">
<thead>
<tr>
......
{% extends "base.html" %}
{% block title %}Start{% endblock %}
{% block content %}
<p>Welcome to Zoe</p>
<ul>
<li><a href="{{ reverse_url('home_user') }}">User start page</a></li>
</ul>
{% endblock %}
<div id="main-container">
<div id="main">
<h1>
<img alt="zoe dummy login page" src="{{ static_url("img/logo.png") }}">
</h1>
<div id="login-form">
<form action="/login" method="post" id="login_form">
<fieldset>
<label for="username">Username</label>
<input autocapitalize="off" autocorrect="off" class="text-input" id="username" name="username" tabindex="1" type="text" value="">
</fieldset>
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block content %}
<div id="main-container">
<div id="main">
<div class="login-form">
<img alt="Zoe Analytics logo" src="{{ static_url("img/logo.png") }}" class="logo">
<form action="/login" method="post" id="login_form">
<fieldset>
<label for="username">User name</label>
<input class="text-input" autofocus="autofocus" id="username" name="username" tabindex="1" type="text" value="">
</fieldset>
<fieldset>
<label for="password">Password</label>
<input class="text-input" id="password" name="password" tabindex="2" type="password" value="">
</fieldset>
<fieldset>
<span class="errormessage">{{errormessage}}</span>
</fieldset>
<fieldset>
<label for="password">Password</label>
<input class="text-input" id="password" name="password" tabindex="2" type="password" value="">
</fieldset>
{% if why %}
<fieldset>
{% if why == "disabled" %}
<span class="error-message">User disabled</span>
{% endif %}
{% if why == "invalid" %}
<span class="error-message">Invalid user name or password</span>
{% endif %}
<div id="form_btn">
<input id="signin-btn" class="btn btn-blue" type="submit" value="Sign In" tabindex="3">
</div>
</form>
</div>
</fieldset>
{% endif %}
<div class="form-btn">
<button id="signin-btn" class="btn btn-blue" type="submit" tabindex="3">Sign in</button>
</div>
</form>
</div>
</div>
</div>
{% endblock %}
......@@ -40,8 +40,6 @@ def catch_exceptions(func):
self = args[0]
try:
return func(*args, **kwargs)
except zoe_api.exceptions.ZoeAuthException:
return missing_auth(self)
except zoe_api.exceptions.ZoeNotFoundException as e:
return error_page(self, str(e), 404)
except zoe_api.exceptions.ZoeException as e:
......@@ -53,48 +51,39 @@ def catch_exceptions(func):
return func_wrapper
def missing_auth(handler: ZoeRequestHandler):
"""Redirect to login page."""
handler.redirect(handler.get_argument('next', u'/login'))
def get_auth_login(username, password):
def get_auth_login(api_endpoint, username, password):
"""Authenticate username and password against the configured user store."""
# First of all try to authenticate against a fixed list of users in a text file
authenticator = PlainTextAuthenticator() # type: BaseAuthenticator
try:
uid, role = authenticator.auth(username, password)
return uid, role
except zoe_api.exceptions.ZoeAuthException:
pass
# It it fails, continue with the normal authentication
if get_conf().auth_type == 'ldap':
authenticator = LDAPAuthenticator() # type: BaseAuthenticator
elif get_conf().auth_type == 'ldapsasl':
authenticator = LDAPSASLAuthenticator() # type: BaseAuthenticator
else:
raise zoe_api.exceptions.ZoeException('Configuration error, unknown authentication method: {}'.format(get_conf().auth_type))
uid, role = authenticator.auth(username, password)
if uid is None:
raise zoe_api.exceptions.ZoeAuthException
# It it fails, continue with the normal authentication
if get_conf().auth_type == 'ldap':
authenticator = LDAPAuthenticator() # type: BaseAuthenticator
elif get_conf().auth_type == 'ldapsasl':
authenticator = LDAPSASLAuthenticator() # type: BaseAuthenticator
else:
raise zoe_api.exceptions.ZoeException('Configuration error, unknown authentication method: {}'.format(get_conf().auth_type))
uid, role = authenticator.auth(username, password)
try: