From 4bc896fad2d66dfba7cb90ce66c068184d2de571 Mon Sep 17 00:00:00 2001
From: gifrerenom <lluis.gifre@cttc.es>
Date: Fri, 28 Oct 2022 19:35:50 +0000
Subject: [PATCH] WebUI component:

- extneded context selection to context/topology
- enabled selection of context/topology to plot
- enabled listing of devices/links based on context/topology
- updated network icon
---
 src/webui/service/__init__.py                 |   9 ++-
 src/webui/service/device/routes.py            |  28 +++++--
 src/webui/service/link/routes.py              |  28 +++++--
 src/webui/service/main/forms.py               |  25 +++---
 src/webui/service/main/routes.py              |  73 +++++++++++++-----
 .../topology_icons/Acknowledgements.txt       |   3 +-
 .../service/static/topology_icons/network.png | Bin 8988 -> 7520 bytes
 src/webui/service/templates/base.html         |   2 +-
 src/webui/service/templates/main/home.html    |  22 +++---
 9 files changed, 126 insertions(+), 64 deletions(-)

diff --git a/src/webui/service/__init__.py b/src/webui/service/__init__.py
index 75e103642..d60cca659 100644
--- a/src/webui/service/__init__.py
+++ b/src/webui/service/__init__.py
@@ -19,10 +19,10 @@ from context.client.ContextClient import ContextClient
 from device.client.DeviceClient import DeviceClient
 
 def get_working_context() -> str:
-    if 'context_uuid' in session:
-        return session['context_uuid']
-    else:
-        return 'Not selected'
+    return session['context_uuid'] if 'context_uuid' in session else '---'
+
+def get_working_topology() -> str:
+    return session['topology_uuid'] if 'topology_uuid' in session else '---'
 
 def liveness():
     pass
@@ -85,6 +85,7 @@ def create_app(use_config=None, web_app_root=None):
     app.jinja_env.filters['from_json'] = from_json
     
     app.jinja_env.globals.update(get_working_context=get_working_context)
+    app.jinja_env.globals.update(get_working_topology=get_working_topology)
 
     if web_app_root is not None:
         app.wsgi_app = SetSubAppMiddleware(app.wsgi_app, web_app_root)
diff --git a/src/webui/service/device/routes.py b/src/webui/service/device/routes.py
index f1423e92e..b57c5735d 100644
--- a/src/webui/service/device/routes.py
+++ b/src/webui/service/device/routes.py
@@ -16,7 +16,9 @@ from flask import current_app, render_template, Blueprint, flash, session, redir
 from common.proto.context_pb2 import (
     ConfigActionEnum, ConfigRule,
     Device, DeviceDriverEnum, DeviceId, DeviceList, DeviceOperationalStatusEnum,
-    Empty)
+    Empty, TopologyId)
+from common.tools.object_factory.Context import json_context_id
+from common.tools.object_factory.Topology import json_topology_id
 from context.client.ContextClient import ContextClient
 from device.client.DeviceClient import DeviceClient
 from webui.service.device.forms import AddDeviceForm
@@ -27,16 +29,28 @@ device_client = DeviceClient()
 
 @device.get('/')
 def home():
-    context_uuid = session.get('context_uuid', '-')
-    if context_uuid == "-":
+    if 'context_topology_uuid' not in session:
         flash("Please select a context!", "warning")
         return redirect(url_for("main.home"))
+
+    context_uuid = session['context_uuid']
+    topology_uuid = session['topology_uuid']
+
     context_client.connect()
-    response: DeviceList = context_client.ListDevices(Empty())
+    json_topo_id = json_topology_id(topology_uuid, context_id=json_context_id(context_uuid))
+    grpc_topology = context_client.GetTopology(TopologyId(**json_topo_id))
+    topo_device_uuids = {device_id.device_uuid.uuid for device_id in grpc_topology.device_ids}
+    grpc_devices: DeviceList = context_client.ListDevices(Empty())
     context_client.close()
-    return render_template('device/home.html', devices=response.devices,
-                                               dde=DeviceDriverEnum,
-                                               dose=DeviceOperationalStatusEnum)
+
+    devices = [
+        device for device in grpc_devices.devices
+        if device.device_id.device_uuid.uuid in topo_device_uuids
+    ]
+
+    return render_template(
+        'device/home.html', devices=devices, dde=DeviceDriverEnum,
+        dose=DeviceOperationalStatusEnum)
 
 @device.route('add', methods=['GET', 'POST'])
 def add():
diff --git a/src/webui/service/link/routes.py b/src/webui/service/link/routes.py
index 51e903d9e..5b8831b77 100644
--- a/src/webui/service/link/routes.py
+++ b/src/webui/service/link/routes.py
@@ -14,7 +14,9 @@
 
 
 from flask import current_app, render_template, Blueprint, flash, session, redirect, url_for
-from common.proto.context_pb2 import Empty, Link, LinkEvent, LinkId, LinkIdList, LinkList, DeviceId
+from common.proto.context_pb2 import Empty, Link, LinkEvent, LinkId, LinkIdList, LinkList, DeviceId, TopologyId
+from common.tools.object_factory.Context import json_context_id
+from common.tools.object_factory.Topology import json_topology_id
 from context.client.ContextClient import ContextClient
 
 
@@ -23,18 +25,28 @@ context_client = ContextClient()
 
 @link.get('/')
 def home():
-    context_uuid = session.get('context_uuid', '-')
-    if context_uuid == "-":
+    if 'context_topology_uuid' not in session:
         flash("Please select a context!", "warning")
         return redirect(url_for("main.home"))
-    request = Empty()
+
+    context_uuid = session['context_uuid']
+    topology_uuid = session['topology_uuid']
+
     context_client.connect()
-    response = context_client.ListLinks(request)
+    json_topo_id = json_topology_id(topology_uuid, context_id=json_context_id(context_uuid))
+    grpc_topology = context_client.GetTopology(TopologyId(**json_topo_id))
+    topo_link_uuids = {link_id.link_uuid.uuid for link_id in grpc_topology.link_ids}
+    grpc_links: LinkList = context_client.ListLinks(Empty())
     context_client.close()
+
+    links = [
+        link for link in grpc_links.links
+        if link.link_id.link_uuid.uuid in topo_link_uuids
+    ]
+
     return render_template(
-        "link/home.html",
-        links=response.links,
-    )
+        'link/home.html', links=links)
+
 
 @link.route('detail/<path:link_uuid>', methods=('GET', 'POST'))
 def detail(link_uuid: str):
diff --git a/src/webui/service/main/forms.py b/src/webui/service/main/forms.py
index abef11e06..b138592fc 100644
--- a/src/webui/service/main/forms.py
+++ b/src/webui/service/main/forms.py
@@ -19,20 +19,21 @@ from wtforms import SelectField, FileField, SubmitField
 from wtforms.validators import DataRequired, Length
 
 
-class ContextForm(FlaskForm):
-    context = SelectField(  'Context',
-                            choices=[],
-                            validators=[
-                                DataRequired(),
-                                Length(min=1)
-                            ])
-    
+class ContextTopologyForm(FlaskForm):
+    context_topology = SelectField(
+        'Ctx/Topo',
+        choices=[],
+        validators=[
+            DataRequired(),
+            Length(min=1)
+        ])
     submit = SubmitField('Submit')
 
 
 class DescriptorForm(FlaskForm):
-    descriptors = FileField('Descriptors',
-                            validators=[
-                                FileAllowed(['json'], 'JSON Descriptors only!')
-                            ])
+    descriptors = FileField(
+        'Descriptors',
+        validators=[
+            FileAllowed(['json'], 'JSON Descriptors only!')
+        ])
     submit = SubmitField('Submit')
diff --git a/src/webui/service/main/routes.py b/src/webui/service/main/routes.py
index 9b1b08857..1ca5329e3 100644
--- a/src/webui/service/main/routes.py
+++ b/src/webui/service/main/routes.py
@@ -12,10 +12,12 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-import json, logging
+import json, logging, re
 from flask import jsonify, redirect, render_template, Blueprint, flash, session, url_for, request
-from common.proto.context_pb2 import Connection, Context, Device, Empty, Link, Service, Slice, Topology, ContextIdList
+from common.proto.context_pb2 import Connection, Context, Device, Empty, Link, Service, Slice, Topology, ContextIdList, TopologyId, TopologyIdList
 from common.tools.grpc.Tools import grpc_message_to_json_string
+from common.tools.object_factory.Context import json_context_id
+from common.tools.object_factory.Topology import json_topology_id
 from context.client.ContextClient import ContextClient
 from device.client.DeviceClient import DeviceClient
 from service.client.ServiceClient import ServiceClient
@@ -23,7 +25,7 @@ from slice.client.SliceClient import SliceClient
 from webui.service.main.DescriptorTools import (
     format_custom_config_rules, get_descriptors_add_contexts, get_descriptors_add_services, get_descriptors_add_slices,
     get_descriptors_add_topologies, split_devices_by_rules)
-from webui.service.main.forms import ContextForm, DescriptorForm
+from webui.service.main.forms import ContextTopologyForm, DescriptorForm
 
 main = Blueprint('main', __name__)
 
@@ -154,20 +156,34 @@ def process_descriptors(descriptors):
 def home():
     context_client.connect()
     device_client.connect()
-    response: ContextIdList = context_client.ListContextIds(Empty())
-    context_form: ContextForm = ContextForm()
-    context_form.context.choices.append(('', 'Select...'))
+    context_topology_form: ContextTopologyForm = ContextTopologyForm()
+    context_topology_form.context_topology.choices.append(('', 'Select...'))
 
-    for context in response.context_ids:
-        context_form.context.choices.append((context.context_uuid.uuid, context.context_uuid))
+    ctx_response: ContextIdList = context_client.ListContextIds(Empty())
+    for context_id in ctx_response.context_ids:
+        context_uuid = context_id.context_uuid.uuid
+        topo_response: TopologyIdList = context_client.ListTopologyIds(context_id)
+        for topology_id in topo_response.topology_ids:
+            topology_uuid = topology_id.topology_uuid.uuid
+            context_topology_uuid  = 'ctx[{:s}]/topo[{:s}]'.format(context_uuid, topology_uuid)
+            context_topology_name  = 'Context({:s}):Topology({:s})'.format(context_uuid, topology_uuid)
+            context_topology_entry = (context_topology_uuid, context_topology_name)
+            context_topology_form.context_topology.choices.append(context_topology_entry)
 
-    if context_form.validate_on_submit():
-        session['context_uuid'] = context_form.context.data
-        flash(f'The context was successfully set to `{context_form.context.data}`.', 'success')
-        return redirect(url_for("main.home"))
+    if context_topology_form.validate_on_submit():
+        context_topology_uuid = context_topology_form.context_topology.data
+        if len(context_topology_uuid) > 0:
+            match = re.match('ctx\[([^\]]+)\]\/topo\[([^\]]+)\]', context_topology_uuid)
+            if match is not None:
+                session['context_topology_uuid'] = context_topology_uuid = match.group(0)
+                session['context_uuid'] = context_uuid = match.group(1)
+                session['topology_uuid'] = topology_uuid = match.group(2)
+                MSG = f'Context({context_uuid})/Topology({topology_uuid}) successfully selected.'
+                flash(MSG, 'success')
+                return redirect(url_for("main.home"))
 
-    if 'context_uuid' in session:
-        context_form.context.data = session['context_uuid']
+    if 'context_topology_uuid' in session:
+        context_topology_form.context_topology.data = session['context_topology_uuid']
 
     descriptor_form: DescriptorForm = DescriptorForm()
     try:
@@ -181,22 +197,39 @@ def home():
         context_client.close()
         device_client.close()
 
-    return render_template('main/home.html', context_form=context_form, descriptor_form=descriptor_form)
+    return render_template(
+        'main/home.html', context_topology_form=context_topology_form, descriptor_form=descriptor_form)
 
 @main.route('/topology', methods=['GET'])
 def topology():
     context_client.connect()
     try:
+        if 'context_topology_uuid' not in session:
+            return jsonify({'devices': [], 'links': []})
+
+        context_uuid = session['context_uuid']
+        topology_uuid = session['topology_uuid']
+
+        json_topo_id = json_topology_id(topology_uuid, context_id=json_context_id(context_uuid))
+        grpc_topology = context_client.GetTopology(TopologyId(**json_topo_id))
+
+        topo_device_uuids = {device_id.device_uuid.uuid for device_id in grpc_topology.device_ids}
+        topo_link_uuids   = {link_id  .link_uuid  .uuid for link_id   in grpc_topology.link_ids  }
+
         response = context_client.ListDevices(Empty())
-        devices = [{
-            'id': device.device_id.device_uuid.uuid,
-            'name': device.device_id.device_uuid.uuid,
-            'type': device.device_type,
-        } for device in response.devices]
+        devices = []
+        for device in response.devices:
+            if device.device_id.device_uuid.uuid not in topo_device_uuids: continue
+            devices.append({
+                'id': device.device_id.device_uuid.uuid,
+                'name': device.device_id.device_uuid.uuid,
+                'type': device.device_type,
+            })
 
         response = context_client.ListLinks(Empty())
         links = []
         for link in response.links:
+            if link.link_id.link_uuid.uuid not in topo_link_uuids: continue
             if len(link.link_endpoint_ids) != 2:
                 str_link = grpc_message_to_json_string(link)
                 logger.warning('Unexpected link with len(endpoints) != 2: {:s}'.format(str_link))
diff --git a/src/webui/service/static/topology_icons/Acknowledgements.txt b/src/webui/service/static/topology_icons/Acknowledgements.txt
index ddf7a8d0d..932103acc 100644
--- a/src/webui/service/static/topology_icons/Acknowledgements.txt
+++ b/src/webui/service/static/topology_icons/Acknowledgements.txt
@@ -1,6 +1,7 @@
 Network Topology Icons taken from https://vecta.io/symbols
 
-https://symbols.getvecta.com/stencil_240/51_cloud.4d0a827676.png => cloud.png
+https://symbols.getvecta.com/stencil_240/51_cloud.4d0a827676.png => network.png
+    #modified to be grey instead of white
 
 https://symbols.getvecta.com/stencil_240/15_atm-switch.1bbf9a7cca.png => packet-switch.png
 https://symbols.getvecta.com/stencil_241/45_atm-switch.6a7362c1df.png => emu-packet-switch.png
diff --git a/src/webui/service/static/topology_icons/network.png b/src/webui/service/static/topology_icons/network.png
index 0f8e9c9714edd1c11904367ef1e9c60ef7ed3295..1f770f7bb2a31834a191e6c8727f059e1f14bbe1 100644
GIT binary patch
literal 7520
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

literal 8988
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
O0V&C;%T`KbasLBlgSHX?

diff --git a/src/webui/service/templates/base.html b/src/webui/service/templates/base.html
index 5d7801d11..bee98ee82 100644
--- a/src/webui/service/templates/base.html
+++ b/src/webui/service/templates/base.html
@@ -103,7 +103,7 @@
                 </li>
               </ul>
               <span class="navbar-text" style="color: #fff;">
-                Current context: <b>{{ get_working_context() }}</b>
+                Current Context(<b>{{ get_working_context() }}</b>)/Topology(<b>{{ get_working_topology() }}</b>)
               </span>
             </div>
           </div>
diff --git a/src/webui/service/templates/main/home.html b/src/webui/service/templates/main/home.html
index db390939f..43b066cc0 100644
--- a/src/webui/service/templates/main/home.html
+++ b/src/webui/service/templates/main/home.html
@@ -19,7 +19,7 @@
 {% block content %}
     <h2>ETSI TeraFlowSDN Controller</h2>
 
-    {% for field, message in context_form.errors.items() %}
+    {% for field, message in context_topology_form.errors.items() %}
         <div class="alert alert-dismissible fade show" role="alert">
         <b>{{ field }}</b>: {{ message }}
         <button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
@@ -28,32 +28,32 @@
     {% endfor %}
 
     <form id="select_context" method="POST" enctype="multipart/form-data">
-        {{ context_form.hidden_tag() }}
+        {{ context_topology_form.hidden_tag() }}
         <fieldset class="form-group">
-            <legend>Select the working context, or upload a JSON descriptors file</legend>
+            <legend>Select the desired Context/Topology</legend>
             <div class="row mb-3">
-                {{ context_form.context.label(class="col-sm-1 col-form-label") }}
+                {{ context_topology_form.context_topology.label(class="col-sm-1 col-form-label") }}
                 <div class="col-sm-5">
-                    {% if context_form.context.errors %}
-                        {{ context_form.context(class="form-select is-invalid") }}
+                    {% if context_topology_form.context_topology.errors %}
+                        {{ context_topology_form.context_topology(class="form-select is-invalid") }}
                         <div class="invalid-feedback">
-                            {% for error in context_form.context.errors %}
+                            {% for error in context_topology_form.context_topology.errors %}
                                 <span>{{ error }}</span>
                             {% endfor %}
                         </div>
                     {% else %}
-                        {{ context_form.context(class="form-select") }}
+                        {{ context_topology_form.context_topology(class="form-select") }}
                     {% endif %}
                 </div>
                 <div class="col-sm-2">
-                    {{ context_form.submit(class='btn btn-primary') }}
+                    {{ context_topology_form.submit(class='btn btn-primary') }}
                 </div>
             </div>
         </fieldset>
     </form>
 
-    <form id="select_context" method="POST" enctype="multipart/form-data">
-        {{ context_form.hidden_tag() }}
+    <form id="upload_descriptors" method="POST" enctype="multipart/form-data">
+        {{ descriptor_form.hidden_tag() }}
         <fieldset class="form-group">
             <legend>Upload a JSON descriptors file</legend>
             <div class="row mb-3">
-- 
GitLab