Commit a026cef4 authored by Daniele Venzano's avatar Daniele Venzano

Continue the refactoring of the web interface

parent 1f3450a9
......@@ -197,7 +197,7 @@ class APIEndpoint:
for port in service.description['ports']:
port_key = str(port['port_number']) + "/" + port['protocol']
backend_port = self.sql.port_list(only_one=True, service_id=service.id, internal_name=port_key)
if backend_port.external_ip is not None:
if backend_port is not None and backend_port.external_ip is not None:
endpoint = port['url_template'].format(**{"ip_port": backend_port.external_ip + ":" + str(backend_port.external_port)})
endpoints.append((port['name'], endpoint))
......
......@@ -48,6 +48,8 @@ def zoe_web_main() -> int:
if args.log_file != "stderr":
log_args['filename'] = args.log_file
logging.basicConfig(**log_args)
logging.getLogger("MARKDOWN").setLevel(logging.WARNING)
logging.getLogger("tornado").setLevel(logging.WARNING)
if config.get_conf().auth_type == 'ldap' and not zoe_api.auth.ldap.LDAP_AVAILABLE:
log.error("LDAP authentication requested, but 'pyldap' module not installed.")
......
......@@ -73,7 +73,7 @@ def get_auth(handler: tornado.web.RequestHandler):
if handler.get_secure_cookie('zoe'):
cookie_val = str(handler.get_secure_cookie('zoe'))
uid, role = cookie_val[2:-1].split('.')
log.info('Authentication done using cookie')
log.debug('Authentication done using cookie')
return uid, role
auth_header = handler.request.headers.get('Authorization')
......
......@@ -40,7 +40,6 @@ def web_init(api_endpoint) -> List[tornado.web.URLSpec]:
tornado.web.url(r'/logout', zoe_api.web.start.LogoutWeb, route_args, name='logout'),
tornado.web.url(r'/executions', zoe_api.web.executions.ExecutionListWeb, route_args, name='execution_list'),
tornado.web.url(r'/executions/new', zoe_api.web.executions.ExecutionDefineWeb, route_args, name='execution_define'),
tornado.web.url(r'/executions/start', zoe_api.web.executions.ExecutionStartWeb, route_args, name='execution_start'),
tornado.web.url(r'/executions/restart/([0-9]+)', zoe_api.web.executions.ExecutionRestartWeb, route_args, name='execution_restart'),
tornado.web.url(r'/executions/terminate/([0-9]+)', zoe_api.web.executions.ExecutionTerminateWeb, route_args, name='execution_terminate'),
......
......@@ -23,27 +23,6 @@ from zoe_api.api_endpoint import APIEndpoint # pylint: disable=unused-import
from zoe_api.web.custom_request_handler import ZoeRequestHandler
class ExecutionDefineWeb(ZoeRequestHandler):
"""Handler class"""
def initialize(self, **kwargs):
"""Initializes the request handler."""
super().initialize(**kwargs)
self.api_endpoint = kwargs['api_endpoint'] # type: APIEndpoint
@catch_exceptions
def get(self):
"""Define a new execution."""
uid, role = get_auth(self)
if uid is None:
return self.redirect(self.get_argument('next', u'/login'))
template_vars = {
"uid": uid,
"role": role,
}
self.render('execution_new.html', **template_vars)
class ExecutionStartWeb(ZoeRequestHandler):
"""Handler class"""
def initialize(self, **kwargs):
......@@ -86,7 +65,7 @@ class ExecutionListWeb(ZoeRequestHandler):
template_vars = {
"uid": uid,
"role": role,
'executions': sorted(executions, key=lambda e: e.id)
'executions': sorted(executions, key=lambda e: e.id, reverse=True)
}
self.render('execution_list.html', **template_vars)
......
......@@ -88,14 +88,44 @@ class HomeWeb(ZoeRequestHandler):
filters = {
"user_id": uid,
"limit": 5,
"limit": 5
}
last_executions = self.api_endpoint.execution_list(uid, role, **filters)
filters = {
"user_id": uid,
"status": "running"
}
last_running_executions = self.api_endpoint.execution_list(uid, role, **filters)
filters = {
"user_id": uid,
"status": "submitted"
}
last_running_executions += self.api_endpoint.execution_list(uid, role, **filters)
filters = {
"user_id": uid,
"status": "scheduled"
}
last_running_executions += self.api_endpoint.execution_list(uid, role, **filters)
filters = {
"user_id": uid,
"status": "starting"
}
executions = self.api_endpoint.execution_list(uid, role, **filters)
last_running_executions += self.api_endpoint.execution_list(uid, role, **filters)
running_reservations = [e.total_reservations for e in last_running_executions]
total_memory = sum([r.memory.max for r in running_reservations])
total_cores = sum([r.cores.max for r in running_reservations])
template_vars = {
"uid": uid,
"role": role,
'executions': sorted(executions, key=lambda e: e.id)
"total_memory": total_memory,
"total_cores": total_cores,
'last_executions': sorted(last_executions, key=lambda e: e.id),
'running_executions': sorted(last_running_executions, key=lambda e: e.id)
}
self.render('home_user.html', **template_vars)
//! moment-timezone.js
//! version : 0.5.13
//! Copyright (c) JS Foundation and other contributors
//! license : MIT
//! github.com/moment/moment-timezone
!function(a,b){"use strict";"function"==typeof define&&define.amd?define(["moment"],b):"object"==typeof module&&module.exports?module.exports=b(require("moment")):b(a.moment)}(this,function(a){"use strict";function b(a){return a>96?a-87:a>64?a-29:a-48}function c(a){var c,d=0,e=a.split("."),f=e[0],g=e[1]||"",h=1,i=0,j=1;for(45===a.charCodeAt(0)&&(d=1,j=-1),d;d<f.length;d++)c=b(f.charCodeAt(d)),i=60*i+c;for(d=0;d<g.length;d++)h/=60,c=b(g.charCodeAt(d)),i+=c*h;return i*j}function d(a){for(var b=0;b<a.length;b++)a[b]=c(a[b])}function e(a,b){for(var c=0;c<b;c++)a[c]=Math.round((a[c-1]||0)+6e4*a[c]);a[b-1]=1/0}function f(a,b){var c,d=[];for(c=0;c<b.length;c++)d[c]=a[b[c]];return d}function g(a){var b=a.split("|"),c=b[2].split(" "),g=b[3].split(""),h=b[4].split(" ");return d(c),d(g),d(h),e(h,g.length),{name:b[0],abbrs:f(b[1].split(" "),g),offsets:f(c,g),untils:h,population:0|b[5]}}function h(a){a&&this._set(g(a))}function i(a){var b=a.toTimeString(),c=b.match(/\([a-z ]+\)/i);c&&c[0]?(c=c[0].match(/[A-Z]/g),c=c?c.join(""):void 0):(c=b.match(/[A-Z]{3,5}/g),c=c?c[0]:void 0),"GMT"===c&&(c=void 0),this.at=+a,this.abbr=c,this.offset=a.getTimezoneOffset()}function j(a){this.zone=a,this.offsetScore=0,this.abbrScore=0}function k(a,b){for(var c,d;d=6e4*((b.at-a.at)/12e4|0);)c=new i(new Date(a.at+d)),c.offset===a.offset?a=c:b=c;return a}function l(){var a,b,c,d=(new Date).getFullYear()-2,e=new i(new Date(d,0,1)),f=[e];for(c=1;c<48;c++)b=new i(new Date(d,c,1)),b.offset!==e.offset&&(a=k(e,b),f.push(a),f.push(new i(new Date(a.at+6e4)))),e=b;for(c=0;c<4;c++)f.push(new i(new Date(d+c,0,1))),f.push(new i(new Date(d+c,6,1)));return f}function m(a,b){return a.offsetScore!==b.offsetScore?a.offsetScore-b.offsetScore:a.abbrScore!==b.abbrScore?a.abbrScore-b.abbrScore:b.zone.population-a.zone.population}function n(a,b){var c,e;for(d(b),c=0;c<b.length;c++)e=b[c],I[e]=I[e]||{},I[e][a]=!0}function o(a){var b,c,d,e=a.length,f={},g=[];for(b=0;b<e;b++){d=I[a[b].offset]||{};for(c in d)d.hasOwnProperty(c)&&(f[c]=!0)}for(b in f)f.hasOwnProperty(b)&&g.push(H[b]);return g}function p(){try{var a=Intl.DateTimeFormat().resolvedOptions().timeZone;if(a){var b=H[r(a)];if(b)return b;z("Moment Timezone found "+a+" from the Intl api, but did not have that data loaded.")}}catch(c){}var d,e,f,g=l(),h=g.length,i=o(g),k=[];for(e=0;e<i.length;e++){for(d=new j(t(i[e]),h),f=0;f<h;f++)d.scoreOffsetAt(g[f]);k.push(d)}return k.sort(m),k.length>0?k[0].zone.name:void 0}function q(a){return D&&!a||(D=p()),D}function r(a){return(a||"").toLowerCase().replace(/\//g,"_")}function s(a){var b,c,d,e;for("string"==typeof a&&(a=[a]),b=0;b<a.length;b++)d=a[b].split("|"),c=d[0],e=r(c),F[e]=a[b],H[e]=c,d[5]&&n(e,d[2].split(" "))}function t(a,b){a=r(a);var c,d=F[a];return d instanceof h?d:"string"==typeof d?(d=new h(d),F[a]=d,d):G[a]&&b!==t&&(c=t(G[a],t))?(d=F[a]=new h,d._set(c),d.name=H[a],d):null}function u(){var a,b=[];for(a in H)H.hasOwnProperty(a)&&(F[a]||F[G[a]])&&H[a]&&b.push(H[a]);return b.sort()}function v(a){var b,c,d,e;for("string"==typeof a&&(a=[a]),b=0;b<a.length;b++)c=a[b].split("|"),d=r(c[0]),e=r(c[1]),G[d]=e,H[d]=c[0],G[e]=d,H[e]=c[1]}function w(a){s(a.zones),v(a.links),A.dataVersion=a.version}function x(a){return x.didShowError||(x.didShowError=!0,z("moment.tz.zoneExists('"+a+"') has been deprecated in favor of !moment.tz.zone('"+a+"')")),!!t(a)}function y(a){return!(!a._a||void 0!==a._tzm)}function z(a){"undefined"!=typeof console&&"function"==typeof console.error&&console.error(a)}function A(b){var c=Array.prototype.slice.call(arguments,0,-1),d=arguments[arguments.length-1],e=t(d),f=a.utc.apply(null,c);return e&&!a.isMoment(b)&&y(f)&&f.add(e.parse(f),"minutes"),f.tz(d),f}function B(a){return function(){return this._z?this._z.abbr(this):a.call(this)}}function C(a){return function(){return this._z=null,a.apply(this,arguments)}}var D,E="0.5.13",F={},G={},H={},I={},J=a.version.split("."),K=+J[0],L=+J[1];(K<2||2===K&&L<6)&&z("Moment Timezone requires Moment.js >= 2.6.0. You are using Moment.js "+a.version+". See momentjs.com"),h.prototype={_set:function(a){this.name=a.name,this.abbrs=a.abbrs,this.untils=a.untils,this.offsets=a.offsets,this.population=a.population},_index:function(a){var b,c=+a,d=this.untils;for(b=0;b<d.length;b++)if(c<d[b])return b},parse:function(a){var b,c,d,e,f=+a,g=this.offsets,h=this.untils,i=h.length-1;for(e=0;e<i;e++)if(b=g[e],c=g[e+1],d=g[e?e-1:e],b<c&&A.moveAmbiguousForward?b=c:b>d&&A.moveInvalidForward&&(b=d),f<h[e]-6e4*b)return g[e];return g[i]},abbr:function(a){return this.abbrs[this._index(a)]},offset:function(a){return this.offsets[this._index(a)]}},j.prototype.scoreOffsetAt=function(a){this.offsetScore+=Math.abs(this.zone.offset(a.at)-a.offset),this.zone.abbr(a.at).replace(/[^A-Z]/g,"")!==a.abbr&&this.abbrScore++},A.version=E,A.dataVersion="",A._zones=F,A._links=G,A._names=H,A.add=s,A.link=v,A.load=w,A.zone=t,A.zoneExists=x,A.guess=q,A.names=u,A.Zone=h,A.unpack=g,A.unpackBase60=c,A.needsOffset=y,A.moveInvalidForward=!0,A.moveAmbiguousForward=!1;var M=a.fn;a.tz=A,a.defaultZone=null,a.updateOffset=function(b,c){var d,e=a.defaultZone;void 0===b._z&&(e&&y(b)&&!b._isUTC&&(b._d=a.utc(b._a)._d,b.utc().add(e.parse(b),"minutes")),b._z=e),b._z&&(d=b._z.offset(b),Math.abs(d)<16&&(d/=60),void 0!==b.utcOffset?b.utcOffset(-d,c):b.zone(d,c))},M.tz=function(b){return b?(this._z=t(b),this._z?a.updateOffset(this):z("Moment Timezone has no data for "+b+". See http://momentjs.com/timezone/docs/#/data-loading/."),this):this._z?this._z.name:void 0},M.zoneName=B(M.zoneName),M.zoneAbbr=B(M.zoneAbbr),M.utc=C(M.utc),a.tz.setDefault=function(b){return(K<2||2===K&&L<9)&&z("Moment Timezone setDefault() requires Moment.js >= 2.9.0. You are using Moment.js "+a.version+"."),a.defaultZone=b?t(b):null,a};var N=a.momentProperties;return"[object Array]"===Object.prototype.toString.call(N)?(N.push("_z"),N.push("_a")):N&&(N._z=null),a});
This source diff could not be displayed because it is too large. You can view the blob instead.
body {
font-family: sans-serif;
font-family: "Helvetica Neue", Helvetica, Arial, sans-serif;
max-width: 90%;
margin-left: 20px;
font-size: 95%;
}
a:link {
color: rgba(79, 140, 30, 1);
color: rgb(53, 51, 144);
text-decoration: none;
}
a:visited {
color: rgba(79, 140, 30, 1);
color: rgb(53, 51, 144);
text-decoration: none;
}
......@@ -21,7 +22,7 @@ div.header {
height: 5em;
display: flex;
align-items: center;
border-bottom: 1px solid rgba(79, 140, 30, 1);
border-bottom: 1px solid rgb(0, 169, 225);
}
#logos img {
......@@ -55,32 +56,50 @@ div.header {
table.app_list {
border-collapse: collapse;
width: 95%;
}
table.app_list tr {
border-top: 1px black solid;
border-top: 1px #bad5e1 solid;
text-align: left;
}
table.app_list td {
padding-right: 1.5em;
table.app_list td, table.app_list th {
padding-right: 1em;
padding-bottom: 0.5em;
padding-top: 0.5em;
}
table.app_list tr.even {
table.app_list tbody tr:nth-child(2n+1) {
background-color: #F3FEEA;
}
div.status_line {
float: left;
table.sortable th:not(.sorttable_sorted):not(.sorttable_sorted_reverse):not(.sorttable_nosort):after {
content: "\00A0\2195";
}
table.sortable th::after, th.sorttable_sorted::after, th.sorttable_sorted_reverse::after {
content: " ";
display: inline-block;
}
#footer {
font-size: smaller;
label.filter {
font-size: 1.4em;
font-weight: bold;
}
input {
margin-top: 5px;
}
input.filter {
margin-top: 1.4em;
margin-bottom: 1.5em;
}
input.error input:invalid {
box-shadow: 0 0 4px red;
}
#loginbox {
width: 25em;
margin: 0 auto;
......@@ -104,3 +123,68 @@ textarea.logoutput {
.readable_description {
display: none;
}
div.zapp {
cursor: pointer;
width: 14em;
margin-left: 1em;
}
div.zapp p {
text-align: center;
}
div.zapp img {
width: 70%;
margin: 0 auto;
display: block;
}
div.zapp-list {
display: flex;
align-items: center;
}
div.zapp-category {
clear: both;
}
div.zapp-description {
font-size: smaller;
margin-bottom: 1em;
overflow: auto;
}
div.zapp-description img {
float: left;
height: 8em;
margin: 10px 10px 0 5px;
}
h3.zapp-startup {
clear: both;
}
form#zapp_start_form input, form#zapp_start_form label {
display: block;
}
form#zapp_start_form label {
font-variant: small-caps;
}
form#zapp_start_form input {
font-family: "Lucida Console", Monaco, monospace;
}
/* footer */
div.status_line {
float: left;
}
#footer {
font-size: smaller;
clear: both;
}
......@@ -13,7 +13,7 @@
<a href="{{ reverse_url("zappshop") }}">ZApp shop</a>
</div>
<div class="nav-item">
<a href="{{ reverse_url("execution_list") }}">My executions</a>
<a href="{{ reverse_url("execution_list") }}">Executions</a>
</div>
</div>
<div id="user_info">
......
......@@ -57,7 +57,4 @@
</div>
</div>
<div>
<p><a href="{{ reverse_url("home_user") }}">Home</a></p>
</div>
{% endblock %}
......@@ -4,53 +4,53 @@
{% block custom_head %}
<script src="/static/sorttable.js" type="application/javascript"></script>
<script src="/static/moment.min.js" type="application/javascript"></script>
<script src="/static/moment-timezone.min.js" type="application/javascript"></script>
<script>
moment.locale(window.navigator.userLanguage || window.navigator.language);
function format_timestamp(ts) {
document.write(moment(ts).calendar())
var m = moment.utc(ts);
m.local();
document.write(m.calendar());
}
</script>
{% endblock %}
{% block content %}
<div id="my_executions">
<h3>Executions</h3>
<p><a href="{{ reverse_url('execution_define') }}">New execution</a></p>
<label class="filter">All executions <input class="filter" placeholder="Filter" /></label>
<table id="exec_list" class="app_list sortable">
<thead>
<tr>
<th>ID</th>
<th>Execution name</th>
{% if is_admin %}
{% if role == "admin" %}
<th>User</th>
{% endif %}
<th>Status</th>
<th>Scheduled</th>
<th>Started</th>
<th>Finished</th>
<th>Actions</th>
<th class="sorttable_nosort">Actions</th>
</tr>
</thead>
<tbody>
<tbody class="list">
{% for e in executions %}
<tr class="{{ loop.cycle('odd', 'even') }}">
<tr>
<td>{{ e.id }}</td>
<td><a href="/executions/inspect/{{ e.id }}">{{ e.name }}</a></td>
{% if is_admin %}
<td class="exec-name"><a href="/executions/inspect/{{ e.id }}">{{ e.name }}</a></td>
{% if role == "admin" %}
<td>{{ e.user_id }}</td>
{% endif %}
<td>{{ e.status }}</td>
<td><script>format_timestamp("{{ e.time_submit }}")</script></td>
<td sorttable_customkey="{{ e.time_submit }}"><script>format_timestamp("{{ e.time_submit }}")</script></td>
{% if e.time_start == None %}
<td>not yet</td>
<td sorttable_customkey="-1">not yet</td>
{% else %}
<td><script>format_timestamp("{{ e.time_start }}")</script></td>
<td sorttable_customkey="{{ e.time_start }}"><script>format_timestamp("{{ e.time_start }}")</script></td>
{% endif %}
{% if e.time_end == None %}
<td>not yet</td>
<td sorttable_customkey="-1">not yet</td>
{% else %}
<td><script>format_timestamp("{{ e.time_end }}")</script></td>
<td sorttable_customkey="{{ e.time_end }}"><script>format_timestamp("{{ e.time_end }}")</script></td>
{% endif %}
{% if e.is_active %}
<td><a href="/executions/terminate/{{ e.id }}">Terminate</a></td>
......@@ -63,4 +63,17 @@
</table>
</div>
<script>
var $rows = $('#exec_list tbody tr');
$('input.filter').keyup(function() {
var val = '^(?=.*\\b' + $.trim($(this).val()).split(/\s+/).join('\\b)(?=.*\\b') + ').*$',
reg = RegExp(val, 'i'),
text;
$rows.show().filter(function() {
text = $(this).text().replace(/\s+/g, ' ');
return !reg.test(text);
}).hide();
});
</script>
{% endblock %}
{% extends "base_user.html" %}
{% block title %}New execution definition{% endblock %}
{% block content %}
<h1>Zoe - Analytics on demand</h1>
<h2>New execution</h2>
<form method="post" action="{{ reverse_url('execution_start') }}" enctype="multipart/form-data">
<label>Execution name: <input type="text" name="exec_name"></label><br>
<label>Application description: <input type="file" name="file"></label><br>
<input type="submit" value="Start!">
</form>
<div>
<p><a href="{{ reverse_url("home_user") }}">Home</a></p>
</div>
{% endblock %}
......@@ -4,43 +4,86 @@
{% block custom_head %}
<script src="/static/sorttable.js" type="application/javascript"></script>
<script src="/static/moment.min.js" type="application/javascript"></script>
<script src="/static/moment-timezone.min.js" type="application/javascript"></script>
<script>
moment.locale(window.navigator.userLanguage || window.navigator.language);
function format_timestamp(ts) {
document.write(moment(ts).calendar())
var m = moment.utc(ts);
m.local();
document.write(m.calendar());
}
</script>
{% endblock %}
{% block content %}
<div id="my_executions">
<h3>Executions</h3>
<table id="exec_list" class="app_list sortable">
<h2>Welcome to Zoe Analytics</h2>
<p>You have {{ running_executions|length }} execution{{ "s" if running_executions|length != 1 }} running{% if running_executions|length > 0 %}
with a total reservation of <script>format_bytes({{ total_memory }}, 2);</script> of memory and {{ total_cores }} cores.
{% else %}.{% endif %}
</p>
{% if running_executions|length > 0 %}
<div class="execution_table">
<h3>Running executions</h3>
<table id="exec_list" class="app_list">
<thead>
<tr>
<th>ID</th>
<th>Execution name</th>
{% if is_admin %}
<th>User</th>
{% endif %}
<th>Status</th>
<th>Scheduled</th>
<th>Started</th>
<th>Finished</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for e in executions %}
<tr class="{{ loop.cycle('odd', 'even') }}">
{% for e in running_executions %}
<tr>
<td>{{ e.id }}</td>
<td><a href="/executions/inspect/{{ e.id }}">{{ e.name }}</a></td>
{% if is_admin %}
<td>{{ e.user_id }}</td>
<td>{{ e.status }}</td>
{% if e.time_start == None %}
<td>not yet</td>
{% else %}
<td><script>format_timestamp("{{ e.time_start }}")</script></td>
{% endif %}
{% if e.time_end == None %}
<td>not yet</td>
{% else %}
<td><script>format_timestamp("{{ e.time_end }}")</script></td>
{% endif %}
{% if e.is_active %}
<td><a href="/executions/terminate/{{ e.id }}">Terminate</a></td>
{% else %}
<td><a href="/executions/restart/{{ e.id }}">Restart</a></td>
{% endif %}
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% endif %}
{% if last_executions|length > 0 %}
<div class="execution_table">
<h3>Most recent executions</h3>
<table id="exec_list" class="app_list">
<thead>
<tr>
<th>ID</th>
<th>Execution name</th>
<th>Status</th>
<th>Started</th>
<th>Finished</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for e in last_executions %}
<tr>
<td>{{ e.id }}</td>
<td><a href="/executions/inspect/{{ e.id }}">{{ e.name }}</a></td>
<td>{{ e.status }}</td>
<td><script>format_timestamp("{{ e.time_submit }}")</script></td>
{% if e.time_start == None %}
<td>not yet</td>
{% else %}
......@@ -61,5 +104,6 @@
</tbody>
</table>
</div>
{% endif %}
{% endblock %}
......@@ -11,13 +11,11 @@
<img alt="ZOE logo" src="{{ static_url("logo.png") }}">
<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=""><br>
<label for="username">Username:</label>
<input autocapitalize="off" autocorrect="off" class="text-input" id="username" name="username" tabindex="1" type="text" value=""><br>
<label for="password">Password:</label>
<input class="text-input" id="password" name="password" tabindex="2" type="password" value="">
</fieldset>
<label for="password">Password:</label>
<input class="text-input" id="password" name="password" tabindex="2" type="password" value="">
<div id="form_btn">
<input id="signin-btn" class="btn btn-blue" type="submit" value="Login" tabindex="3">
......
......@@ -9,26 +9,33 @@
{% for zapp_category in zapps|sort %}
<div class="zapp-category">
<h3>{{ zapp_category }}</h3>
{% for zapp in zapps[zapp_category] %}
<div class="zapp">
<img src="{{ reverse_url("zappshop_logo", zapp.id) }}" alt="logo">
<p>{{ zapp.readable_name }}</p>
<button class="zapp-details" value="rd-{{ zapp.id }}">Details</button>
<button class="zapp-start" value="{{ zapp.id }}-{{ zapp.manifest_index }}">Start</button>
<div class="readable_description" id="rd-{{ zapp.id }}">{{ zapp.readable_description|safe }}</div>
</div>
{% endfor %}
<div class="zapp-list">
{% for zapp in zapps[zapp_category] %}
<div class="zapp" id="{{ zapp.id }}-{{ zapp.manifest_index }}">
<img src="{{ reverse_url("zappshop_logo", zapp.id) }}" alt="logo">
<p>{{ zapp.readable_name }}</p>
<div class="readable_description" id="rd-{{ zapp.id }}">{{ zapp.readable_description|safe }}</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
<script>
$("button.zapp-details").click(function () {
$('#'+$(this).attr('value')).toggle();
});
$("button.zapp-start").click(function () {
window.location.href = "{{ reverse_url('zappshop_start', '') }}" + $(this).attr('value');
$("div.zapp").click(function () {
window.location.href = "{{ reverse_url('zappshop_start', '') }}" + $(this).attr('id');
});
</script>
{% if role != "guest" %}
<div class="zapp-category">
<h3>Custom JSON upload</h3>
<form method="post" action="{{ reverse_url('execution_start') }}" enctype="multipart/form-data">
<label>Execution name: <input type="text" name="exec_name"></label><br>
<label>Application description: <input type="file" name="file"></label><br>
<input type="submit" value="Start!">
</form>
</div>
{% endif %}
{% endblock %}
......@@ -2,37 +2,48 @@
{% block title %}ZApp Shop{% endblock %}
{% block content %}
<h2>{{ zapp.readable_name }} start</h2>
{% block custom_head %}
<script src="/static/jquery.validate.min.js" type="application/javascript"></script>
{% endblock %}
<img src="{{ reverse_url("zappshop_logo", zapp.id) }}" alt="logo">
{% block content %}
<div>{{ zapp.readable_description|safe }}</div>
<div class="zapp-description">
<img src="{{ reverse_url("zappshop_logo", zapp.id) }}" alt="logo">
{{ zapp.readable_description|safe }}
</div>
<h3 class="zapp-startup">ZApp details</h3>
<p>This ZApp is composed by the following services:</p>
<ul>
{% for service in zapp.zoe_description.services %}
<li>{{ service["total_count"] }} {{ service["name"] }} ({{ service["essential_count"] }} essentials)
<li>{{ service["total_count"] }} {{ service["name"] }} ({{ service["essential_count"] }} essential{{ 's' if service["essential_count"] > 1 }})
<ul>
<li>memory: <script>format_bytes({{ service["resources"]["memory"]["max"] }}, 2) </script></li>
<li>cpu cores: {{ service["resources"]["cores"]["max"] }}</li>
<li>memory: {% if service["resources"]["memory"]["max"] != None %}
<script>format_bytes({{ service["resources"]["memory"]["max"] }}, 2) </script></li>
{% else %}
No limit
{% endif %}
<li>cpu cores: {{ service["resources"]["cores"]["max"] if service["resources"]["cores"]["max"] != None else "No limit" }}</li>
</ul></li>
{% endfor %}
</ul>
<p>Parameters:</p>
<h4>Start-up parameters:</h4>
<form action="{{ reverse_url("zappshop_start", "") }}{{ zapp.id }}-{{ zapp.manifest_index }}" method="post" id="zapp_start_form">
<input type="hidden" name="zapp-id" value="{{ zapp.id }}-{{ zapp.manifest_index }}">
<label>Execution name: <input type="text" name="exec_name"/></label><br/>
<label>Execution name:&nbsp;<input type="text" name="exec_name" placeholder="experiment3" maxlength="16" size="18" required/></label><br/>
{% for param in zapp.parameters %}
<label>{{ param.readable_name }}
<input name="{{ param.name }}" value="{{ param.default if param.default != None }}" required title="{{ param.description }}"/></label><br/>
<label>{{ param.readable_name }}:&nbsp;
<input name="{{ param.name }}" value="{{ param.default if param.default != None }}" required title="{{ param.description }}" size="128"/></label><br/>
{% endfor %}
<input type="submit" value="Start"/>
<button type="submit">Start</button>
</form>
<script>
$("#login_form").validate();
$("#zapp_start_form").validate();
</script>
<p><a href="{{ reverse_url("zappshop") }}">Back to the ZApp shop</a></p>
{% endblock %}
......@@ -91,7 +91,7 @@ def get_auth(handler: ZoeRequestHandler):
if handler.get_secure_cookie('zoe'):
cookie_val = str(handler.get_secure_cookie('zoe'))
uid, role = cookie_val[2:-1].split('.')
log.info('Authentication done using cookie')
log.debug('Authentication done using cookie')
return uid, role
else:
return None, None
......
......@@ -120,6 +120,11 @@ class ZAppStartWeb(ZoeRequestHandler):
for env in service['environment']:
if env[0] == param.name:
env[1] = self.get_argument(param.name)
elif param.kind == 'command':
for service in app_descr['services']:
if service['name'] ==<