Commit cb106875 authored by Lucie LONG's avatar Lucie LONG
Browse files

WebUI component:

- corrected constraint display in service list page
- added list of connections related to service in detail page
- added detail of links
- improved look and feel of device detail page
- added feature of loading services through  JSON file
parent 17219923
Loading
Loading
Loading
Loading
+14 −3
Original line number Diff line number Diff line
@@ -12,10 +12,12 @@
# 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 common.proto.context_pb2 import Empty, LinkList

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 context.client.ContextClient import ContextClient


link = Blueprint('link', __name__, url_prefix='/link')
context_client = ContextClient()

@@ -33,3 +35,12 @@ def home():
        "link/home.html",
        links=response.links,
    )

@link.route('detail/<path:link_uuid>', methods=('GET', 'POST'))
def detail(link_uuid: str):
    request = LinkId()
    request.link_uuid.uuid = link_uuid
    context_client.connect()
    response = context_client.GetLink(request)
    context_client.close()
    return render_template('link/detail.html',link=response)
+58 −32
Original line number Diff line number Diff line
@@ -11,33 +11,45 @@
# 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.

import json, logging
import copy, json, logging
from flask import jsonify, redirect, render_template, Blueprint, flash, session, url_for, request
from common.proto.context_pb2 import Context, Device, Empty, Link, Topology, ContextIdList
from common.proto.context_pb2 import Context, Device, Empty, Link, Service, Topology, ContextIdList
from common.tools.grpc.Tools import grpc_message_to_json_string
from context.client.ContextClient import ContextClient
from device.client.DeviceClient import DeviceClient
from service.client.ServiceClient import ServiceClient
from webui.service.main.forms import ContextForm, DescriptorForm

main = Blueprint('main', __name__)

context_client = ContextClient()
device_client = DeviceClient()

service_client = ServiceClient()
logger = logging.getLogger(__name__)

def process_descriptor(item_name_singluar, item_name_plural, grpc_method, grpc_class, items):
ENTITY_TO_TEXT = {
    # name   => singular,    plural
    'context' : ('Context',  'Contexts'  ),
    'topology': ('Topology', 'Topologies'),
    'device'  : ('Device',   'Devices'   ),
    'link'    : ('Link',     'Links'     ),
    'service' : ('Service',  'Services'  ),
}
ACTION_TO_TEXT = {
    # action => infinitive, past
    'add'     : ('Add',    'Added'),
    'update'  : ('Update', 'Updated'),
}
def process_descriptor(entity_name, action_name, grpc_method, grpc_class, entities):
    entity_name_singluar,entity_name_plural = ENTITY_TO_TEXT[entity_name]
    action_infinitive, action_past = ACTION_TO_TEXT[action_name]
    num_ok, num_err = 0, 0
    for item in items:
    for entity in entities:
        try:
            grpc_method(grpc_class(**item))
            grpc_method(grpc_class(**entity))
            num_ok += 1
        except Exception as e: # pylint: disable=broad-except
            flash(f'Unable to add {item_name_singluar} {str(item)}: {str(e)}', 'error')
            flash(f'Unable to {action_infinitive} {entity_name_singluar} {str(entity)}: {str(e)}', 'error')
            num_err += 1
    if num_ok : flash(f'{str(num_ok)} {item_name_plural} added', 'success')
    if num_err: flash(f'{str(num_err)} {item_name_plural} failed', 'danger')

    if num_ok : flash(f'{str(num_ok)} {entity_name_plural} {action_past}', 'success')
    if num_err: flash(f'{str(num_err)} {entity_name_plural} failed', 'danger')
def process_descriptors(descriptors):
    logger.warning(str(descriptors.data))
    logger.warning(str(descriptors.name))
@@ -52,16 +64,30 @@ def process_descriptors(descriptors):
    except Exception as e: # pylint: disable=broad-except
        flash(f'Unable to load descriptor file: {str(e)}', 'danger')
        return

    contexts   = descriptors.get('contexts'  , [])
    topologies = descriptors.get('topologies', [])
    devices    = descriptors.get('devices'   , [])
    links      = descriptors.get('links'     , [])
    services   = descriptors.get('services'  , [])
    services_add = []
    for service in services:
        service_copy = copy.deepcopy(service)
        service_copy['service_endpoint_ids'] = []
        service_copy['service_constraints'] = []
        service_copy['service_config'] = {'config_rules': []}
        services_add.append(service_copy)
    context_client.connect()
    device_client.connect()
    process_descriptor('Context',  'Contexts',   context_client.SetContext,  Context,  descriptors['contexts'  ])
    process_descriptor('Topology', 'Topologies', context_client.SetTopology, Topology, descriptors['topologies'])
    process_descriptor('Device',   'Devices',    device_client .AddDevice,   Device,   descriptors['devices'   ])
    process_descriptor('Link',     'Links',      context_client.SetLink,     Link,     descriptors['links'     ])
    service_client.connect()
    process_descriptor('context',  'add',    context_client.SetContext,    Context,  contexts    )
    process_descriptor('topology', 'add',    context_client.SetTopology,   Topology, topologies  )
    process_descriptor('device',   'add',    device_client .AddDevice,     Device,   devices     )
    process_descriptor('link',     'add',    context_client.SetLink,       Link,     links       )
    process_descriptor('service',  'add',    service_client.CreateService, Service,  services_add)
    process_descriptor('service',  'update', service_client.UpdateService, Service,  services    )
    service_client.close()
    device_client.close()
    context_client.close()

@main.route('/', methods=['GET', 'POST'])
def home():
    context_client.connect()
@@ -89,7 +115,6 @@ def home():
        context_client.close()
        device_client.close()
    return render_template('main/home.html', context_form=context_form, descriptor_form=descriptor_form)

@main.route('/topology', methods=['GET'])
def topology():
    context_client.connect()
@@ -100,28 +125,29 @@ def topology():
            'name': device.device_id.device_uuid.uuid,
            'type': device.device_type,
        } for device in response.devices]

        response = context_client.ListLinks(Empty())
        links = [{
        links = []
        for link in response.links:
            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))
                continue
            links.append({
                'id': link.link_id.link_uuid.uuid,
                'source': link.link_endpoint_ids[0].device_id.device_uuid.uuid,
                'target': link.link_endpoint_ids[1].device_id.device_uuid.uuid,
        } for link in response.links]

            })
        return jsonify({'devices': devices, 'links': links})
    except:
        logger.exception('Error retrieving topology')
    finally:
        context_client.close()

@main.get('/about')
def about():
    return render_template('main/about.html')

@main.get('/debug')
def debug():
    return render_template('main/debug.html')

@main.get('/resetsession')
def reset_session():
    session.clear()
+4 −3
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@

import grpc
from flask import current_app, redirect, render_template, Blueprint, flash, session, url_for
from common.proto.context_pb2 import ContextId, Service, ServiceId, ServiceList, ServiceTypeEnum, ServiceStatusEnum
from common.proto.context_pb2 import ContextId, Service, ServiceId, ServiceList, ServiceTypeEnum, ServiceStatusEnum, Connection
from context.client.ContextClient import ContextClient
from service.client.ServiceClient import ServiceClient

@@ -73,12 +73,13 @@ def detail(service_uuid: str):
    try:
        context_client.connect()
        response: Service = context_client.GetService(request)
        connections: Connection = context_client.ListConnections(request)
        context_client.close()
    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'))
    return render_template('service/detail.html', service=response)
    return render_template('service/detail.html', service=response, connections=connections)


@service.get('<path:service_uuid>/delete')
+120 −102
Original line number Diff line number Diff line
@@ -40,54 +40,71 @@
           </div>
       </div>

       <br>
       <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">
            <div class="col-sm-4">
                <b>UUID: </b>{{ device.device_id.device_uuid.uuid }}<br><br>
                <b>Type: </b>{{ device.device_type }}<br><br>
                <b>Drivers: </b>
                <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>
            <div class="col-sm-8">
                    <table class="table table-striped table-hover">
                        <thead>
                            <tr>
                                <th scope="col">Endpoints</th>
                                <th scope="col">Type</th>
                            </tr>
                        </thead>
                        <tbody>
                            {% for endpoint in device.device_endpoints %}
                <li>{{ endpoint.endpoint_id.endpoint_uuid.uuid }}: {{ endpoint.endpoint_type }}</li>
                            <tr>
                                <td>
                                    {{ endpoint.endpoint_id.endpoint_uuid.uuid }}
                                </td>
                                <td>
                                    {{ endpoint.endpoint_type }}
                                </td>
                            </tr>
                            {% endfor %}
            </ul>
                        </tbody>
                    </table>
                </div> 
            </div>
    <div class="row mb-3">
        </div>

        <b>Configurations:</b>
        <div class="col-sm-10">
            <ul>
        <table class="table table-striped table-hover">
            <thead>
                <tr>
                    <th scope="col">Key</th>
                    <th scope="col">Value</th>
                </tr>
            </thead>
            <tbody>
                {% for config in device.device_config.config_rules %}
                {% if config.WhichOneof('config_rule') == 'custom' %}
                <li>{{ config.custom.resource_key }}:
                <tr>
                    <td>
                        {{ config.custom.resource_key }}
                    </td>
                    <td>
                        <ul>
                            {% for key, value in (config.custom.resource_value | from_json).items() %}
                            <li><b>{{ key }}:</b> {{ value }}</li>
                            {% endfor %}
                        </ul>
                </li>
                    </td>
                </tr>
                {% endif %}
                {% endfor %}
            </ul>
        </div>
    </div>
            </tbody>
        </table>


       <!-- Modal -->
   <div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" aria-labelledby="staticBackdropLabel" aria-hidden="true">
@@ -109,3 +126,4 @@
     </div>
   
   {% endblock %}
   
 No newline at end of file
+59 −0
Original line number Diff line number Diff line
<!--
    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>Link {{ link.link_id.link_uuid.uuid }}</h1>
    <div class="row mb-3">
          <div class="col-sm-3">
               <button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('link.home') }}'">
                    <i class="bi bi-box-arrow-in-left"></i>
                    Back to link list
               </button>
          </div>
     </div>

     <br>
       <div class="row mb-3">
            <div class="col-sm-4">
                <b>UUID: </b>{{ link.link_id.link_uuid.uuid }}<br><br>
            </div>
            <div class="col-sm-8">
                    <table class="table table-striped table-hover">
                        <thead>
                            <tr>
                                <th scope="col">Endpoints</th>
                                <th scope="col">Type</th>
                            </tr>
                        </thead>
                        <tbody>
                              {% for end_point in link.link_endpoint_ids %}
                              <tr>
                                   <td>
                                        {{ end_point.endpoint_uuid.uuid }} 
                                   </td>
                                   <td>
                                        {{ end_point.endpoint_uuid.uuid }}
                                   </td>
                              </tr>
                              {% endfor %}
                        </tbody>
                    </table>
            </div>
        </div>

   {% endblock %}
   
 No newline at end of file
Loading