diff --git a/src/webui/Config.py b/src/webui/Config.py index e7720a405e2874c3679f9f507df2fdffc610f84a..ffa55f5e3562bff75f2e18e411e2d9229c22f19f 100644 --- a/src/webui/Config.py +++ b/src/webui/Config.py @@ -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)) diff --git a/src/webui/Dockerfile b/src/webui/Dockerfile index c22ba2e8b4fe895a93ee944a740a4108ed1c0e4c..1e55ee411ed7f7c89a3d0ecad982f877c697057f 100644 --- a/src/webui/Dockerfile +++ b/src/webui/Dockerfile @@ -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 diff --git a/src/webui/grafana_dashboard.json b/src/webui/grafana_dashboard.json index afe3ea1260cae8781fea1e29e30e2d1651ab7c2e..a845ac20c7861b86fd1931452b7802b3f1e57aa8 100644 --- a/src/webui/grafana_dashboard.json +++ b/src/webui/grafana_dashboard.json @@ -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, diff --git a/src/webui/service/__init__.py b/src/webui/service/__init__.py index d9755563ad60b2b897d96540a7ceaaa43f4d36dc..9ea6e58214c61fb7f693912097af2b4a17628371 100644 --- a/src/webui/service/__init__.py +++ b/src/webui/service/__init__.py @@ -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 diff --git a/src/webui/service/__main__.py b/src/webui/service/__main__.py index 5dd20aab74751390a11b32e9ae2c63aadb9e364e..9a41c91a1e8a519fef1ed244264c4692cac62756 100644 --- a/src/webui/service/__main__.py +++ b/src/webui/service/__main__.py @@ -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') diff --git a/src/webui/service/device/routes.py b/src/webui/service/device/routes.py index 1f9a6783c54ef14a72e9afb6db8e15b04fa20872..3e78222e9edbcbee6c52e111ac422e92e862011e 100644 --- a/src/webui/service/device/routes.py +++ b/src/webui/service/device/routes.py @@ -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')) diff --git a/src/webui/service/js/__init__.py b/src/webui/service/js/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..70a33251242c51f49140e596b8208a19dd5245f7 --- /dev/null +++ b/src/webui/service/js/__init__.py @@ -0,0 +1,14 @@ +# 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. + diff --git a/src/webui/service/js/routes.py b/src/webui/service/js/routes.py new file mode 100644 index 0000000000000000000000000000000000000000..fee4109df048a29954ce924ef57ec62d6adc27fb --- /dev/null +++ b/src/webui/service/js/routes.py @@ -0,0 +1,25 @@ +# 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') diff --git a/src/webui/service/service/routes.py b/src/webui/service/service/routes.py index 17efe1c0973550f6b185dc4ce0a0f88e9e3a82d8..5eb18b2a1d25f743733dab567671ffe158bb3b57 100644 --- a/src/webui/service/service/routes.py +++ b/src/webui/service/service/routes.py @@ -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')) diff --git a/src/webui/service/templates/base.html b/src/webui/service/templates/base.html index d5f748eae43531bc58951aa44b24c5272d230883..a24edaa541a09c7a22f52d9bf3e705c62c6ef1c6 100644 --- a/src/webui/service/templates/base.html +++ b/src/webui/service/templates/base.html @@ -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> diff --git a/src/webui/service/templates/device/detail.html b/src/webui/service/templates/device/detail.html index 143bbeed70b545f6d1e123efe5f8559a3cc44355..f27ac554b1c975039c2cb481c92debf47ba1591f 100644 --- a/src/webui/service/templates/device/detail.html +++ b/src/webui/service/templates/device/detail.html @@ -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> diff --git a/src/webui/service/static/site.js b/src/webui/service/templates/js/site.js similarity index 100% rename from src/webui/service/static/site.js rename to src/webui/service/templates/js/site.js diff --git a/src/webui/service/static/topology.js b/src/webui/service/templates/js/topology.js similarity index 96% rename from src/webui/service/static/topology.js rename to src/webui/service/templates/js/topology.js index dd58388cd2b7253f328b2ba7f38e007cdcc1007f..05216fb98808d5b574d613344c63a7e19cb2c472 100644 --- a/src/webui/service/static/topology.js +++ b/src/webui/service/templates/js/topology.js @@ -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)); diff --git a/src/webui/service/templates/main/home.html b/src/webui/service/templates/main/home.html index 2134a3a87abde0d8c6af69dbc136819a4fbbf1c1..3cc9fbcffce6cfbb6ebb40dec9d3359f59df5a15 100644 --- a/src/webui/service/templates/main/home.html +++ b/src/webui/service/templates/main/home.html @@ -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 %} diff --git a/src/webui/service/templates/service/detail.html b/src/webui/service/templates/service/detail.html index 77988c74c1f004fe872961ac50aabe9cede8dc65..b7c210c3f3f9c7e3bf5a9b6311e0503d2e1d40e9 100644 --- a/src/webui/service/templates/service/detail.html +++ b/src/webui/service/templates/service/detail.html @@ -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>