Commit 8bac3eb6 authored by Daniele Venzano's avatar Daniele Venzano

Implement base app shop functionality

Set also the final logo and work on the general web ui refactoring
parent 8aef786d
......@@ -100,6 +100,9 @@ class APIEndpoint:
def execution_delete(self, uid, role, exec_id):
"""Delete an execution."""
if role != "admin":
raise zoe_api.exceptions.ZoeAuthException()
e = self.sql.execution_list(id=exec_id, only_one=True)
assert isinstance(e, zoe_lib.state.sql_manager.Execution)
if e is None:
......
sonar.projectName=zoe_api
sonar.host.url=http://your-sonarqube-server-address
sonar.sources=.
sonar.language=py
sonar.sourceEncoding=UTF-8
......@@ -22,6 +22,7 @@ import tornado.web
import zoe_api.web.start
import zoe_api.web.websockets
import zoe_api.web.executions
import zoe_api.web.zapp_shop
from zoe_lib.version import ZOE_API_VERSION, ZOE_VERSION
......@@ -46,7 +47,11 @@ def web_init(api_endpoint) -> List[tornado.web.URLSpec]:
tornado.web.url(r'/executions/inspect/([0-9]+)', zoe_api.web.executions.ExecutionInspectWeb, route_args, name='execution_inspect'),
tornado.web.url(r'/service/logs/([0-9]+)', zoe_api.web.executions.ServiceLogsWeb, route_args, name='service_logs'),
tornado.web.url(r'/websocket', zoe_api.web.websockets.WebSocketEndpointWeb, route_args, name='websocket')
tornado.web.url(r'/websocket', zoe_api.web.websockets.WebSocketEndpointWeb, route_args, name='websocket'),
tornado.web.url(r'/zapp-shop', zoe_api.web.zapp_shop.ZAppShopHomeWeb, route_args, name='zappshop'),
tornado.web.url(r'/zapp-shop/logo/([a-z\-.]+)', zoe_api.web.zapp_shop.ZAppLogoWeb, route_args, name='zappshop_logo'),
tornado.web.url(r'/zapp-shop/start/([a-z\-.]+)', zoe_api.web.zapp_shop.ZAppStartWeb, route_args, name='zappshop_start')
]
return web_routes
......
This diff is collapsed.
......@@ -133,3 +133,7 @@ textarea.logoutput {
padding-right: 1em;
padding-top: 0.4em;
}
.readable_description {
display: none;
}
function format_bytes(bytes, decimals) {
if(bytes === 0) {
document.write('0 Byte');
return;
}
var k = 1000;
var dm = decimals + 1 || 3;
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
document.write((bytes / Math.pow(k, i)).toPrecision(dm) + ' ' + sizes[i]);
}
......@@ -5,55 +5,15 @@
<meta charset="UTF-8">
<title>{% block title %}{% endblock %} - Zoe</title>
<script src="/static/jquery-2.1.4.min.js" type="application/javascript"></script>
<script src="/static/jquery-ui-1.11.4/jquery-ui.min.js" type="application/javascript"></script>
<script src="/static/jquery.wizard.js" type="application/javascript"></script>
<script src="/static/jquery.validate.min.js" type="application/javascript"></script>
<script src="/static/sorttable.js" type="application/javascript"></script>
<script src="/static/moment.min.js" type="application/javascript"></script>
<script src="/static/zoe.js" type="application/javascript"></script>
<link rel="stylesheet" href="/static/zoe.css" type="text/css">
<link rel="stylesheet" href="/static/jquery-ui-1.11.4/jquery-ui.min.css">
{% endblock %}
{% block custom_head %}
{% endblock %}
</head>
<body>
<script>
/* function update_status() {
$.getJSON("")
.done(function( data ) {
$("#num_nodes").text(data.num_nodes);
$("#num_containers").text(data.num_containers);
}).error(function( data ) {
$("#num_nodes").text("N/A");
$("#num_containers").text("N/A");
});
}
update_status();
window.setInterval(update_status, 5000);
*/
moment.locale(window.navigator.userLanguage || window.navigator.language);
function format_timestamp(ts) {
document.write(moment(ts).calendar())
}
function format_bytes(bytes, decimals) {
if(bytes == 0) {
document.write('0 Byte');
return;
}
var k = 1000;
var dm = decimals + 1 || 3;
var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB'];
var i = Math.floor(Math.log(bytes) / Math.log(k));
document.write((bytes / Math.pow(k, i)).toPrecision(dm) + ' ' + sizes[i]);
}
</script>
{% if uid %}
<div id="userinfo">
{{ uid }} ({{ role }}) <a href="{{ reverse_url("logout") }}">logout</a>
</div>
{% endif %}
{% block content_header %}
{% endblock %}
<div id="content">{% block content %}{% endblock %}</div>
<div id="footer">
{% block footer %}
......
{% extends "base.html" %}
{% block content_header %}
{% if uid %}
<div id="userinfo">
{{ uid }} ({{ role }}) <a href="{{ reverse_url("logout") }}">logout</a>
</div>
{% endif %}
{% endblock %}
{% block content %}
{{ super() }}
{% endblock %}
{% extends "base_user.html" %}
{% block title %}Home{% endblock %}
{% block custom_head %}
<script src="/static/sorttable.js" type="application/javascript"></script>
<script src="/static/moment.min.js" type="application/javascript"></script>
<script>
moment.locale(window.navigator.userLanguage || window.navigator.language);
function format_timestamp(ts) {
document.write(moment(ts).calendar())
}
</script>
{% endblock %}
{% block content %}
<h1>Zoe - Analytics on demand</h1>
......
{% extends "base.html" %}
{% block title %}Login{% endblock %}
{% block custom_head %}
<script src="/static/jquery.validate.min.js" type="application/javascript"></script>
{% endblock %}
{% block content %}
<div id="loginbox">
<img alt="ZOE logo" src="{{ static_url("logo.svg") }}">
<img alt="ZOE logo" src="{{ static_url("logo.png") }}">
<div id="login-form">
<form action="/login" method="post" id="login_form">
<fieldset>
......@@ -21,4 +26,8 @@
</div>
</div>
<script>
$("#login_form").validate();
</script>
{% endblock %}
{% extends "base_user.html" %}
{% block title %}ZApp Shop{% endblock %}
{% block content %}
<h2>ZApp shop</h2>
{% for zapp_category in zapps|sort %}
<div class="zapp-category">
<h3>{{ zapp_category[0].category }}</h3>
{% for zapp in 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 }}">Start</button>
<div class="readable_description" id="rd-{{ zapp.id }}">{{ zapp.readable_description|safe }}</div>
</div>
{% endfor %}
</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');
});
</script>
{% endblock %}
# Copyright (c) 2017, Daniele Venzano
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Zoe App shop web pages."""
import logging
from zoe_api import zapp_shop
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.custom_request_handler import ZoeRequestHandler
log = logging.getLogger(__name__)
class ZAppShopHomeWeb(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):
"""Home page with authentication."""
uid, role = get_auth(self)
if uid is None:
return self.redirect(self.get_argument('next', u'/login'))
zapp_ids = zapp_shop.zshop_list_apps()
zapps = [zapp_shop.zshop_read_manifest(zapp_id) for zapp_id in zapp_ids]
template_vars = {
"uid": uid,
"role": role,
'zapps': zapps,
}
self.render('zapp_shop.html', **template_vars)
class ZAppLogoWeb(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, zapp_id):
"""Home page with authentication."""
uid, role = get_auth(self)
if uid is None:
return self.redirect(self.get_argument('next', u'/login'))
self.set_header("Content-type", "image/png")
self.write(zapp_shop.get_logo(zapp_id))
class ZAppStartWeb(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, zapp_id):
"""Home page with authentication."""
uid, role = get_auth(self)
if uid is None:
return self.redirect(self.get_argument('next', u'/login'))
zapp_ids = zapp_shop.zshop_list_apps()
zapps = [zapp_shop.zshop_read_manifest(zapp_id) for zapp_id in zapp_ids]
template_vars = {
"uid": uid,
"role": role,
'zapps': zapps,
}
self.render('zapp_shop.html', **template_vars)
# Copyright (c) 2017, Daniele Venzano
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
# implied.
# See the License for the specific language governing permissions and
# limitations under the License.
"""Zoe App shop module."""
import logging
import os
import json
import markdown
from zoe_lib.config import get_conf
log = logging.getLogger(__name__)
class ZAppParameter:
"""A ZApp parameter that should be exposed to the user interface."""
def __init__(self, param_manifest):
self.kind = param_manifest['kind']
self.name = param_manifest['name']
self.type = param_manifest['type']
self.default = param_manifest['default']
class ZApp:
"""A ZApp."""
def __init__(self, zapp_id, manifest, manifest_index):
self.id = zapp_id
self.category = manifest['category']
zapp = manifest['zapps'][manifest_index]
self.readable_name = zapp['name']
self.readable_description = self.read_description()
self.json_file = zapp['description']
self.zoe_description = self.parse_json_description()
self.parameters = []
self.parse_parameters(zapp)
def parse_parameters(self, zapp_manifest):
"""Translates the parameters from the manifest into objects."""
for param in zapp_manifest['parameters']:
self.parameters.append(ZAppParameter(param))
def read_description(self):
"""Reads and renders the README.md file."""
mdown = open(os.path.join(get_conf().zapp_shop_path, self.id, "README.md"), 'r', encoding='utf-8').read()
return markdown.markdown(mdown, extensions=['markdown.extensions.extra', 'markdown.extensions.codehilite'])
def parse_json_description(self):
"""Reads the classic json description."""
return json.load(open(os.path.join(get_conf().zapp_shop_path, self.id, self.json_file), 'r'))
def zshop_list_apps():
"""List the ZApp repos."""
dirs = [d for d in os.listdir(get_conf().zapp_shop_path) if os.path.isdir(os.path.join(get_conf().zapp_shop_path, d)) and os.path.exists(os.path.join(get_conf().zapp_shop_path, d, "manifest.json"))]
return dirs
def zshop_read_manifest(zapp_id):
"""Reads and decodes the manifest file."""
manifest_path = os.path.join(get_conf().zapp_shop_path, zapp_id, "manifest.json")
manifest = json.load(open(manifest_path))
zapps = []
for idx in range(len(manifest['zapps'])):
zapps.append(ZApp(zapp_id, manifest, idx))
return zapps
def get_logo(zapp_id):
"""Return the ZApp PNG logo image file contents."""
logo_path = os.path.join(get_conf().zapp_shop_path, zapp_id, 'logo.png')
return open(logo_path, "rb").read()
......@@ -80,6 +80,12 @@ def exec_list_cmd(auth, args):
print(tabulate(tabular_data, headers))
def exec_rm_cmd(auth, args):
"""Delete an execution and kill it if necessary."""
exec_api = ZoeExecutionsAPI(auth['url'], auth['user'], auth['pass'])
exec_api.delete(args.id)
ENV_HELP_TEXT = '''To authenticate with Zoe you need to define three environment variables:
ZOE_URL: point to the URL of the Zoe Scheduler (ex.: http://localhost:5000/
ZOE_USER: the username used for authentication
......@@ -121,6 +127,10 @@ def process_arguments() -> Tuple[ArgumentParser, Namespace]:
argparser_app_list.add_argument('--later-than-end', help='Show only executions ended later than this timestamp (seconds since UTC epoch)')
argparser_app_list.set_defaults(func=exec_list_cmd)
argparser_execution_rm = subparser.add_parser('exec-rm', help="Deletes an execution")
argparser_execution_rm.add_argument('id', type=int, help="Execution id")
argparser_execution_rm.set_defaults(func=exec_rm_cmd)
return parser, parser.parse_args()
......
......@@ -108,6 +108,8 @@ def load_configuration(test_conf=None):
argparser.add_argument('--kube-config-file', help='Kubernetes configuration file', default='/opt/zoe/kube.conf')
argparser.add_argument('--kube-namespace', help='The namespace that Zoe operates on', default='default')
# other options
argparser.add_argument('--zapp-shop-path', help='Path where ZApp folders are stored', default='/var/lib/zoe-apps')
argparser.add_argument('--cookie-secret', help='secret used to encrypt cookies', default='changeme')
argparser.add_argument('--log-file', help='output logs to a file', default='stderr')
......
sonar.projectName=zoe_lib
sonar.host.url=http://your-sonarqube-server-address
sonar.sources=.
sonar.language=py
sonar.sourceEncoding=UTF-8
......@@ -302,7 +302,10 @@ class SwarmClient:
cont.stop(timeout=5)
if delete:
cont.remove(force=True)
try:
cont.remove(force=True)
except docker.errors.APIError as e:
log.warning(str(e))
def event_listener(self, callback: Callable[[str], bool]) -> None:
"""An infinite loop that listens for events from Swarm."""
......
sonar.projectName=zoe_master
sonar.host.url=http://your-sonarqube-server-address
sonar.sources=.
sonar.language=py
sonar.sourceEncoding=UTF-8
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment