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

Merge branch 'feat/webui' of https://gitlab.com/teraflow-h2020/controller into...

Merge branch 'feat/webui' of https://gitlab.com/teraflow-h2020/controller into feat/ecoc22-dc-interconnect-disjoint
parents 8f697710 cb106875
No related branches found
No related tags found
2 merge requests!54Release 2.0.0,!4Compute component:
...@@ -12,10 +12,12 @@ ...@@ -12,10 +12,12 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # 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 from context.client.ContextClient import ContextClient
link = Blueprint('link', __name__, url_prefix='/link') link = Blueprint('link', __name__, url_prefix='/link')
context_client = ContextClient() context_client = ContextClient()
...@@ -32,4 +34,13 @@ def home(): ...@@ -32,4 +34,13 @@ def home():
return render_template( return render_template(
"link/home.html", "link/home.html",
links=response.links, links=response.links,
) )
\ No newline at end of file
@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)
...@@ -11,33 +11,45 @@ ...@@ -11,33 +11,45 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import copy, json, logging
import json, logging
from flask import jsonify, redirect, render_template, Blueprint, flash, session, url_for, request 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 context.client.ContextClient import ContextClient
from device.client.DeviceClient import DeviceClient from device.client.DeviceClient import DeviceClient
from service.client.ServiceClient import ServiceClient
from webui.service.main.forms import ContextForm, DescriptorForm from webui.service.main.forms import ContextForm, DescriptorForm
main = Blueprint('main', __name__) main = Blueprint('main', __name__)
context_client = ContextClient() context_client = ContextClient()
device_client = DeviceClient() device_client = DeviceClient()
service_client = ServiceClient()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
ENTITY_TO_TEXT = {
def process_descriptor(item_name_singluar, item_name_plural, grpc_method, grpc_class, items): # 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 num_ok, num_err = 0, 0
for item in items: for entity in entities:
try: try:
grpc_method(grpc_class(**item)) grpc_method(grpc_class(**entity))
num_ok += 1 num_ok += 1
except Exception as e: # pylint: disable=broad-except 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 num_err += 1
if num_ok : flash(f'{str(num_ok)} {item_name_plural} added', 'success') if num_ok : flash(f'{str(num_ok)} {entity_name_plural} {action_past}', 'success')
if num_err: flash(f'{str(num_err)} {item_name_plural} failed', 'danger') if num_err: flash(f'{str(num_err)} {entity_name_plural} failed', 'danger')
def process_descriptors(descriptors): def process_descriptors(descriptors):
logger.warning(str(descriptors.data)) logger.warning(str(descriptors.data))
logger.warning(str(descriptors.name)) logger.warning(str(descriptors.name))
...@@ -52,16 +64,30 @@ def process_descriptors(descriptors): ...@@ -52,16 +64,30 @@ def process_descriptors(descriptors):
except Exception as e: # pylint: disable=broad-except except Exception as e: # pylint: disable=broad-except
flash(f'Unable to load descriptor file: {str(e)}', 'danger') flash(f'Unable to load descriptor file: {str(e)}', 'danger')
return 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() context_client.connect()
device_client.connect() device_client.connect()
process_descriptor('Context', 'Contexts', context_client.SetContext, Context, descriptors['contexts' ]) service_client.connect()
process_descriptor('Topology', 'Topologies', context_client.SetTopology, Topology, descriptors['topologies']) process_descriptor('context', 'add', context_client.SetContext, Context, contexts )
process_descriptor('Device', 'Devices', device_client .AddDevice, Device, descriptors['devices' ]) process_descriptor('topology', 'add', context_client.SetTopology, Topology, topologies )
process_descriptor('Link', 'Links', context_client.SetLink, Link, descriptors['links' ]) 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() device_client.close()
context_client.close() context_client.close()
@main.route('/', methods=['GET', 'POST']) @main.route('/', methods=['GET', 'POST'])
def home(): def home():
context_client.connect() context_client.connect()
...@@ -89,7 +115,6 @@ def home(): ...@@ -89,7 +115,6 @@ def home():
context_client.close() context_client.close()
device_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_form=context_form, descriptor_form=descriptor_form)
@main.route('/topology', methods=['GET']) @main.route('/topology', methods=['GET'])
def topology(): def topology():
context_client.connect() context_client.connect()
...@@ -100,29 +125,30 @@ def topology(): ...@@ -100,29 +125,30 @@ def topology():
'name': device.device_id.device_uuid.uuid, 'name': device.device_id.device_uuid.uuid,
'type': device.device_type, 'type': device.device_type,
} for device in response.devices] } for device in response.devices]
response = context_client.ListLinks(Empty()) response = context_client.ListLinks(Empty())
links = [{ links = []
'id': link.link_id.link_uuid.uuid, for link in response.links:
'source': link.link_endpoint_ids[0].device_id.device_uuid.uuid, if len(link.link_endpoint_ids) != 2:
'target': link.link_endpoint_ids[1].device_id.device_uuid.uuid, str_link = grpc_message_to_json_string(link)
} for link in response.links] 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,
})
return jsonify({'devices': devices, 'links': links}) return jsonify({'devices': devices, 'links': links})
except: except:
logger.exception('Error retrieving topology') logger.exception('Error retrieving topology')
finally: finally:
context_client.close() context_client.close()
@main.get('/about') @main.get('/about')
def about(): def about():
return render_template('main/about.html') return render_template('main/about.html')
@main.get('/debug') @main.get('/debug')
def debug(): def debug():
return render_template('main/debug.html') return render_template('main/debug.html')
@main.get('/resetsession') @main.get('/resetsession')
def reset_session(): def reset_session():
session.clear() session.clear()
return redirect(url_for("main.home")) return redirect(url_for("main.home"))
\ No newline at end of file
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
import grpc import grpc
from flask import current_app, redirect, render_template, Blueprint, flash, session, url_for 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 context.client.ContextClient import ContextClient
from service.client.ServiceClient import ServiceClient from service.client.ServiceClient import ServiceClient
...@@ -73,12 +73,13 @@ def detail(service_uuid: str): ...@@ -73,12 +73,13 @@ def detail(service_uuid: str):
try: try:
context_client.connect() context_client.connect()
response: Service = context_client.GetService(request) response: Service = context_client.GetService(request)
connections: Connection = context_client.ListConnections(request)
context_client.close() context_client.close()
except Exception as e: except Exception as e:
flash('The system encountered an error and cannot show the details of this service.', 'warning') flash('The system encountered an error and cannot show the details of this service.', 'warning')
current_app.logger.exception(e) current_app.logger.exception(e)
return redirect(url_for('service.home')) 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') @service.get('<path:service_uuid>/delete')
...@@ -100,4 +101,4 @@ def delete(service_uuid: str): ...@@ -100,4 +101,4 @@ def delete(service_uuid: str):
except Exception as e: except Exception as e:
flash('Problem deleting service "{:s}": {:s}'.format(service_uuid, str(e.details())), 'danger') flash('Problem deleting service "{:s}": {:s}'.format(service_uuid, str(e.details())), 'danger')
current_app.logger.exception(e) current_app.logger.exception(e)
return redirect(url_for('service.home')) return redirect(url_for('service.home'))
\ No newline at end of file
<!-- <!--
Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) 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>Device {{ device.device_id.device_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('device.home') }}'">
<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>
Licensed under the Apache License, Version 2.0 (the "License"); <br>
you may not use this file except in compliance with the License. <div class="row mb-3">
You may obtain a copy of the License at <div class="col-sm-4">
<b>UUID: </b>{{ device.device_id.device_uuid.uuid }}<br><br>
http://www.apache.org/licenses/LICENSE-2.0 <b>Type: </b>{{ device.device_type }}<br><br>
<b>Drivers: </b>
Unless required by applicable law or agreed to in writing, software <ul>
distributed under the License is distributed on an "AS IS" BASIS, {% for driver in device.device_drivers %}
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. <li>{{ dde.Name(driver).replace('DEVICEDRIVER_', '').replace('UNDEFINED', 'EMULATED') }}</li>
See the License for the specific language governing permissions and {% endfor %}
limitations under the License. </ul>
--> </div>
<div class="col-sm-8">
{% extends 'base.html' %} <table class="table table-striped table-hover">
<thead>
{% block content %} <tr>
<h1>Device {{ device.device_id.device_uuid.uuid }}</h1> <th scope="col">Endpoints</th>
<th scope="col">Type</th>
<div class="row mb-3"> </tr>
<div class="col-sm-3"> </thead>
<button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('device.home') }}'"> <tbody>
<i class="bi bi-box-arrow-in-left"></i> {% for endpoint in device.device_endpoints %}
Back to device list <tr>
</button> <td>
{{ endpoint.endpoint_id.endpoint_uuid.uuid }}
</td>
<td>
{{ endpoint.endpoint_type }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div> </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> <b>Configurations:</b>
<div class="col-sm-10"> <table class="table table-striped table-hover">
<ul> <thead>
{% for config in device.device_config.config_rules %} <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' %} {% if config.WhichOneof('config_rule') == 'custom' %}
<li>{{ config.custom.resource_key }}: <tr>
<ul> <td>
{% for key, value in (config.custom.resource_value | from_json).items() %} {{ config.custom.resource_key }}
<li><b>{{ key }}:</b> {{ value }}</li> </td>
{% endfor %} <td>
</ul> <ul>
</li> {% for key, value in (config.custom.resource_value | from_json).items() %}
<li><b>{{ key }}:</b> {{ value }}</li>
{% endfor %}
</ul>
</td>
</tr>
{% endif %} {% endif %}
{% endfor %} {% endfor %}
</ul> </tbody>
</div> </table>
</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 %} <!-- Modal -->
\ No newline at end of file <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
<!--
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
<!-- <!--
Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/)
Licensed under the Apache License, Version 2.0 (the "License"); Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. you may not use this file except in compliance with the License.
You may obtain a copy of the License at You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0 http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS, distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and See the License for the specific language governing permissions and
limitations under the License. limitations under the License.
--> -->
{% extends 'base.html' %} {% extends 'base.html' %}
{% block content %} {% block content %}
<h1>Links</h1> <h1>Links</h1>
<div class="row"> <div class="row">
<div class="col"> <div class="col">
<!-- <a href="#" class="btn btn-primary" style="margin-bottom: 10px;"> <!-- <a href="#" class="btn btn-primary" style="margin-bottom: 10px;">
<i class="bi bi-plus"></i> <i class="bi bi-plus"></i>
Add New Link Add New Link
</a> --> </a> -->
</div> </div>
<div class="col"> <div class="col">
{{ links | length }} links found</i> {{ links | length }} links found</i>
</div> </div>
<!-- <div class="col"> <!-- <div class="col">
<form> <form>
<div class="input-group"> <div class="input-group">
<input type="text" aria-label="Search" placeholder="Search..." class="form-control"/> <input type="text" aria-label="Search" placeholder="Search..." class="form-control"/>
<button type="submit" class="btn btn-primary">Search</button> <button type="submit" class="btn btn-primary">Search</button>
</div> </div>
</form> </form>
</div> --> </div> -->
</div> </div>
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th scope="col">#</th> <th scope="col">#</th>
<th scope="col">Endpoints</th> <th scope="col">Endpoints</th>
<th scope="col"></th> <th scope="col"></th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% if links %} {% if links %}
{% for link in links %} {% for link in links %}
<tr> <tr>
<td> <td>
<!-- <a href="#"> --> <!-- <a href="#"> -->
{{ link.link_id.link_uuid.uuid }} {{ link.link_id.link_uuid.uuid }}
<!-- </a> --> <!-- </a> -->
</td> </td>
<td> <td>
<ul> <ul>
{% for end_point in link.link_endpoint_ids %} {% for end_point in link.link_endpoint_ids %}
<li> <li>
{{ end_point.endpoint_uuid.uuid }} / {{ end_point.endpoint_uuid.uuid }} /
Device: Device:
<a href="{{ url_for('device.detail', device_uuid=end_point.device_id.device_uuid.uuid) }}"> <a href="{{ url_for('device.detail', device_uuid=end_point.device_id.device_uuid.uuid) }}">
{{ end_point.device_id.device_uuid.uuid }} {{ end_point.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"> <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="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"/> <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> </svg>
</a> </a>
</li> </li>
{% endfor %} {% endfor %}
</ul> </ul>
</td> </td>
<td> <td>
<!-- <a href="#"> <a href="{{ url_for('link.detail', link_uuid=link.link_id.link_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"> <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="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"/> <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> </svg>
</a> --> </a>
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
{% else %} {% else %}
<tr> <tr>
<td colspan="7">No links found</td> <td colspan="7">No links found</td>
</tr> </tr>
{% endif %} {% endif %}
</tbody> </tbody>
</table> </table>
{% endblock %} {% endblock %}
\ No newline at end of file \ No newline at end of file
...@@ -98,4 +98,39 @@ ...@@ -98,4 +98,39 @@
</div> </div>
</div> </div>
{% endblock %}
\ No newline at end of file <table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">Connection Id</th>
<th scope="col">Sub-service</th>
<th scope="col">Path</th>
</tr>
</thead>
<tbody>
{% for connections in connections.connections %}
<tr>
<td>
{{ connections.connection_id.connection_uuid.uuid }}
</td>
<td>
{{ connections.sub_service_ids|map(attribute='service_uuid')|map(attribute='uuid')|join(', ') }}
</td>
{% for i in range(connections.path_hops_endpoint_ids|length) %}
<td>
{{ connections.path_hops_endpoint_ids[i].device_id.device_uuid.uuid }} / {{ connections.path_hops_endpoint_ids[i].endpoint_uuid.uuid }}
</td>
{% endfor %}
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}
...@@ -73,7 +73,7 @@ ...@@ -73,7 +73,7 @@
<td> <td>
<ul> <ul>
{% for constraint in service.service_constraints %} {% for constraint in service.service_constraints %}
<li>{{ constraint.constraint_type }}: {{ constraint.constraint_value }}</li> <li>{{ constraint.custom.constraint_type }}: {{ constraint.custom.constraint_value }}</li>
{% endfor %} {% endfor %}
</ul> </ul>
</td> </td>
......
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