Skip to content
Snippets Groups Projects
Commit 538ab09f 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 e941b6cc 4e5c8f3a
No related branches found
No related tags found
2 merge requests!54Release 2.0.0,!4Compute component:
...@@ -60,43 +60,43 @@ spec: ...@@ -60,43 +60,43 @@ spec:
limits: limits:
cpu: 700m cpu: 700m
memory: 1024Mi memory: 1024Mi
- name: grafana # - name: grafana
image: grafana/grafana:8.2.6 # image: grafana/grafana:8.2.6
imagePullPolicy: IfNotPresent # imagePullPolicy: IfNotPresent
ports: # ports:
- containerPort: 3000 # - containerPort: 3000
name: http-grafana # name: http-grafana
protocol: TCP # protocol: TCP
env: # env:
- name: GF_SERVER_ROOT_URL # - name: GF_SERVER_ROOT_URL
value: "http://0.0.0.0:3000/grafana/" # value: "http://0.0.0.0:3000/grafana/"
- name: GF_SERVER_SERVE_FROM_SUB_PATH # - name: GF_SERVER_SERVE_FROM_SUB_PATH
value: "true" # value: "true"
readinessProbe: # readinessProbe:
failureThreshold: 3 # failureThreshold: 3
httpGet: # httpGet:
path: /robots.txt # path: /robots.txt
port: 3000 # port: 3000
scheme: HTTP # scheme: HTTP
initialDelaySeconds: 10 # initialDelaySeconds: 10
periodSeconds: 30 # periodSeconds: 30
successThreshold: 1 # successThreshold: 1
timeoutSeconds: 2 # timeoutSeconds: 2
livenessProbe: # livenessProbe:
failureThreshold: 3 # failureThreshold: 3
initialDelaySeconds: 30 # initialDelaySeconds: 30
periodSeconds: 10 # periodSeconds: 10
successThreshold: 1 # successThreshold: 1
tcpSocket: # tcpSocket:
port: 3000 # port: 3000
timeoutSeconds: 1 # timeoutSeconds: 1
resources: # resources:
requests: # requests:
cpu: 250m # cpu: 250m
memory: 750Mi # memory: 750Mi
limits: # limits:
cpu: 700m # cpu: 700m
memory: 1024Mi # memory: 1024Mi
--- ---
apiVersion: v1 apiVersion: v1
kind: Service kind: Service
...@@ -110,6 +110,6 @@ spec: ...@@ -110,6 +110,6 @@ spec:
- name: webui - name: webui
port: 8004 port: 8004
targetPort: 8004 targetPort: 8004
- name: grafana # - name: grafana
port: 3000 # port: 3000
targetPort: 3000 # targetPort: 3000
# Set the URL of your local Docker registry where the images will be uploaded to.
export TFS_REGISTRY_IMAGE="http://localhost:32000/tfs/" export TFS_REGISTRY_IMAGE="http://localhost:32000/tfs/"
# Set the list of components, separated by spaces, you want to build images for, and deploy. # Set the list of components, separated by spaces, you want to build images for, and deploy.
...@@ -11,12 +10,6 @@ export TFS_COMPONENTS="context device automation service compute monitoring webu ...@@ -11,12 +10,6 @@ export TFS_COMPONENTS="context device automation service compute monitoring webu
# Set the tag you want to use for your images. # Set the tag you want to use for your images.
export TFS_IMAGE_TAG="dev" export TFS_IMAGE_TAG="dev"
# Set the name of the Kubernetes namespace to deploy to.
export TFS_K8S_NAMESPACE="tfs" export TFS_K8S_NAMESPACE="tfs"
# Set additional manifest files to be applied after the deployment
export TFS_EXTRA_MANIFESTS="manifests/nginx_ingress_http.yaml" export TFS_EXTRA_MANIFESTS="manifests/nginx_ingress_http.yaml"
# Set the neew Grafana admin password
export TFS_GRAFANA_PASSWORD="admin123+" export TFS_GRAFANA_PASSWORD="admin123+"
...@@ -79,6 +79,8 @@ COPY --chown=webui:webui src/device/__init__.py device/__init__.py ...@@ -79,6 +79,8 @@ COPY --chown=webui:webui src/device/__init__.py device/__init__.py
COPY --chown=webui:webui src/device/client/. device/client/ COPY --chown=webui:webui src/device/client/. device/client/
COPY --chown=webui:webui src/service/__init__.py service/__init__.py COPY --chown=webui:webui src/service/__init__.py service/__init__.py
COPY --chown=webui:webui src/service/client/. service/client/ COPY --chown=webui:webui src/service/client/. service/client/
COPY --chown=webui:webui src/slice/__init__.py slice/__init__.py
COPY --chown=webui:webui src/slice/client/. slice/client/
COPY --chown=webui:webui src/webui/. webui/ COPY --chown=webui:webui src/webui/. webui/
# Start the service # Start the service
......
...@@ -72,11 +72,15 @@ def create_app(use_config=None, web_app_root=None): ...@@ -72,11 +72,15 @@ def create_app(use_config=None, web_app_root=None):
from webui.service.service.routes import service from webui.service.service.routes import service
app.register_blueprint(service) app.register_blueprint(service)
from webui.service.slice.routes import slice
app.register_blueprint(slice)
from webui.service.device.routes import device from webui.service.device.routes import device
app.register_blueprint(device) app.register_blueprint(device)
from webui.service.link.routes import link from webui.service.link.routes import link
app.register_blueprint(link) app.register_blueprint(link)
app.jinja_env.filters['from_json'] = from_json app.jinja_env.filters['from_json'] = from_json
......
...@@ -11,32 +11,43 @@ ...@@ -11,32 +11,43 @@
# 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 copy, 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, Service, Topology, ContextIdList from common.proto.context_pb2 import Connection, Context, Device, Empty, Link, Service, Slice, Topology, ContextIdList
from common.tools.grpc.Tools import grpc_message_to_json_string 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 service.client.ServiceClient import ServiceClient
from slice.client.SliceClient import SliceClient
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() service_client = ServiceClient()
slice_client = SliceClient()
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
ENTITY_TO_TEXT = { ENTITY_TO_TEXT = {
# name => singular, plural # name => singular, plural
'context' : ('Context', 'Contexts' ), 'context' : ('Context', 'Contexts' ),
'topology': ('Topology', 'Topologies'), 'topology' : ('Topology', 'Topologies' ),
'device' : ('Device', 'Devices' ), 'device' : ('Device', 'Devices' ),
'link' : ('Link', 'Links' ), 'link' : ('Link', 'Links' ),
'service' : ('Service', 'Services' ), 'service' : ('Service', 'Services' ),
'slice' : ('Slice', 'Slices' ),
'connection': ('Connection', 'Connections'),
} }
ACTION_TO_TEXT = { ACTION_TO_TEXT = {
# action => infinitive, past # action => infinitive, past
'add' : ('Add', 'Added'), 'add' : ('Add', 'Added'),
'update' : ('Update', 'Updated'), 'update' : ('Update', 'Updated'),
} }
def process_descriptor(entity_name, action_name, grpc_method, grpc_class, entities): def process_descriptor(entity_name, action_name, grpc_method, grpc_class, entities):
entity_name_singluar,entity_name_plural = ENTITY_TO_TEXT[entity_name] entity_name_singluar,entity_name_plural = ENTITY_TO_TEXT[entity_name]
action_infinitive, action_past = ACTION_TO_TEXT[action_name] action_infinitive, action_past = ACTION_TO_TEXT[action_name]
...@@ -50,25 +61,56 @@ def process_descriptor(entity_name, action_name, grpc_method, grpc_class, entiti ...@@ -50,25 +61,56 @@ def process_descriptor(entity_name, action_name, grpc_method, grpc_class, entiti
num_err += 1 num_err += 1
if num_ok : flash(f'{str(num_ok)} {entity_name_plural} {action_past}', 'success') 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') 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.name))
try: try:
logger.warning(str(request.files))
descriptors_file = request.files[descriptors.name] descriptors_file = request.files[descriptors.name]
logger.warning(str(descriptors_file))
descriptors_data = descriptors_file.read() descriptors_data = descriptors_file.read()
logger.warning(str(descriptors_data))
descriptors = json.loads(descriptors_data) descriptors = json.loads(descriptors_data)
logger.warning(str(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', []) dummy_mode = descriptors.get('dummy_mode' , False)
devices = descriptors.get('devices' , []) contexts = descriptors.get('contexts' , [])
links = descriptors.get('links' , []) topologies = descriptors.get('topologies' , [])
services = descriptors.get('services' , []) devices = descriptors.get('devices' , [])
links = descriptors.get('links' , [])
services = descriptors.get('services' , [])
slices = descriptors.get('slices' , [])
connections = descriptors.get('connections', [])
if dummy_mode:
# Dummy Mode: used to pre-load databases (WebUI debugging purposes) with no smart or automated tasks.
context_client.connect()
contexts_add = copy.deepcopy(contexts)
for context in contexts_add:
context['topology_ids'] = []
context['service_ids'] = []
topologies_add = copy.deepcopy(topologies)
for topology in topologies_add:
topology['device_ids'] = []
topology['link_ids'] = []
process_descriptor('context', 'add', context_client.SetContext, Context, contexts_add )
process_descriptor('topology', 'add', context_client.SetTopology, Topology, topologies_add)
process_descriptor('device', 'add', context_client.SetDevice, Device, devices )
process_descriptor('link', 'add', context_client.SetLink, Link, links )
process_descriptor('service', 'add', context_client.SetService, Service, services )
process_descriptor('context', 'update', context_client.SetContext, Context, contexts )
process_descriptor('topology', 'update', context_client.SetTopology, Topology, topologies )
process_descriptor('slice', 'add', context_client.SetSlice, Slice, slices )
process_descriptor('connection', 'add', context_client.SetConnection, Connection, connections )
context_client.close()
return
# Normal mode: follows the automated workflows in the different components
# in normal mode, connections should not be set
assert len(connections) == 0
services_add = [] services_add = []
for service in services: for service in services:
service_copy = copy.deepcopy(service) service_copy = copy.deepcopy(service)
...@@ -76,18 +118,34 @@ def process_descriptors(descriptors): ...@@ -76,18 +118,34 @@ def process_descriptors(descriptors):
service_copy['service_constraints'] = [] service_copy['service_constraints'] = []
service_copy['service_config'] = {'config_rules': []} service_copy['service_config'] = {'config_rules': []}
services_add.append(service_copy) services_add.append(service_copy)
slices_add = []
for slice in slices:
slice_copy = copy.deepcopy(slice)
slice_copy['slice_endpoint_ids'] = []
slice_copy['slice_constraints'] = []
slice_copy['slice_config'] = {'config_rules': []}
slices_add.append(slice_copy)
context_client.connect() context_client.connect()
device_client.connect() device_client.connect()
service_client.connect() service_client.connect()
slice_client.connect()
process_descriptor('context', 'add', context_client.SetContext, Context, contexts ) process_descriptor('context', 'add', context_client.SetContext, Context, contexts )
process_descriptor('topology', 'add', context_client.SetTopology, Topology, topologies ) process_descriptor('topology', 'add', context_client.SetTopology, Topology, topologies )
process_descriptor('device', 'add', device_client .AddDevice, Device, devices ) process_descriptor('device', 'add', device_client .AddDevice, Device, devices )
process_descriptor('link', 'add', context_client.SetLink, Link, links ) process_descriptor('link', 'add', context_client.SetLink, Link, links )
process_descriptor('service', 'add', service_client.CreateService, Service, services_add) process_descriptor('service', 'add', service_client.CreateService, Service, services_add)
process_descriptor('service', 'update', service_client.UpdateService, Service, services ) process_descriptor('service', 'update', service_client.UpdateService, Service, services )
process_descriptor('slice', 'add', slice_client.CreateSlice, Slice, slices_add )
process_descriptor('slice', 'update', slice_client.UpdateSlice, Slice, slices )
slice_client.close()
service_client.close() 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()
...@@ -95,14 +153,18 @@ def home(): ...@@ -95,14 +153,18 @@ def home():
response: ContextIdList = context_client.ListContextIds(Empty()) response: ContextIdList = context_client.ListContextIds(Empty())
context_form: ContextForm = ContextForm() context_form: ContextForm = ContextForm()
context_form.context.choices.append(('', 'Select...')) context_form.context.choices.append(('', 'Select...'))
for context in response.context_ids: for context in response.context_ids:
context_form.context.choices.append((context.context_uuid.uuid, context.context_uuid)) context_form.context.choices.append((context.context_uuid.uuid, context.context_uuid))
if context_form.validate_on_submit(): if context_form.validate_on_submit():
session['context_uuid'] = context_form.context.data session['context_uuid'] = context_form.context.data
flash(f'The context was successfully set to `{context_form.context.data}`.', 'success') flash(f'The context was successfully set to `{context_form.context.data}`.', 'success')
return redirect(url_for("main.home")) return redirect(url_for("main.home"))
if 'context_uuid' in session: if 'context_uuid' in session:
context_form.context.data = session['context_uuid'] context_form.context.data = session['context_uuid']
descriptor_form: DescriptorForm = DescriptorForm() descriptor_form: DescriptorForm = DescriptorForm()
try: try:
if descriptor_form.validate_on_submit(): if descriptor_form.validate_on_submit():
...@@ -114,7 +176,9 @@ def home(): ...@@ -114,7 +176,9 @@ def home():
finally: finally:
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()
...@@ -125,6 +189,7 @@ def topology(): ...@@ -125,6 +189,7 @@ 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 = []
for link in response.links: for link in response.links:
...@@ -137,18 +202,22 @@ def topology(): ...@@ -137,18 +202,22 @@ def topology():
'source': link.link_endpoint_ids[0].device_id.device_uuid.uuid, 'source': link.link_endpoint_ids[0].device_id.device_uuid.uuid,
'target': link.link_endpoint_ids[1].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
# 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.
# 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.
#
import grpc
from flask import current_app, redirect, render_template, Blueprint, flash, session, url_for
from common.proto.context_pb2 import ContextId, Slice, SliceId, SliceList, Connection, SliceStatusEnum
from context.client.ContextClient import ContextClient
#from slice.client.SliceClient import SliceClient
slice = Blueprint('slice', __name__, url_prefix='/slice')
context_client = ContextClient()
#slice_client = SliceClient()
@slice.get('/')
def home():
# flash('This is an info message', 'info')
# flash('This is a danger message', 'danger')
context_uuid = session.get('context_uuid', '-')
if context_uuid == "-":
flash("Please select a context!", "warning")
return redirect(url_for("main.home"))
request = ContextId()
request.context_uuid.uuid = context_uuid
context_client.connect()
try:
slice_list = context_client.ListSlices(request)
# print(slice_list)
slices = slice_list.slices
context_not_found = False
except grpc.RpcError as e:
if e.code() != grpc.StatusCode.NOT_FOUND: raise
if e.details() != 'Context({:s}) not found'.format(context_uuid): raise
slices = []
context_not_found = True
context_client.close()
return render_template('slice/home.html',slices=slices, context_not_found=context_not_found, sse=SliceStatusEnum)
#
#@slice.route('add', methods=['GET', 'POST'])
#def add():
# flash('Add slice route called', 'danger')
# raise NotImplementedError()
# return render_template('slice/home.html')
#
#
@slice.get('<path:slice_uuid>/detail')
def detail(slice_uuid: str):
context_uuid = session.get('context_uuid', '-')
if context_uuid == "-":
flash("Please select a context!", "warning")
return redirect(url_for("main.home"))
request: SliceId = SliceId()
request.slice_uuid.uuid = slice_uuid
request.context_id.context_uuid.uuid = context_uuid
req = ContextId()
req.context_uuid.uuid = context_uuid
try:
context_client.connect()
response: Slice = context_client.GetSlice(request)
services = context_client.ListServices(req)
context_client.close()
except Exception as e:
flash('The system encountered an error and cannot show the details of this slice.', 'warning')
current_app.logger.exception(e)
return redirect(url_for('slice.home'))
return render_template('slice/detail.html', slice=response, sse=SliceStatusEnum, services=services)
#
#@slice.get('<path:slice_uuid>/delete')
#def delete(slice_uuid: str):
# context_uuid = session.get('context_uuid', '-')
# if context_uuid == "-":
# flash("Please select a context!", "warning")
# return redirect(url_for("main.home"))
#
# try:
# request = SliceId()
# request.slice_uuid.uuid = slice_uuid
# request.context_id.context_uuid.uuid = context_uuid
# slice_client.connect()
# response = slice_client.DeleteSlice(request)
# slice_client.close()
#
# flash('Slice "{:s}" deleted successfully!'.format(slice_uuid), 'success')
# except Exception as e:
# flash('Problem deleting slice "{:s}": {:s}'.format(slice_uuid, str(e.details())), 'danger')
# current_app.logger.exception(e)
# return redirect(url_for('slice.home'))
\ No newline at end of file
...@@ -76,7 +76,13 @@ ...@@ -76,7 +76,13 @@
<a class="nav-link" href="{{ url_for('service.home') }}">Service</a> <a class="nav-link" href="{{ url_for('service.home') }}">Service</a>
{% endif %} {% endif %}
</li> </li>
<li class="nav-item">
{% if '/slice/' in request.path %}
<a class="nav-link active" aria-current="page" href="{{ url_for('slice.home') }}">Slice</a>
{% else %}
<a class="nav-link" href="{{ url_for('slice.home') }}">Slice</a>
{% endif %}
</li>
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/grafana" id="grafana_link" target="grafana">Grafana</a> <a class="nav-link" href="/grafana" id="grafana_link" target="grafana">Grafana</a>
</li> </li>
......
...@@ -26,18 +26,20 @@ ...@@ -26,18 +26,20 @@
Back to service list Back to service list
</button> </button>
</div> </div>
<!--
<div class="col-sm-3"> <div class="col-sm-3">
<a id="update" class="btn btn-secondary" href="#"> <a id="update" class="btn btn-secondary" href="#">
<i class="bi bi-pencil-square"></i> <i class="bi bi-pencil-square"></i>
Update Update
</a> </a>
</div> </div>
<div class="col-sm-3"> <div class="col-sm-3">-->
<!-- <button type="button" class="btn btn-danger"><i class="bi bi-x-square"></i>Delete service</button> --> <!-- <button type="button" class="btn btn-danger"><i class="bi bi-x-square"></i>Delete service</button> -->
<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal"> <!--<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">
<i class="bi bi-x-square"></i>Delete service <i class="bi bi-x-square"></i>Delete service
</button> </button>
</div> </div>
-->
</div> </div>
<div class="row mb-3"> <div class="row mb-3">
...@@ -63,9 +65,12 @@ ...@@ -63,9 +65,12 @@
<td> <td>
<a href="{{ url_for('device.detail', device_uuid=endpoint.device_id.device_uuid.uuid) }}"> <a href="{{ url_for('device.detail', device_uuid=endpoint.device_id.device_uuid.uuid) }}">
{{ endpoint.device_id.device_uuid.uuid }} {{ endpoint.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"
<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"/> class="bi bi-eye" viewBox="0 0 16 16">
<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="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> </svg>
</a> </a>
</td> </td>
...@@ -79,22 +84,61 @@ ...@@ -79,22 +84,61 @@
<table class="table table-striped table-hover"> <table class="table table-striped table-hover">
<thead> <thead>
<tr> <tr>
<th scope="col">Kind</th>
<th scope="col">Type</th> <th scope="col">Type</th>
<th scope="col">Value</th> <th scope="col">Value</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for constraint in service.service_constraints %} {% for constraint in service.service_constraints %}
{% if constraint.WhichOneof('constraint')=='custom' %}
<tr>
<td>Custom</td>
<td>{{ constraint.custom.constraint_type }}</td>
<td>{{ constraint.custom.constraint_value }}</td>
</tr>
{% elif constraint.WhichOneof('constraint')=='endpoint_location' %}
<tr> <tr>
<td>Endpoint Location</td>
<td> <td>
{{ constraint.custom.constraint_type }} {{ constraint.endpoint_location.endpoint_id.device_id.device_uuid.uuid }} / {{
constraint.endpoint_location.endpoint_id.endpoint_uuid.uuid }}
</td> </td>
<td> <td>
<ul> {% if constraint.endpoint_location.location.WhichOneof('location')=='region' %}
{{ constraint.custom.constraint_value }} Region: {{ constraint.endpoint_location.location.region }}
</ul> {% elif constraint.endpoint_location.location.WhichOneof('location')=='gps_position' %}
Position (lat/long):
{{ constraint.endpoint_location.location.gps_position.latitude }} /
{{ constraint.endpoint_location.location.gps_position.longitude }}
{% endif %}
</td>
</tr>
{% elif constraint.WhichOneof('constraint')=='endpoint_priority' %}
<tr>
<td>Endpoint Priority</td>
<td>
{{ constraint.endpoint_priority.endpoint_id.device_id.device_uuid.uuid }} / {{
constraint.endpoint_priority.endpoint_id.endpoint_uuid.uuid }}
</td>
<td>{{ constraint.endpoint_priority.priority }}</td>
</tr>
{% elif constraint.WhichOneof('constraint')=='sla_availability' %}
<tr>
<td>SLA Availability</td>
<td>-</td>
<td>
{{ constraint.sla_availability.num_disjoint_paths }} disjoint paths;
{% if constraint.sla_availability.all_active %}all{% else %}single{% endif %}active
</td> </td>
</tr> </tr>
{% else %}
<tr>
<td>-</td>
<td>-</td>
<td>{{ constraint }}</td>
</tr>
{% endif %}
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
......
<!--
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>Slice {{ slice.slice_id.slice_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('slice.home') }}'">
<i class="bi bi-box-arrow-in-left"></i>
Back to slice 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 slice</button> -->
<!--<button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal">
<i class="bi bi-x-square"></i>Delete slice
</button>
</div>
-->
</div>
<div class="row mb-3">
<div class="col-sm-4">
<b>UUID: </b> {{ slice.slice_id.slice_uuid.uuid }}<br><br>
<b>Status: </b> {{ sse.Name(slice.slice_status.slice_status).replace('SLICESTATUS_', '') }}<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">Device</th>
</tr>
</thead>
<tbody>
{% for endpoint in slice.slice_endpoint_ids %}
<tr>
<td>
{{ endpoint.endpoint_uuid.uuid }}
</td>
<td>
<a href="{{ url_for('device.detail', device_uuid=endpoint.device_id.device_uuid.uuid) }}">
{{ endpoint.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 %}
</tbody>
</table>
</div>
</div>
<b>Constraints:</b>
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">Kind</th>
<th scope="col">Type</th>
<th scope="col">Value</th>
</tr>
</thead>
<tbody>
{% for constraint in slice.slice_constraints %}
{% if constraint.WhichOneof('constraint')=='custom' %}
<tr>
<td>Custom</td>
<td>{{ constraint.custom.constraint_type }}</td>
<td>{{ constraint.custom.constraint_value }}</td>
</tr>
{% elif constraint.WhichOneof('constraint')=='endpoint_location' %}
<tr>
<td>Endpoint Location</td>
<td>
{{ constraint.endpoint_location.endpoint_id.device_id.device_uuid.uuid }} / {{
constraint.endpoint_location.endpoint_id.endpoint_uuid.uuid }}
</td>
<td>
{% if constraint.endpoint_location.location.WhichOneof('location')=='region' %}
Region: {{ constraint.endpoint_location.location.region }}
{% elif constraint.endpoint_location.location.WhichOneof('location')=='gps_position' %}
Position (lat/long):
{{ constraint.endpoint_location.location.gps_position.latitude }} /
{{ constraint.endpoint_location.location.gps_position.longitude }}
{% endif %}
</td>
</tr>
{% elif constraint.WhichOneof('constraint')=='endpoint_priority' %}
<tr>
<td>Endpoint Priority</td>
<td>
{{ constraint.endpoint_priority.endpoint_id.device_id.device_uuid.uuid }} / {{
constraint.endpoint_priority.endpoint_id.endpoint_uuid.uuid }}
</td>
<td>{{ constraint.endpoint_priority.priority }}</td>
</tr>
{% elif constraint.WhichOneof('constraint')=='sla_availability' %}
<tr>
<td>SLA Availability</td>
<td>-</td>
<td>
{{ constraint.sla_availability.num_disjoint_paths }} disjoint paths;
{% if constraint.sla_availability.all_active %}all{% else %}single{% endif %}active
</td>
</tr>
{% else %}
<tr>
<td>-</td>
<td>-</td>
<td>{{ constraint }}</td>
</tr>
{% endif %}
{% endfor %}
</tbody>
</table>
<b>Configurations:</b>
<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 slice.slice_config.config_rules %}
{% if config.WhichOneof('config_rule') == 'custom' %}
<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 %}
</tbody>
</table>
<div class="row mb-2">
<div class="col-sm-6">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">Service Id</th>
</tr>
</thead>
<tbody>
{% for services in services.services %}
<tr>
<td>
<a href="{{ url_for('service.detail', service_uuid=services.service_id.service_uuid.uuid) }}">
{{ services.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>
</tr>
{% endfor %}
</tbody>
</table>
</div>
<div class="col-sm-6">
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">Sub-slice</th>
</tr>
</thead>
<tbody>
{% for services in services.services %}
<tr>
<td>
{{ services.sub_slice_ids|map(attribute='slice_uuid')|map(attribute='uuid')|join(', ') }}
</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>Slice</h1>
<div class="row">
<div class="col">
{{ slices | length }} slices found in context <i>{{ session['context_uuid'] }}</i>
</div>
</div>
<table class="table table-striped table-hover">
<thead>
<tr>
<th scope="col">#</th>
<th scope="col">End points</th>
<th scope="col">Status</th>
<th scope="col"></th>
</tr>
</thead>
<tbody>
{% if slices %}
{% for slice in slices %}
<tr>
<td>
{{ slice.slice_id.slice_uuid.uuid }}
</td>
<td>
{% for i in range(slice.slice_endpoint_ids|length) %}
<ul>
<li> {{ slice.slice_endpoint_ids[i].device_id.device_uuid.uuid }} / {{ slice.slice_endpoint_ids[i].endpoint_uuid.uuid }} </li>
</ul>
{% endfor %}
</td>
<td>
{{ sse.Name(slice.slice_status.slice_status).replace('SLICESTATUS_', '') }}
</td>
<td>
<a href="{{ url_for('slice.detail', slice_uuid=slice.slice_id.slice_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 slices found</td>
</tr>
{% endif %}
</tbody>
</table>
{% endblock %}
\ No newline at end of file
...@@ -68,6 +68,7 @@ class TestWebUI(ClientTestCase): ...@@ -68,6 +68,7 @@ class TestWebUI(ClientTestCase):
with self.app.app_context(): with self.app.app_context():
url_for('main.home') url_for('main.home')
url_for('service.home') url_for('service.home')
url_for('slice.home')
url_for('device.home') url_for('device.home')
url_for('link.home') url_for('link.home')
#url_for('main.debug') #url_for('main.debug')
......
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