Skip to content
Snippets Groups Projects
Commit 21e13d4e authored by Lluis Gifre Renom's avatar Lluis Gifre Renom
Browse files

WebUI component:

- removed hardcoded paths and replaced by url_for methods
- arranged service paths to be homogeneous with other entities
- implemented service delete
- adapted JavaScript scripts to use dynamically generated paths from Jinja
- corrected error logger in delete of device and service
- implemented logic to support deploying the WebUI on a subpath of WSGI servers
parent f28cab56
No related branches found
No related tags found
1 merge request!54Release 2.0.0
Showing
with 102 additions and 24 deletions
......@@ -36,3 +36,6 @@ CONTEXT_SERVICE_PORT = int(os.environ.get('CONTEXTSERVICE_SERVICE_PORT_GRPC', 10
DEVICE_SERVICE_ADDRESS = os.environ.get('DEVICESERVICE_SERVICE_HOST', 'deviceservice')
DEVICE_SERVICE_PORT = int(os.environ.get('DEVICESERVICE_SERVICE_PORT_GRPC', 2020))
SERVICE_SERVICE_ADDRESS = os.environ.get('SERVICESERVICE_SERVICE_HOST', 'serviceservice')
SERVICE_SERVICE_PORT = int(os.environ.get('SERVICESERVICE_SERVICE_PORT_GRPC', 3030))
......@@ -54,6 +54,9 @@ COPY --chown=webui:webui context/client/. context/client
COPY --chown=webui:webui device/__init__.py device/__init__.py
COPY --chown=webui:webui device/proto/. device/proto
COPY --chown=webui:webui device/client/. device/client
COPY --chown=webui:webui service/__init__.py service/__init__.py
COPY --chown=webui:webui service/proto/. service/proto
COPY --chown=webui:webui service/client/. service/client
COPY --chown=webui:webui webui/. webui
# Start webui service
......
......@@ -227,12 +227,12 @@
"current": {
"selected": true,
"text": [
"R1-INF",
"R3-INF"
"R1-EMU",
"R3-EMU"
],
"value": [
"R1-INF",
"R3-INF"
"R1-EMU",
"R3-EMU"
]
},
"datasource": null,
......@@ -257,12 +257,10 @@
"current": {
"selected": true,
"text": [
"13/2/0",
"13/2/1"
"13/1/2"
],
"value": [
"13/2/0",
"13/2/1"
"13/1/2"
]
},
"datasource": null,
......
......@@ -51,8 +51,17 @@ def readiness():
def from_json(json_str):
return json.loads(json_str)
class SetSubAppMiddleware():
def __init__(self, app, web_app_root):
self.app = app
self.web_app_root = web_app_root
def create_app(use_config=None):
def __call__(self, environ, start_response):
environ['SCRIPT_NAME'] = self.web_app_root
environ['APPLICATION_ROOT'] = self.web_app_root
return self.app(environ, start_response)
def create_app(use_config=None, web_app_root=None):
app = Flask(__name__)
if use_config:
app.config.from_mapping(**use_config)
......@@ -64,6 +73,9 @@ def create_app(use_config=None):
app.register_blueprint(healthz, url_prefix='/healthz')
from webui.service.js.routes import js
app.register_blueprint(js)
from webui.service.main.routes import main
app.register_blueprint(main)
......@@ -80,4 +92,6 @@ def create_app(use_config=None):
app.jinja_env.globals.update(get_working_context=get_working_context)
if web_app_root is not None:
app.wsgi_app = SetSubAppMiddleware(app.wsgi_app, web_app_root)
return app
......@@ -24,6 +24,7 @@ def main():
metrics_port = os.environ.get('METRICS_PORT', METRICS_PORT )
host = os.environ.get('HOST', HOST )
debug = os.environ.get('DEBUG', DEBUG )
web_app_root = os.environ.get('WEBUI_APPLICATION_ROOT', None )
logging.basicConfig(level=log_level)
logger = logging.getLogger(__name__)
......@@ -40,7 +41,7 @@ def main():
app = create_app(use_config={
'SECRET_KEY': SECRET_KEY,
'MAX_CONTENT_LENGTH': MAX_CONTENT_LENGTH,
})
}, web_app_root=web_app_root)
app.run(host=host, port=service_port, debug=debug)
logger.info('Bye')
......
......@@ -123,11 +123,10 @@ def delete(device_uuid):
request.device_uuid.uuid = device_uuid
device_client.connect()
response = device_client.DeleteDevice(request)
device_client.close()
flash('Device deleted successfully!', 'success')
flash('Device "{:s}" deleted successfully!'.format(device_uuid), 'success')
except Exception as e:
flash(f'Problem deleting the device. {e.details()}', 'danger')
flash('Problem deleting device "{:s}": {:s}'.format(device_uuid, str(e.details())), 'danger')
current_app.logger.exception(e)
return redirect(url_for('device.home'))
# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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.
# Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
#
# 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.
from flask import render_template, Blueprint
js = Blueprint('js', __name__, url_prefix='/js')
@js.get('site.js')
def site_js():
return render_template('js/site.js')
@js.get('topology.js')
def topology_js():
return render_template('js/topology.js')
......@@ -15,14 +15,16 @@
import grpc
from flask import current_app, redirect, render_template, Blueprint, flash, session, url_for
from context.proto.context_pb2 import Service, ServiceId
from webui.Config import CONTEXT_SERVICE_ADDRESS, CONTEXT_SERVICE_PORT
from webui.Config import CONTEXT_SERVICE_ADDRESS, CONTEXT_SERVICE_PORT, SERVICE_SERVICE_ADDRESS, SERVICE_SERVICE_PORT
from context.client.ContextClient import ContextClient
from service.client.ServiceClient import ServiceClient
from webui.proto.context_pb2 import ContextId, ServiceList, ServiceTypeEnum, ServiceStatusEnum, ConfigActionEnum
service = Blueprint('service', __name__, url_prefix='/service')
context_client: ContextClient = ContextClient(CONTEXT_SERVICE_ADDRESS, CONTEXT_SERVICE_PORT)
service_client: ServiceClient = ServiceClient(SERVICE_SERVICE_ADDRESS, SERVICE_SERVICE_PORT)
@service.get('/')
def home():
......@@ -61,7 +63,7 @@ def add():
return render_template('service/home.html')
@service.get('detail/<path:service_uuid>')
@service.get('<path:service_uuid>/detail')
def detail(service_uuid: str):
context_uuid = session.get('context_uuid', '-')
if context_uuid == "-":
......@@ -82,6 +84,23 @@ def detail(service_uuid: str):
return render_template('service/detail.html', service=response)
@service.get('delete/<path:service_uuid>')
@service.get('<path:service_uuid>/delete')
def delete(service_uuid: str):
pass
context_uuid = session.get('context_uuid', '-')
if context_uuid == "-":
flash("Please select a context!", "warning")
return redirect(url_for("main.home"))
try:
request: ServiceId = ServiceId()
request.service_uuid.uuid = service_uuid
request.context_id.context_uuid.uuid = context_uuid
service_client.connect()
response = service_client.DeleteService(request)
service_client.close()
flash('Service "{:s}" deleted successfully!'.format(service_uuid), 'success')
except Exception as e:
flash('Problem deleting service "{:s}": {:s}'.format(service_uuid, str(e.details())), 'danger')
current_app.logger.exception(e)
return redirect(url_for('service.home'))
......@@ -31,7 +31,7 @@
</head>
<body>
<div id="teraflow-branding" style="width: 260px; margin: 7px;">
<a href="/" title="Home" rel="home" id="main-logo" class="site-logo site-logo-pages">
<a href="{{ url_for('main.home') }}" title="Home" rel="home" id="main-logo" class="site-logo site-logo-pages">
<svg id="Capa_1" data-name="Capa 1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 436.3 132.1"><defs><style>.cls-1{fill:#36a9e1;}.cls-2{fill:#1d71b8;}.cls-3{fill:none;stroke-width:2.52px;}.cls-10,.cls-3,.cls-4,.cls-5,.cls-7,.cls-8,.cls-9{stroke:#0f77b6;}.cls-3,.cls-4,.cls-8{stroke-miterlimit:10;}.cls-10,.cls-4,.cls-5,.cls-7,.cls-8,.cls-9{fill:#fff;}.cls-4{stroke-width:0.73px;}.cls-5,.cls-7{stroke-miterlimit:10;}.cls-5{stroke-width:0.75px;}.cls-6{fill:#0f77b6;}.cls-7{stroke-width:0.72px;}.cls-8{stroke-width:0.7px;}.cls-9{stroke-miterlimit:10;stroke-width:0.69px;}.cls-10{stroke-miterlimit:10;stroke-width:0.7px;}</style></defs><path class="cls-1" d="M96,57V51.3h44.1V57H121v52.3h-5.9V57Z"></path><path class="cls-1" d="M168.9,95.1l4.7,2.4a26,26,0,0,1-5.3,7.3,22.27,22.27,0,0,1-6.7,4.2,22.64,22.64,0,0,1-8.5,1.4c-7,0-12.5-2.3-16.4-6.9a23.53,23.53,0,0,1-5.9-15.6,23,23,0,0,1,5-14.5c4.2-5.4,9.9-8.1,17-8.1,7.3,0,13.2,2.8,17.5,8.3,3.1,3.9,4.7,8.8,4.7,14.7H136.4a17.48,17.48,0,0,0,4.8,12.3,15.26,15.26,0,0,0,11.4,4.8,20,20,0,0,0,6.4-1.1,19.3,19.3,0,0,0,5.3-3A33.07,33.07,0,0,0,168.9,95.1Zm0-11.6a18.66,18.66,0,0,0-3.2-7.1,15.25,15.25,0,0,0-5.6-4.3,16.87,16.87,0,0,0-7.3-1.6,16.06,16.06,0,0,0-10.9,4.1,18.15,18.15,0,0,0-5,8.9Z"></path><path class="cls-1" d="M182,66.4h5.6v6.3a20,20,0,0,1,5.3-5.5,10.67,10.67,0,0,1,5.8-1.8,9.87,9.87,0,0,1,4.9,1.5l-2.9,4.7a7.52,7.52,0,0,0-2.9-.7,8.09,8.09,0,0,0-5.3,2.3,14.64,14.64,0,0,0-3.9,7c-.7,2.4-1,7.4-1,14.8v14.5H182Z"></path><path class="cls-1" d="M246.2,66.4v42.9h-5.4V102a23.11,23.11,0,0,1-7.8,6.3,21.23,21.23,0,0,1-9.4,2.1,21,21,0,0,1-15.6-6.6,23.07,23.07,0,0,1,.1-32,21.23,21.23,0,0,1,15.7-6.6,20,20,0,0,1,17.1,8.9V66.2h5.3Zm-22.1,4.2a16.67,16.67,0,0,0-8.5,2.3,15.93,15.93,0,0,0-6.2,6.4,17.68,17.68,0,0,0-2.3,8.7,18.26,18.26,0,0,0,2.3,8.7,15.93,15.93,0,0,0,6.2,6.4,16.58,16.58,0,0,0,8.4,2.3,17.59,17.59,0,0,0,8.6-2.3,15.42,15.42,0,0,0,6.2-6.2,17.17,17.17,0,0,0,2.2-8.8,16.73,16.73,0,0,0-4.9-12.4A15.8,15.8,0,0,0,224.1,70.6Z"></path><path class="cls-2" d="M259.5,51.3h29.1V57H265.3V75.2h23.3v5.7H265.3v28.5h-5.8V51.3Z"></path><path class="cls-2" d="M296.9,49.9h5.5v59.5h-5.5Z"></path><path class="cls-2" d="M330.5,65.3a21.1,21.1,0,0,1,16.4,7.2A22.55,22.55,0,0,1,352.8,88a22.24,22.24,0,0,1-6.3,15.7c-4.2,4.5-9.5,6.7-16.1,6.7s-12-2.2-16.1-6.7A22.24,22.24,0,0,1,308,88a22.73,22.73,0,0,1,5.9-15.5A21.81,21.81,0,0,1,330.5,65.3Zm0,5.4a15.83,15.83,0,0,0-11.8,5.1,17,17,0,0,0-4.9,12.3,17.68,17.68,0,0,0,2.3,8.7,15.19,15.19,0,0,0,6.1,6.2,16.48,16.48,0,0,0,8.4,2.2A16,16,0,0,0,339,103a15.82,15.82,0,0,0,6.1-6.2,17.68,17.68,0,0,0,2.3-8.7,17.07,17.07,0,0,0-5-12.3A16.2,16.2,0,0,0,330.5,70.7Z"></path><path class="cls-2" d="M351.2,66.4h5.7L370,97.6l13.7-31.1h1l13.8,31.1,13.4-31.1h5.7L399,109.3h-1L384.3,78.6l-13.7,30.7h-1Z"></path><polyline class="cls-3" points="51 105 51 41.2 27 41.2"></polyline><polyline class="cls-3" points="38.1 33.8 56.4 33.8 56.4 93"></polyline><polyline class="cls-3" points="79.9 33.8 61.5 33.8 61.5 79.2"></polyline><polyline class="cls-3" points="90.7 41.2 66.7 41.2 66.7 105"></polyline><line class="cls-3" x1="83.1" y1="62.6" x2="66.7" y2="62.6"></line><circle class="cls-4" cx="27" cy="41.2" r="5.3"></circle><path class="cls-1" d="M23.3,41.2a3.8,3.8,0,1,0,3.8-3.8A3.8,3.8,0,0,0,23.3,41.2Z"></path><circle class="cls-5" cx="51" cy="105" r="5.4"></circle><path class="cls-1" d="M47.3,105a3.8,3.8,0,1,0,3.8-3.8A3.8,3.8,0,0,0,47.3,105Z"></path><circle class="cls-6" cx="56.36" cy="93.02" r="3.4"></circle><circle class="cls-6" cx="61.5" cy="79.2" r="2.8"></circle><circle class="cls-7" cx="66.7" cy="105.01" r="5.3"></circle><path class="cls-1" d="M63,105a3.8,3.8,0,1,0,3.8-3.8A3.8,3.8,0,0,0,63,105Z"></path><circle class="cls-8" cx="90.7" cy="41.2" r="5.1"></circle><path class="cls-1" d="M87,41.2a3.8,3.8,0,1,0,3.8-3.8A3.8,3.8,0,0,0,87,41.2Z"></path><circle class="cls-8" cx="84.7" cy="62.6" r="5.1"></circle><path class="cls-1" d="M81,62.6a3.8,3.8,0,1,0,3.8-3.8A3.8,3.8,0,0,0,81,62.6Z"></path><line class="cls-3" x1="34.8" y1="62.6" x2="51.1" y2="62.6"></line><circle class="cls-8" cx="33.1" cy="62.6" r="5.1"></circle><path class="cls-1" d="M36.9,62.6a3.8,3.8,0,1,1-3.8-3.8A3.8,3.8,0,0,1,36.9,62.6Z"></path><line class="cls-3" x1="23.7" y1="26.7" x2="94.1" y2="26.7"></line><circle class="cls-9" cx="94.09" cy="26.67" r="5"></circle><path class="cls-1" d="M90.3,26.7a3.8,3.8,0,1,0,3.8-3.8A3.8,3.8,0,0,0,90.3,26.7Z"></path><circle class="cls-6" cx="78" cy="33.8" r="3.8"></circle><circle class="cls-6" cx="40" cy="33.8" r="3.8"></circle><circle class="cls-10" cx="23.71" cy="26.71" r="5.1"></circle><path class="cls-1" d="M20,26.7a3.8,3.8,0,1,0,3.8-3.8A3.8,3.8,0,0,0,20,26.7Z"></path></svg>
</a>
</div>
......
......@@ -21,7 +21,7 @@
<div class="row mb-3">
<div class="col-sm-3">
<button type="button" class="btn btn-success" onclick="window.location.href = '/device/'">
<button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('device.home') }}'">
<i class="bi bi-box-arrow-in-left"></i>
Back to device list
</button>
......
......@@ -51,7 +51,7 @@ forceProperties = {
var simulation = d3.forceSimulation();
// load the data
d3.json('/topology', function(data) {
d3.json("{{ url_for('main.topology') }}", function(data) {
// set the data and properties of link lines and node circles
link = svg.append("g").attr("class", "links").style('stroke', '#aaa')
.selectAll("line")
......@@ -63,7 +63,9 @@ d3.json('/topology', function(data) {
.data(data.devices)
.enter()
.append("image")
.attr('xlink:href', function(d) {return '/static/topology_icons/' + d.type + '.png';})
.attr('xlink:href', function(d) {
return "{{ url_for('static', filename='/topology_icons/') }}" + d.type + ".png";
})
.attr('width', icon_width)
.attr('height', icon_height)
.call(d3.drag().on("start", dragstarted).on("drag", dragged).on("end", dragended));
......
......@@ -79,6 +79,6 @@
<script src="https://d3js.org/d3.v4.min.js"></script>
<div id="topology"></div>
<script src="{{ url_for('static', filename='topology.js') }}"></script>
<script src="{{ url_for('js.topology_js') }}"></script>
{% endblock %}
......@@ -21,7 +21,7 @@
<div class="row mb-3">
<div class="col-sm-3">
<button type="button" class="btn btn-success" onclick="window.location.href = '/service/'">
<button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('service.home') }}'">
<i class="bi bi-box-arrow-in-left"></i>
Back to service list
</button>
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment