Skip to content
Snippets Groups Projects
Commit cb106875 authored by longllu's avatar longllu
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
No related branches found
No related tags found
2 merge requests!54Release 2.0.0,!4Compute component:
......@@ -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()
......@@ -32,4 +34,13 @@ def home():
return render_template(
"link/home.html",
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 @@
# 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,29 +125,30 @@ def topology():
'name': device.device_id.device_uuid.uuid,
'type': device.device_type,
} for device in response.devices]
response = context_client.ListLinks(Empty())
links = [{
'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]
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,
})
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()
return redirect(url_for("main.home"))
return redirect(url_for("main.home"))
\ No newline at end of file
......@@ -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')
......@@ -100,4 +101,4 @@ def delete(service_uuid: str):
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'))
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");
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>
<br>
<div class="row mb-3">
<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 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 %}
<tr>
<td>
{{ endpoint.endpoint_id.endpoint_uuid.uuid }}
</td>
<td>
{{ endpoint.endpoint_type }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</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>
<div class="col-sm-10">
<ul>
{% for config in device.device_config.config_rules %}
<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 }}:
<ul>
{% for key, value in (config.custom.resource_value | from_json).items() %}
<li><b>{{ key }}:</b> {{ value }}</li>
{% endfor %}
</ul>
</li>
<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>
</td>
</tr>
{% endif %}
{% endfor %}
</ul>
</div>
</div>
{% endfor %}
</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">
<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
<!-- 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
<!--
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/)
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="#" 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">Endpoints</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% if links %}
{% for link in links %}
<tr>
<td>
<!-- <a href="#"> -->
{{ link.link_id.link_uuid.uuid }}
<!-- </a> -->
</td>
<td>
<ul>
{% for end_point in link.link_endpoint_ids %}
<li>
{{ end_point.endpoint_uuid.uuid }} /
Device:
<a href="{{ url_for('device.detail', device_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">
<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>
</li>
{% endfor %}
</ul>
</td>
<td>
<!-- <a href="#">
<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 links found</td>
</tr>
{% endif %}
</tbody>
</table>
{% 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>Links</h1>
<div class="row">
<div class="col">
<!-- <a href="#" 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">Endpoints</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% if links %}
{% for link in links %}
<tr>
<td>
<!-- <a href="#"> -->
{{ link.link_id.link_uuid.uuid }}
<!-- </a> -->
</td>
<td>
<ul>
{% for end_point in link.link_endpoint_ids %}
<li>
{{ end_point.endpoint_uuid.uuid }} /
Device:
<a href="{{ url_for('device.detail', device_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">
<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>
</li>
{% endfor %}
</ul>
</td>
<td>
<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">
<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 links found</td>
</tr>
{% endif %}
</tbody>
</table>
{% endblock %}
\ No newline at end of file
......@@ -98,4 +98,39 @@
</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 @@
<td>
<ul>
{% 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 %}
</ul>
</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