diff --git a/configure_dashboards.sh b/configure_dashboards.sh index edfdfa370538cfb7129e365be5062f16291a67d7..f0e31dd8c3af5b94de4c674b5919ca0b03558c2e 100755 --- a/configure_dashboards.sh +++ b/configure_dashboards.sh @@ -25,8 +25,8 @@ INFLUXDB_DATABASE=$(kubectl --namespace $K8S_NAMESPACE get secrets influxdb-secr # GRAFANA_HOSTNAME=$(kubectl get node $K8S_HOSTNAME -o 'jsonpath={.status.addresses[?(@.type=="InternalIP")].address}') # GRAFANA_HOSTNAME=`kubectl get service/webuiservice-public -n ${K8S_NAMESPACE} -o jsonpath='{.spec.clusterIP}'` GRAFANA_HOSTNAME=`kubectl get nodes --selector=node-role.kubernetes.io/master -o jsonpath='{$.items[*].status.addresses[?(@.type=="InternalIP")].address}'` -# GRAFANA_PORT=$(kubectl get service webuiservice-public --namespace $K8S_NAMESPACE -o 'jsonpath={.spec.ports[?(@.port==3000)].nodePort}') -GRAFANA_PORT=`kubectl get service/webuiservice-public -n ${K8S_NAMESPACE} -o jsonpath='{.spec.ports[1].nodePort}'` +GRAFANA_PORT=$(kubectl get service webuiservice-public --namespace $K8S_NAMESPACE -o 'jsonpath={.spec.ports[?(@.port==3000)].nodePort}') +# GRAFANA_PORT=`kubectl get service/webuiservice-public -n ${K8S_NAMESPACE} -o jsonpath='{.spec.ports[1].nodePort}'` GRAFANA_USERNAME="admin" GRAFANA_PASSWORD=${GRAFANA_PASSWORD:-"admin123+"} GRAFANA_URL="http://${GRAFANA_USERNAME}:${GRAFANA_PASSWORD}@${GRAFANA_HOSTNAME}:${GRAFANA_PORT}" diff --git a/manifests/expose_services.yaml b/manifests/expose_services.yaml deleted file mode 100644 index 7e5d2236ab3be6928cd9247a8f5a6e8220a4d1ab..0000000000000000000000000000000000000000 --- a/manifests/expose_services.yaml +++ /dev/null @@ -1,90 +0,0 @@ ---- -apiVersion: v1 -kind: Service -metadata: - name: contextservice-public - labels: - app: contextservice -spec: - type: NodePort - selector: - app: contextservice - ports: - - name: grpc - protocol: TCP - port: 1010 - targetPort: 1010 - - name: http - protocol: TCP - port: 8080 - targetPort: 8080 - nodePort: 30808 ---- -apiVersion: v1 -kind: Service -metadata: - name: redis-tests - labels: - app: contextservice -spec: - type: NodePort - selector: - app: contextservice - ports: - - name: redis - protocol: TCP - port: 6379 - targetPort: 6379 ---- -apiVersion: v1 -kind: Service -metadata: - name: influx-tests - labels: - app: monitoringservice -spec: - type: NodePort - selector: - app: monitoringservice - ports: - - name: influx - protocol: TCP - port: 8086 - targetPort: 8086 ---- -apiVersion: v1 -kind: Service -metadata: - name: deviceservice-public - labels: - app: deviceservice -spec: - type: NodePort - selector: - app: deviceservice - ports: - - name: grpc - protocol: TCP - port: 2020 - targetPort: 2020 ---- -apiVersion: v1 -kind: Service -metadata: - name: computeservice-public - labels: - app: computeservice -spec: - type: NodePort - selector: - app: computeservice - ports: - - name: http - protocol: TCP - port: 8080 - targetPort: 8080 - - name: grpc - protocol: TCP - port: 9090 - targetPort: 9090 ---- \ No newline at end of file diff --git a/src/webui/service/__init__.py b/src/webui/service/__init__.py index 24579b3d756498ec994b7875357d2315ddda260c..ad1171d1e5b3daae6054ac24cbb190e104c8a745 100644 --- a/src/webui/service/__init__.py +++ b/src/webui/service/__init__.py @@ -13,6 +13,8 @@ # limitations under the License. import os +import json + from flask import Flask, session from flask_healthz import healthz, HealthError @@ -46,6 +48,10 @@ def readiness(): raise HealthError('Can\'t connect with the service: ' + e.details()) +def from_json(json_str): + return json.loads(json_str) + + def create_app(use_config=None): app = Flask(__name__) if use_config: @@ -67,6 +73,11 @@ def create_app(use_config=None): from webui.service.device.routes import device app.register_blueprint(device) + from webui.service.link.routes import link + app.register_blueprint(link) + + app.jinja_env.filters['from_json'] = from_json + app.jinja_env.globals.update(get_working_context=get_working_context) return app diff --git a/src/webui/service/device/routes.py b/src/webui/service/device/routes.py index f4e58d3b0f92ef4e002c086ccc6092c9d5d397ef..d3306d0c12d31975eda735fbd83ffd6de8fbdcbc 100644 --- a/src/webui/service/device/routes.py +++ b/src/webui/service/device/routes.py @@ -33,7 +33,7 @@ def home(): flash("Please select a context!", "warning") return redirect(url_for("main.home")) request: ContextId = ContextId() - request.context_uuid.uuid = session.get('context_uuid', '-') + request.context_uuid.uuid = context_uuid context_client.connect() response: DeviceList = context_client.ListDevices(request) context_client.close() @@ -98,7 +98,7 @@ def add(): device_client.close() flash(f'New device was created with ID "{response.device_uuid.uuid}".', 'success') - return redirect('/device/') + return redirect(url_for('device.home')) except Exception as e: flash(f'Problem adding the device. {e.details()}', 'danger') @@ -106,16 +106,18 @@ def add(): submit_text='Add New Device', device_driver_ids=device_driver_ids) -@device.route('detail/<device_uuid>', methods=['GET', 'POST']) +@device.route('detail/<path:device_uuid>', methods=['GET', 'POST']) def detail(device_uuid: str): request: DeviceId = DeviceId() request.device_uuid.uuid = device_uuid context_client.connect() response: Device = context_client.GetDevice(request) context_client.close() - return render_template('device/detail.html', device=response) + return render_template('device/detail.html', device=response, + dde=DeviceDriverEnum, + dose=DeviceOperationalStatusEnum) -@device.get('<device_uuid>/delete') +@device.get('<path:device_uuid>/delete') def delete(device_uuid): try: @@ -136,4 +138,4 @@ def delete(device_uuid): except Exception as e: flash(f'Problem deleting the device. {e.details()}', 'danger') - return redirect('/device/') + return redirect(url_for('device.home')) diff --git a/src/webui/service/link/__init__.py b/src/webui/service/link/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..e69de29bb2d1d6434b8b29ae775ad8c2e48c5391 diff --git a/src/webui/service/link/routes.py b/src/webui/service/link/routes.py new file mode 100644 index 0000000000000000000000000000000000000000..91157a0b9450dcffe395c78b434875f9254caeed --- /dev/null +++ b/src/webui/service/link/routes.py @@ -0,0 +1,37 @@ +# 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, flash, session, redirect, url_for +from device.client.DeviceClient import DeviceClient +from context.client.ContextClient import ContextClient +from webui.Config import (CONTEXT_SERVICE_ADDRESS, CONTEXT_SERVICE_PORT) +from webui.proto.context_pb2 import (Empty, LinkList) + +link = Blueprint('link', __name__, url_prefix='/link') +context_client: ContextClient = ContextClient(CONTEXT_SERVICE_ADDRESS, CONTEXT_SERVICE_PORT) + +@link.get('/') +def home(): + context_uuid = session.get('context_uuid', '-') + if context_uuid == "-": + flash("Please select a context!", "warning") + return redirect(url_for("main.home")) + request: Empty = Empty() + context_client.connect() + response: LinkList = context_client.ListLinks(request) + context_client.close() + return render_template( + "link/home.html", + links=response.links, + ) \ No newline at end of file diff --git a/src/webui/service/service/routes.py b/src/webui/service/service/routes.py index d16dccdf8c3a321c47200c4098fda28057a1d079..eb9f9dad86b6528dad649053a192fcfde1b92583 100644 --- a/src/webui/service/service/routes.py +++ b/src/webui/service/service/routes.py @@ -13,7 +13,8 @@ # limitations under the License. import grpc -from flask import redirect, render_template, Blueprint, flash, session, url_for +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 context.client.ContextClient import ContextClient from webui.proto.context_pb2 import ContextId, ServiceList, ServiceTypeEnum, ServiceStatusEnum, ConfigActionEnum @@ -60,8 +61,22 @@ def add(): return render_template('service/home.html') -@service.get('detail/<service_uuid>') +@service.get('detail/<path:service_uuid>') def detail(service_uuid: str): - flash('Detail service route called', 'danger') - raise NotImplementedError() - return render_template('service/home.html') + context_uuid = session.get('context_uuid', '-') + if context_uuid == "-": + flash("Please select a context!", "warning") + return redirect(url_for("main.home")) + + request: ServiceId = ServiceId() + request.service_uuid.uuid = service_uuid + request.context_id.context_uuid.uuid = context_uuid + try: + context_client.connect() + response: Service = context_client.GetService(request) + context_client.close() + return render_template('service/detail.html', service=response) + except Exception as e: + flash('The system encountered an error and cannot show the details of this service.', 'warning') + 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 3fdc5e8753a27f5ac73868fd9515bf05aa4561d5..dd28d0e2c9c3904f231e953db29bdd31e2770137 100644 --- a/src/webui/service/templates/base.html +++ b/src/webui/service/templates/base.html @@ -60,7 +60,13 @@ {% else %} <a class="nav-link" href="{{ url_for('device.home') }}">Device</a> {% endif %} - <!-- <a class="nav-link" href="{{ url_for('service.home') }}">Service</a> --> + </li> + <li class="nav-item"> + {% if '/link/' in request.path %} + <a class="nav-link active" aria-current="page" href="{{ url_for('link.home') }}">Link</a> + {% else %} + <a class="nav-link" href="{{ url_for('link.home') }}">Link</a> + {% endif %} </li> <li class="nav-item"> {% if '/service/' in request.path %} @@ -68,7 +74,6 @@ {% else %} <a class="nav-link" href="{{ url_for('service.home') }}">Service</a> {% endif %} - <!-- <a class="nav-link" href="{{ url_for('service.home') }}">Service</a> --> </li> <!-- <li class="nav-item"> diff --git a/src/webui/service/templates/device/detail.html b/src/webui/service/templates/device/detail.html index af82ede821afb3f576196bbd3561a6dea2a13eae..143bbeed70b545f6d1e123efe5f8559a3cc44355 100644 --- a/src/webui/service/templates/device/detail.html +++ b/src/webui/service/templates/device/detail.html @@ -41,15 +41,23 @@ </div> <div class="row mb-3"> - <b>UUID:</b> - <div class="col-sm-10"> + <div class="col-sm-1"><b>UUID:</b></div> + <div class="col-sm-5"> {{ device.device_id.device_uuid.uuid }} </div> + <div class="col-sm-1"><b>Type:</b></div> + <div class="col-sm-5"> + {{ device.device_type }} + </div> </div> <div class="row mb-3"> - <b>Type:</b> - <div class="col-sm-10"> - {{ device.device_type }} + <div class="col-sm-1"><b>Drivers:</b></div> + <div class="col-sm-11"> + <ul> + {% for driver in device.device_drivers %} + <li>{{ dde.Name(driver).replace('DEVICEDRIVER_', '').replace('UNDEFINED', 'EMULATED') }}</li> + {% endfor %} + </ul> </div> </div> <div class="row mb-3"> @@ -67,7 +75,13 @@ <div class="col-sm-10"> <ul> {% for config in device.device_config.config_rules %} - <li>{{ config.resource_key }}: {{ config.resource_value }}</li> + <li>{{ config.resource_key }}: + <ul> + {% for key, value in (config.resource_value | from_json).items() %} + <li><b>{{ key }}:</b> {{ value }}</li> + {% endfor %} + </ul> + </li> {% endfor %} </ul> </div> diff --git a/src/webui/service/templates/device/home.html b/src/webui/service/templates/device/home.html index a05cac8b99bf1135f80add68d4280ccfba021220..2c108add96df7de413f5310d4bd9e3c3fb69a6ed 100644 --- a/src/webui/service/templates/device/home.html +++ b/src/webui/service/templates/device/home.html @@ -73,7 +73,7 @@ <td> <ul> {% for driver in device.device_drivers %} - <li>{{ dde.Name(driver).replace('DEVICEDRIVER_', '') }}</li> + <li>{{ dde.Name(driver).replace('DEVICEDRIVER_', '').replace('UNDEFINED', 'EMULATED') }}</li> {% endfor %} </ul> </td> diff --git a/src/webui/service/templates/link/home.html b/src/webui/service/templates/link/home.html new file mode 100644 index 0000000000000000000000000000000000000000..9e9814d62d323d046bd04eb98070c019e5e31569 --- /dev/null +++ b/src/webui/service/templates/link/home.html @@ -0,0 +1,98 @@ +<!-- + 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. +--> + +{% extends 'base.html' %} + +{% block content %} + <h1>Links</h1> + + <div class="row"> + <div class="col"> + <a href="{{ url_for('link.add') }}" class="btn btn-primary" style="margin-bottom: 10px;"> + <i class="bi bi-plus"></i> + Add New Link + </a> + </div> + <div class="col"> + {{ links | length }} links found</i> + </div> + <!-- <div class="col"> + <form> + <div class="input-group"> + <input type="text" aria-label="Search" placeholder="Search..." class="form-control"/> + <button type="submit" class="btn btn-primary">Search</button> + </div> + </form> + </div> --> + </div> + + <table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">#</th> + <th scope="col">Type</th> + <th scope="col">Endpoints</th> + <th scope="col">Drivers</th> + <th scope="col">Status</th> + <th scope="col"></th> + </tr> + </thead> + <tbody> + {% if devices %} + {% for device in devices %} + <tr> + <td> + <!-- <a href="{{ url_for('device.detail', device_uuid=device.device_id.device_uuid.uuid) }}"> --> + {{ device.device_id.device_uuid.uuid }} + <!-- </a> --> + </td> + <td> + {{ device.device_type }} + </td> + <td> + <ul> + {% for end_point in device.device_endpoints %} + <li>{{ end_point.endpoint_id.endpoint_uuid.uuid }}</li> + {% endfor %} + </ul> + </td> + <td> + <ul> + {% for driver in device.device_drivers %} + <li>{{ dde.Name(driver).replace('DEVICEDRIVER_', '').replace('UNDEFINED', 'EMULATED') }}</li> + {% endfor %} + </ul> + </td> + <td>{{ dose.Name(device.device_operational_status).replace('DEVICEOPERATIONALSTATUS_', '') }}</td> + <td> + <a href="{{ url_for('device.detail', device_uuid=device.device_id.device_uuid.uuid) }}"> + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16"> + <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/> + <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/> + </svg> + </a> + </td> + </tr> + {% endfor %} + {% else %} + <tr> + <td colspan="7">No devices found</td> + </tr> + {% endif %} + </tbody> + </table> + +{% endblock %} \ No newline at end of file diff --git a/src/webui/service/templates/service/detail.html b/src/webui/service/templates/service/detail.html new file mode 100644 index 0000000000000000000000000000000000000000..ffc951ad6f23e4e4d0c0b1172debdf86ba278005 --- /dev/null +++ b/src/webui/service/templates/service/detail.html @@ -0,0 +1,109 @@ +<!-- + 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. +--> + +{% extends 'base.html' %} + +{% block content %} + <h1>Service {{ service.service_uuid.uuid }}</h1> + + <div class="row mb-3"> + <div class="col-sm-3"> + <button type="button" class="btn btn-success" onclick="window.location.href = '/device/'"> + <i class="bi bi-box-arrow-in-left"></i> + Back to device list + </button> + </div> + <div class="col-sm-3"> + <a id="update" class="btn btn-secondary" href="#"> + <i class="bi bi-pencil-square"></i> + Update + </a> + </div> + <div class="col-sm-3"> + <!-- <button type="button" class="btn btn-danger"><i class="bi bi-x-square"></i>Delete device</button> --> + <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal"> + <i class="bi bi-x-square"></i>Delete device + </button> + </div> + </div> + + <div class="row mb-3"> + <div class="col-sm-1"><b>UUID:</b></div> + <div class="col-sm-5"> + {{ device.device_id.device_uuid.uuid }} + </div> + <div class="col-sm-1"><b>Type:</b></div> + <div class="col-sm-5"> + {{ device.device_type }} + </div> + </div> + <div class="row mb-3"> + <div class="col-sm-1"><b>Drivers:</b></div> + <div class="col-sm-11"> + <ul> + {% for driver in device.device_drivers %} + <li>{{ dde.Name(driver).replace('DEVICEDRIVER_', '').replace('UNDEFINED', 'EMULATED') }}</li> + {% endfor %} + </ul> + </div> + </div> + <div class="row mb-3"> + <b>Endpoints:</b> + <div class="col-sm-10"> + <ul> + {% for endpoint in device.device_endpoints %} + <li>{{ endpoint.endpoint_id.endpoint_uuid.uuid }}: {{ endpoint.endpoint_type }}</li> + {% endfor %} + </ul> + </div> + </div> + <div class="row mb-3"> + <b>Configurations:</b> + <div class="col-sm-10"> + <ul> + {% for config in device.device_config.config_rules %} + <li>{{ config.resource_key }}: + <ul> + {% for key, value in (config.resource_value | from_json).items() %} + <li><b>{{ key }}:</b> {{ value }}</li> + {% endfor %} + </ul> + </li> + {% endfor %} + </ul> + </div> + </div> + + <!-- Modal --> +<div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="staticBackdropLabel">Delete device?</h5> + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> + </div> + <div class="modal-body"> + Are you sure you want to delete the device "{{ device.device_id.device_uuid.uuid }}"? + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button> + <a type="button" class="btn btn-danger" href="{{ url_for('device.delete', device_uuid=device.device_id.device_uuid.uuid) }}"><i class="bi bi-exclamation-diamond"></i>Yes</a> + </div> + </div> + </div> + </div> + +{% endblock %} \ No newline at end of file diff --git a/src/webui/service/templates/service/home.html b/src/webui/service/templates/service/home.html index 13fad6625a69a958aacd94929df9ac61295a718b..0e152006c149df35d477ecfb81bb4fcc0b562d9a 100644 --- a/src/webui/service/templates/service/home.html +++ b/src/webui/service/templates/service/home.html @@ -48,8 +48,7 @@ <th scope="col">End points</th> <th scope="col">Constraints</th> <th scope="col">Status</th> - <!-- <th scope="col">Configuration</th> --> - <!-- <th scope="col"></th> --> + <th scope="col"></th> </tr> </thead> <tbody> @@ -79,25 +78,14 @@ </ul> </td> <td>{{ sse.Name(service.service_status.service_status).replace('SERVICESTATUS_', '') }}</td> - <!-- <td> - <ul> - {% for rule in service.service_config.config_rules %} - <li> - Key: {{ rule.resource_key }} - <br/> - Value: {{ rule.resource_value }} - </li> - {% endfor %} - </ul> - </td> --> - <!-- <td> - <a href="{{ url_for('service.detail', service_uuid=service.service_id.service_uuid.uuid.replace('/', '_')) }}"> + <td> + <a href="{{ url_for('service.detail', service_uuid=service.service_id.service_uuid.uuid) }}"> <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16"> <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/> <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/> </svg> </a> - </td> --> + </td> </tr> {% endfor %} {% else %}