diff --git a/src/start_webui_dev_mode.sh b/src/start_webui_dev_mode.sh index 74540bcb36115dc175f371acbb3f80930404eac9..8c45cd89ed619bae1a9252f2a74e9f3aa5d8b91c 100755 --- a/src/start_webui_dev_mode.sh +++ b/src/start_webui_dev_mode.sh @@ -14,19 +14,23 @@ # for development purposes only -K8S_NAMESPACE=${K8S_NAMESPACE:-'tf-dev'} +# K8S_NAMESPACE=${K8S_NAMESPACE:-'tf-dev'} -export CONTEXTSERVICE_SERVICE_HOST=`kubectl get service/contextservice -n ${K8S_NAMESPACE} -o jsonpath='{.spec.clusterIP}'` +# export CONTEXTSERVICE_SERVICE_HOST=`kubectl get service/contextservice -n ${K8S_NAMESPACE} -o jsonpath='{.spec.clusterIP}'` -echo Context IP: $CONTEXTSERVICE_SERVICE_HOST +# echo Context IP: $CONTEXTSERVICE_SERVICE_HOST -export DEVICESERVICE_SERVICE_HOST=`kubectl get service/deviceservice -n ${K8S_NAMESPACE} -o jsonpath='{.spec.clusterIP}'` +# export DEVICESERVICE_SERVICE_HOST=`kubectl get service/deviceservice -n ${K8S_NAMESPACE} -o jsonpath='{.spec.clusterIP}'` -echo Device IP: $DEVICESERVICE_SERVICE_HOST +# echo Device IP: $DEVICESERVICE_SERVICE_HOST + +source tfs_runtime_env_vars.sh export PROTOCOL_BUFFERS_PYTHON_IMPLEMENTATION='python' export HOST="127.0.0.1" +export HOSTNAME="test" export FLASK_ENV="development" +export LOG_LEVEL="DEBUG" # python3 -m webbrowser http://${HOST}:8004 diff --git a/src/webui/service/device/forms.py b/src/webui/service/device/forms.py index d1880d321c4cb2cc825baed5353b892aae90aae9..cfa741ab306dd4a0c7ddc0272a3680891bfe1597 100644 --- a/src/webui/service/device/forms.py +++ b/src/webui/service/device/forms.py @@ -14,41 +14,45 @@ # external imports from flask_wtf import FlaskForm -from wtforms import StringField, SelectField, TextAreaField, SubmitField +from wtforms import StringField, SelectField, TextAreaField, SubmitField, BooleanField, Form from wtforms.validators import DataRequired, Length, NumberRange, Regexp, ValidationError -from common.proto.context_pb2 import DeviceDriverEnum, DeviceOperationalStatusEnum +from common.proto.context_pb2 import DeviceOperationalStatusEnum from webui.utils.form_validators import key_value_validator class AddDeviceForm(FlaskForm): device_id = StringField('ID', validators=[DataRequired(), Length(min=5)]) - device_type = StringField('Type', - validators=[DataRequired(), Length(min=5)]) - device_config = TextAreaField('Configurations', validators=[key_value_validator()]) + device_type = SelectField('Type', choices = []) operational_status = SelectField('Operational Status', # choices=[(-1, 'Select...'), (0, 'Undefined'), (1, 'Disabled'), (2, 'Enabled')], coerce=int, validators=[NumberRange(min=0)]) - device_drivers = TextAreaField('Drivers', validators=[DataRequired(), Regexp(r'^\d+(,\d+)*$')]) + device_drivers_undefined = BooleanField('UNDEFINED') + device_drivers_openconfig = BooleanField('OPENCONFIG') + device_drivers_transport_api = BooleanField('TRANSPORT_API') + device_drivers_p4 = BooleanField('P4') + device_drivers_ietf_network_topology = BooleanField('IETF_NETWORK_TOPOLOGY') + device_drivers_onf_tr_352 = BooleanField('ONF_TR_352') + device_drivers_xr = BooleanField('XR') + device_config_address = StringField('connect/address',default='127.0.0.1',validators=[DataRequired(), Length(min=5)]) + device_config_port = StringField('connect/port',default='0',validators=[DataRequired(), Length(min=1)]) + device_config_settings = TextAreaField('connect/settings',default='{}',validators=[DataRequired(), Length(min=2)]) submit = SubmitField('Add') def validate_operational_status(form, field): if field.data not in DeviceOperationalStatusEnum.DESCRIPTOR.values_by_number: raise ValidationError('The operational status value selected is incorrect!') - def validate_device_drivers(form, field): - if ',' not in field.data: - data = str(field.data) + ',' - else: - data = field.data - for value in data.split(','): - value = value.strip() - if len(value) == 0: - continue - try: - value_int = int(value) - except: - raise ValidationError(f'The value "{value}" is not a valid driver identified.') - if value_int not in DeviceDriverEnum.DESCRIPTOR.values_by_number: - values = ', '.join([str(x) for x in DeviceDriverEnum.DESCRIPTOR.values_by_number]) - raise ValidationError(f'The device driver {value_int} is not correct. Allowed values are: {values}.') +class ConfigForm(FlaskForm): + device_key_config = StringField('Key configuration') + device_value_config = StringField('Value configuration') + submit = SubmitField('Add') + + +class UpdateDeviceForm(FlaskForm): + update_operational_status = SelectField('Operational Status', + choices=[(-1, 'Select...'), (0, 'Undefined'), (1, 'Disabled'), (2, 'Enabled')], + coerce=int, + validators=[NumberRange(min=0)]) + + submit = SubmitField('Update') diff --git a/src/webui/service/device/routes.py b/src/webui/service/device/routes.py index b57c5735d4b26c541d60a885512fe37a2fd626bc..fe475594b4f0a1a8674ccbbe37c704df02f43621 100644 --- a/src/webui/service/device/routes.py +++ b/src/webui/service/device/routes.py @@ -16,12 +16,16 @@ from flask import current_app, render_template, Blueprint, flash, session, redir from common.proto.context_pb2 import ( ConfigActionEnum, ConfigRule, Device, DeviceDriverEnum, DeviceId, DeviceList, DeviceOperationalStatusEnum, - Empty, TopologyId) + Empty, TopologyId, ContextId) from common.tools.object_factory.Context import json_context_id from common.tools.object_factory.Topology import json_topology_id +from common.tools.context_queries.Device import add_device_to_topology from context.client.ContextClient import ContextClient from device.client.DeviceClient import DeviceClient from webui.service.device.forms import AddDeviceForm +from common.DeviceTypes import DeviceTypeEnum +from webui.service.device.forms import ConfigForm +from webui.service.device.forms import UpdateDeviceForm device = Blueprint('device', __name__, url_prefix='/device') context_client = ContextClient() @@ -57,60 +61,75 @@ def add(): form = AddDeviceForm() # listing enum values - form.operational_status.choices = [(-1, 'Select...')] + form.operational_status.choices = [] for key, value in DeviceOperationalStatusEnum.DESCRIPTOR.values_by_name.items(): form.operational_status.choices.append( (DeviceOperationalStatusEnum.Value(key), key.replace('DEVICEOPERATIONALSTATUS_', ''))) - # device driver ids - device_driver_ids = [] - for key in DeviceDriverEnum.DESCRIPTOR.values_by_name: - device_driver_ids.append(f"{DeviceDriverEnum.Value(key)}={key.replace('DEVICEDRIVER_', '')}") - device_driver_ids = ', '.join(device_driver_ids) + # items for Device Type field + for device_type in DeviceTypeEnum: + form.device_type.choices.append((device_type.value,device_type.value)) if form.validate_on_submit(): device = Device() + # Device UUID: device.device_id.device_uuid.uuid = form.device_id.data - device.device_type = form.device_type.data - if '\n' not in form.device_config.data: - data = form.device_config.data.strip() + '\n' - else: - data = form.device_config.data.strip() - - for config in data.split('\n'): - if len(config.strip()) > 0: - parts = config.strip().split('=') - config_rule: ConfigRule = ConfigRule() - config_rule.action = ConfigActionEnum.CONFIGACTION_SET - config_rule.custom.resource_key = parts[0].strip() - config_rule.custom.resource_value = parts[1].strip() - device.device_config.config_rules.append(config_rule) + # Device type: + device.device_type = str(form.device_type.data) + + # Device configurations: + config_rule = device.device_config.config_rules.add() + config_rule.action = ConfigActionEnum.CONFIGACTION_SET + config_rule.custom.resource_key = '_connect/address' + config_rule.custom.resource_value = form.device_config_address.data + + config_rule = device.device_config.config_rules.add() + config_rule.action = ConfigActionEnum.CONFIGACTION_SET + config_rule.custom.resource_key = '_connect/port' + config_rule.custom.resource_value = form.device_config_port.data + + config_rule = device.device_config.config_rules.add() + config_rule.action = ConfigActionEnum.CONFIGACTION_SET + config_rule.custom.resource_key = '_connect/settings' + config_rule.custom.resource_value = form.device_config_settings.data + + # Device status: device.device_operational_status = form.operational_status.data - if ',' not in form.device_drivers.data: - data = form.device_drivers.data.strip() + ',' - else: - data = form.device_drivers.data.strip() - - for driver in data.split(','): - driver = driver.strip() - if len(driver) == 0: - continue - device.device_drivers.append(int(driver)) + # Device drivers: + if form.device_drivers_undefined.data: + device.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_UNDEFINED) + if form.device_drivers_openconfig.data: + device.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_OPENCONFIG) + if form.device_drivers_transport_api.data: + device.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_TRANSPORT_API) + if form.device_drivers_p4.data: + device.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_P4) + if form.device_drivers_ietf_network_topology.data: + device.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_IETF_NETWORK_TOPOLOGY) + if form.device_drivers_onf_tr_352.data: + device.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_ONF_TR_352) + if form.device_drivers_xr.data: + device.device_drivers.append(DeviceDriverEnum.DEVICEDRIVER_XR) + try: device_client.connect() response: DeviceId = device_client.AddDevice(device) device_client.close() - + context_uuid = session['context_uuid'] + topology_uuid = session['topology_uuid'] + context_client.connect() + context_id = ContextId(**json_context_id(context_uuid)) + add_device_to_topology(context_client, context_id, topology_uuid, device.device_id.device_uuid.uuid) + context_client.close() flash(f'New device was created with ID "{response.device_uuid.uuid}".', 'success') return redirect(url_for('device.home')) except Exception as e: flash(f'Problem adding the device. {e.details()}', 'danger') return render_template('device/add.html', form=form, - submit_text='Add New Device', - device_driver_ids=device_driver_ids) + submit_text='Add New Device') @device.route('detail/<path:device_uuid>', methods=['GET', 'POST']) def detail(device_uuid: str): @@ -144,3 +163,67 @@ def delete(device_uuid): flash(f'Problem deleting device "{device_uuid}": {e.details()}', 'danger') current_app.logger.exception(e) return redirect(url_for('device.home')) + +@device.route('<path:device_uuid>/addconfig', methods=['GET', 'POST']) +def addconfig(device_uuid): + form = ConfigForm() + request = DeviceId() + request.device_uuid.uuid = device_uuid + context_client.connect() + response = context_client.GetDevice(request) + context_client.close() + + if form.validate_on_submit(): + device = Device() + device.CopyFrom(response) + config_rule = device.device_config.config_rules.add() + config_rule.action = ConfigActionEnum.CONFIGACTION_SET + config_rule.custom.resource_key = form.device_key_config.data + config_rule.custom.resource_value = form.device_value_config.data + try: + device_client.connect() + response: DeviceId = device_client.ConfigureDevice(device) + device_client.close() + flash(f'New configuration was created with ID "{response.device_uuid.uuid}".', 'success') + return redirect(url_for('device.home')) + except Exception as e: + flash(f'Problem adding the device. {e.details()}', 'danger') + + return render_template('device/addconfig.html', form=form, submit_text='Add New Configuration') + +@device.route('updateconfig', methods=['GET', 'POST']) +def updateconfig(): + + + return render_template('device/updateconfig.html') + + +@device.route('<path:device_uuid>/update', methods=['GET', 'POST']) +def update(device_uuid): + form = UpdateDeviceForm() + request = DeviceId() + request.device_uuid.uuid = device_uuid + context_client.connect() + response = context_client.GetDevice(request) + context_client.close() + + # listing enum values + form.update_operational_status.choices = [] + for key, value in DeviceOperationalStatusEnum.DESCRIPTOR.values_by_name.items(): + form.update_operational_status.choices.append((DeviceOperationalStatusEnum.Value(key), key.replace('DEVICEOPERATIONALSTATUS_', ''))) + + form.update_operational_status.default = response.device_operational_status + + if form.validate_on_submit(): + device = Device() + device.CopyFrom(response) + device.device_operational_status = form.update_operational_status.data + try: + device_client.connect() + response: DeviceId = device_client.ConfigureDevice(device) + device_client.close() + flash(f'Status of device with ID "{response.device_uuid.uuid}" was updated.', 'success') + return redirect(url_for('device.home')) + except Exception as e: + flash(f'Problem updating the device. {e.details()}', 'danger') + return render_template('device/update.html', device=response, form=form, submit_text='Update Device') diff --git a/src/webui/service/templates/device/add.html b/src/webui/service/templates/device/add.html index fe1ba31f26579fcf681ccf4da64a7906cae9cff5..1acbc7e48dd342f818cb420c251452be1339a301 100644 --- a/src/webui/service/templates/device/add.html +++ b/src/webui/service/templates/device/add.html @@ -17,100 +17,145 @@ {% extends 'base.html' %} {% block content %} - <h1>Add New Device</h1> - - <form id="add_device" method="POST"> - {{ form.hidden_tag() }} - <fieldset> - <div class="row mb-3"> - {{ form.device_id.label(class="col-sm-2 col-form-label") }} - <div class="col-sm-10"> - {% if form.device_id.errors %} - {{ form.device_id(class="form-control is-invalid") }} - <div class="invalid-feedback"> - {% for error in form.device_id.errors %} - <span>{{ error }}</span> - {% endfor %} - </div> - {% else %} - {{ form.device_id(class="form-control") }} - {% endif %} +<h1>Add New Device</h1> +<br /> +<form id="add_device" method="POST"> + {{ form.hidden_tag() }} + <fieldset> + <div class="row mb-3"> + {{ form.device_id.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.device_id.errors %} + {{ form.device_id(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.device_id.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form.device_id(class="form-control") }} + {% endif %} + </div> + </div> + <br /> + <div class="row mb-3"> + {{ form.device_type.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.device_type.errors %} + {{ form.device_type(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.device_type.errors %} + <span>{{ error }}</span> + {% endfor %} </div> + {% else %} + {{ form.device_type(class="form-select")}} + {% endif %} </div> - <div class="row mb-3"> - {{ form.device_type.label(class="col-sm-2 col-form-label") }} - <div class="col-sm-10"> - {% if form.device_type.errors %} - {{ form.device_type(class="form-control is-invalid") }} - <div class="invalid-feedback"> - {% for error in form.device_type.errors %} - <span>{{ error }}</span> - {% endfor %} - </div> - {% else %} - {{ form.device_type(class="form-control") }} - {% endif %} + </div> + <br /> + <div class="row mb-3"> + {{ form.operational_status.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.operational_status.errors %} + {{ form.operational_status(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.operational_status.errors %} + <span>{{ error }}</span> + {% endfor %} </div> + {% else %} + {{ form.operational_status(class="form-select") }} + {% endif %} </div> - <div class="row mb-3"> - {{ form.operational_status.label(class="col-sm-2 col-form-label") }} - <div class="col-sm-10"> - {% if form.operational_status.errors %} - {{ form.operational_status(class="form-control is-invalid") }} - <div class="invalid-feedback"> - {% for error in form.operational_status.errors %} - <span>{{ error }}</span> - {% endfor %} - </div> - {% else %} - {{ form.operational_status(class="form-control") }} - {% endif %} + </div> + <br /> + <div class="row mb-3"> + <div class="col-sm-2 col-form-label">Drivers</div> + <div class="col-sm-10"> + {% if form.device_drivers_undefined.errors %} + {{ form.device_drivers_undefined(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.device_drivers_undefined.errors %} + <span>{{ error }}</span> + {% endfor %} </div> + {% else %} + {{ form.device_drivers_undefined }} {{ form.device_drivers_undefined.label(class="col-sm-3 + col-form-label") }} + {{ form.device_drivers_openconfig }} {{ form.device_drivers_openconfig.label(class="col-sm-3 + col-form-label") }} + {{ form.device_drivers_transport_api }} {{ form.device_drivers_transport_api.label(class="col-sm-3 + col-form-label") }} + <br />{{ form.device_drivers_p4 }} {{ form.device_drivers_p4.label(class="col-sm-3 col-form-label") }} + {{ form.device_drivers_ietf_network_topology }} {{ + form.device_drivers_ietf_network_topology.label(class="col-sm-3 + col-form-label") }} + {{ form.device_drivers_onf_tr_352 }} {{ form.device_drivers_onf_tr_352.label(class="col-sm-3 + col-form-label") }}<br /> + {{ form.device_drivers_xr }} {{ form.device_drivers_xr.label(class="col-sm-3 + col-form-label") }} + {% endif %} </div> - <div class="row mb-3"> - {{ form.device_config.label(class="col-sm-2 col-form-label") }} - <div class="col-sm-10"> - {% if form.device_config.errors %} - {{ form.device_config(class="form-control is-invalid", rows=5) }} - <div class="invalid-feedback"> - {% for error in form.device_config.errors %} - <span>{{ error }}</span> - {% endfor %} - </div> - {% else %} - {{ form.device_config(class="form-control", rows=5) }} - {% endif %} + </div> + <br /> + Configuration Rules <br /> + <div class="row mb-3"> + {{ form.device_config_address.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.device_config_address.errors %} + {{ form.device_config_address(class="form-control is-invalid", rows=5) }} + <div class="invalid-feedback"> + {% for error in form.device_config_address.errors %} + <span>{{ error }}</span> + {% endfor %} </div> - <div id="device_config_help" class="form-text">The device configurations should follow a <i>key=value</i> format, one configuration per line.</div> + {% else %} + {{ form.device_config_address(class="form-control", rows=5) }} + {% endif %} </div> - <div class="row mb-3"> - {{ form.device_drivers.label(class="col-sm-2 col-form-label") }} - <div class="col-sm-10"> - {% if form.device_drivers.errors %} - {{ form.device_drivers(class="form-control is-invalid", rows=5) }} - <div class="invalid-feedback"> - {% for error in form.device_drivers.errors %} - <span>{{ error }}</span> - {% endfor %} - </div> - {% else %} - {{ form.device_drivers(class="form-control", rows=5) }} - {% endif %} + </div> + <div class="row mb-3"> + {{ form.device_config_port.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.device_config_port.errors %} + {{ form.device_config_port(class="form-control is-invalid", rows=5) }} + <div class="invalid-feedback"> + {% for error in form.device_config_port.errors %} + <span>{{ error }}</span> + {% endfor %} </div> - <div id="device_drivers_help" class="form-text"> - List the device drivers by their numerical ID, separated by commas, without spaces between them. Numerical IDs: {{ device_driver_ids }}. + {% else %} + {{ form.device_config_port(class="form-control", rows=5) }} + {% endif %} + </div> + </div> + <div class="row mb-3"> + {{ form.device_config_settings.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.device_config_settings.errors %} + {{ form.device_config_settings(class="form-control is-invalid", rows=5) }} + <div class="invalid-feedback"> + {% for error in form.device_config_settings.errors %} + <span>{{ error }}</span> + {% endfor %} </div> + {% else %} + {{ form.device_config_settings(class="form-control", rows=5) }} + {% endif %} </div> - <div class="d-grid gap-2 d-md-flex justify-content-md-start"> - <button type="submit" class="btn btn-primary"> - <i class="bi bi-plus-circle-fill"></i> - {{ submit_text }} - </button> - <button type="button" class="btn btn-block btn-secondary" onclick="javascript: history.back()"> - <i class="bi bi-box-arrow-in-left"></i> - Cancel - </button> - </div> - </fieldset> - </form> + </div> + <br /> + <div class="d-grid gap-2 d-md-flex justify-content-md-start"> + <button type="submit" class="btn btn-primary"> + <i class="bi bi-plus-circle-fill"></i> + {{ submit_text }} + </button> + <button type="button" class="btn btn-block btn-secondary" onclick="javascript: history.back()"> + <i class="bi bi-box-arrow-in-left"></i> + Cancel + </button> + </div> + </fieldset> +</form> {% endblock %} \ No newline at end of file diff --git a/src/webui/service/templates/device/addconfig.html b/src/webui/service/templates/device/addconfig.html new file mode 100644 index 0000000000000000000000000000000000000000..c6a17e0a5919c974f168c92bf2248980b39a2332 --- /dev/null +++ b/src/webui/service/templates/device/addconfig.html @@ -0,0 +1,69 @@ +<!-- + 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>Add New Configuration</h1> +<br /> +<form id="add_device" method="POST"> + {{ form.hidden_tag() }} + <fieldset> + <div class="row mb-3"> + {{ form.device_key_config.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.device_key_config.errors %} + {{ form.device_key_config(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.device_key_config.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form.device_key_config(class="form-control") }} + {% endif %} + </div> + </div> + <br /> + <div class="row mb-3"> + {{ form.device_value_config.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.device_value_config.errors %} + {{ form.device_value_config(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.device_value_config.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form.device_value_config(class="form-control") }} + {% endif %} + </div> + </div> + <br /> + <div class="d-grid gap-2 d-md-flex justify-content-md-start"> + <button type="submit" class="btn btn-primary"> + <i class="bi bi-plus-circle-fill"></i> + {{ submit_text }} + </button> + <button type="button" class="btn btn-block btn-secondary" onclick="javascript: history.back()"> + <i class="bi bi-box-arrow-in-left"></i> + Cancel + </button> + </div> + </fieldset> +</form> +{% endblock %} \ No newline at end of file diff --git a/src/webui/service/templates/device/detail.html b/src/webui/service/templates/device/detail.html index 69ca93727310db7f89034f56510ceb5df504083f..e49396c4f7971cb19fff5780d3830082f9422a5b 100644 --- a/src/webui/service/templates/device/detail.html +++ b/src/webui/service/templates/device/detail.html @@ -13,118 +13,140 @@ 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> - <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>Status: </b> {{ dose.Name(device.device_operational_status).replace('DEVICEOPERATIONALSTATUS_', '') }}<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> +{% 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="{{ url_for('device.update',device_uuid=device.device_id.device_uuid.uuid) }}"> + <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> - <b>Configurations:</b> +<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>Status: </b> {{ dose.Name(device.device_operational_status).replace('DEVICEOPERATIONALSTATUS_', '') }}<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">Key</th> - <th scope="col">Value</th> + <th scope="col">Endpoints</th> + <th scope="col">Type</th> </tr> </thead> <tbody> - {% for config in device.device_config.config_rules %} - {% if config.WhichOneof('config_rule') == 'custom' %} + {% for endpoint in device.device_endpoints %} <tr> <td> - {{ config.custom.resource_key }} + {{ endpoint.endpoint_id.endpoint_uuid.uuid }} </td> <td> - <ul> - {% for key, value in (config.custom.resource_value | from_json).items() %} - <li><b>{{ key }}:</b> {{ value }}</li> - {% endfor %} - </ul> + {{ endpoint.endpoint_type }} </td> </tr> - {% endif %} {% endfor %} </tbody> </table> + </div> +</div> +</div> +<b>Configurations:</b> - <!-- 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 +<table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">Key</th> + <th scope="col">Value</th> + <th scope="col"></th> + <th scope="col"></th> + </tr> + </thead> + <tbody> + {% for config in device.device_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> + <td> + <a id="update" class="btn btn-secondary" href="{{ url_for('device.updateconfig')}}"> + <i class="bi bi-pencil-square"></i> + </a> + </td> + <td> + <!-- <button type="button" class="btn btn-danger"><i class="bi bi-x-square"></i>Delete device</button> --> + <button type="button" class="btn btn-danger"> + <i class="bi bi-x-square"></i> + </button> + </td> + </tr> + {% endif %} + {% endfor %} + </tbody> +</table> +<div class="col"> + <a href="{{ url_for('device.addconfig', device_uuid=device.device_id.device_uuid.uuid) }}" class="btn btn-primary" style="margin-bottom: 10px;"> + <i class="bi bi-plus"></i> + Add New Configuration + </a> +</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 %} diff --git a/src/webui/service/templates/device/update.html b/src/webui/service/templates/device/update.html new file mode 100644 index 0000000000000000000000000000000000000000..8c474f525c7e206499f65118eced1d9f3c2b4d74 --- /dev/null +++ b/src/webui/service/templates/device/update.html @@ -0,0 +1,35 @@ +{% extends 'base.html' %} + +{% block content %} +<h1>Update Device {{ device.device_id.device_uuid.uuid }}</h1> +<br /> +<form id="update_device" method="POST"> + {{ form.hidden_tag() }} + <fieldset> + <div class="row mb-3"> + {{ form.update_operational_status.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.update_operational_status.errors %} + {{ form.update_operational_status(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.update_operational_status.errors %} + <span>{{ error }}</span> + {% endfor %} + </div> + {% else %} + {{ form.update_operational_status(class="form-select") }} + {% endif %} + </div> + </div> + <button type="submit" class="btn btn-primary"> + <i class="bi bi-plus-circle-fill"></i> + {{ submit_text }} + </button> + <button type="button" class="btn btn-block btn-secondary" onclick="javascript: history.back()"> + <i class="bi bi-box-arrow-in-left"></i> + Cancel + </button> + </div> + </fieldset> +</form> +{% endblock %} \ No newline at end of file diff --git a/src/webui/service/templates/device/updateconfig.html b/src/webui/service/templates/device/updateconfig.html new file mode 100644 index 0000000000000000000000000000000000000000..de217733d7674b3fc27df7e44705630b5fafeba3 --- /dev/null +++ b/src/webui/service/templates/device/updateconfig.html @@ -0,0 +1,18 @@ +<!-- + 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' %} \ No newline at end of file diff --git a/src/webui/service/templates/js/topology.js b/src/webui/service/templates/js/topology.js index 69de0445dac24bf2f7f16ec21da4a6d35133e9da..29156224da2245cd1db75c4384c66b6643130f4c 100644 --- a/src/webui/service/templates/js/topology.js +++ b/src/webui/service/templates/js/topology.js @@ -89,6 +89,8 @@ d3.json("{{ url_for('main.topology') }}", function(data) { // node tooltip node.append("title").text(function(d) { return d.id; }); + // link tooltip + link.append("title").text(function(d) { return d.id; }); // link style link