Loading src/start_webui_dev_mode.sh +9 −5 Original line number Diff line number Diff line Loading @@ -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 Loading src/webui/service/device/forms.py +26 −22 Original line number Diff line number Diff line Loading @@ -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') src/webui/service/device/routes.py +117 −34 Original line number Diff line number Diff line Loading @@ -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() Loading Loading @@ -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() # 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 = parts[0].strip() config_rule.custom.resource_value = parts[1].strip() device.device_config.config_rules.append(config_rule) 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() # 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) for driver in data.split(','): driver = driver.strip() if len(driver) == 0: continue device.device_drivers.append(int(driver)) 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): Loading Loading @@ -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') src/webui/service/templates/device/add.html +130 −85 Original line number Diff line number Diff line Loading @@ -18,7 +18,7 @@ {% block content %} <h1>Add New Device</h1> <br /> <form id="add_device" method="POST"> {{ form.hidden_tag() }} <fieldset> Loading @@ -37,6 +37,7 @@ {% 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"> Loading @@ -48,10 +49,11 @@ {% endfor %} </div> {% else %} {{ form.device_type(class="form-control") }} {{ form.device_type(class="form-select")}} {% endif %} </div> </div> <br /> <div class="row mb-3"> {{ form.operational_status.label(class="col-sm-2 col-form-label") }} <div class="col-sm-10"> Loading @@ -63,44 +65,87 @@ {% endfor %} </div> {% else %} {{ form.operational_status(class="form-control") }} {{ form.operational_status(class="form-select") }} {% endif %} </div> </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> <br /> Configuration Rules <br /> <div class="row mb-3"> {{ form.device_config.label(class="col-sm-2 col-form-label") }} {{ form.device_config_address.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) }} {% 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.errors %} {% for error in form.device_config_address.errors %} <span>{{ error }}</span> {% endfor %} </div> {% else %} {{ form.device_config(class="form-control", rows=5) }} {{ form.device_config_address(class="form-control", rows=5) }} {% endif %} </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> </div> <div class="row mb-3"> {{ form.device_drivers.label(class="col-sm-2 col-form-label") }} {{ form.device_config_port.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) }} {% 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_drivers.errors %} {% for error in form.device_config_port.errors %} <span>{{ error }}</span> {% endfor %} </div> {% else %} {{ form.device_drivers(class="form-control", rows=5) }} {{ form.device_config_port(class="form-control", rows=5) }} {% endif %} </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 }}. </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> <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> Loading src/webui/service/templates/device/addconfig.html 0 → 100644 +69 −0 Original line number Diff line number Diff line <!-- Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> {% extends 'base.html' %} {% block content %} <h1>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 Loading
src/start_webui_dev_mode.sh +9 −5 Original line number Diff line number Diff line Loading @@ -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 Loading
src/webui/service/device/forms.py +26 −22 Original line number Diff line number Diff line Loading @@ -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')
src/webui/service/device/routes.py +117 −34 Original line number Diff line number Diff line Loading @@ -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() Loading Loading @@ -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() # 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 = parts[0].strip() config_rule.custom.resource_value = parts[1].strip() device.device_config.config_rules.append(config_rule) 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() # 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) for driver in data.split(','): driver = driver.strip() if len(driver) == 0: continue device.device_drivers.append(int(driver)) 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): Loading Loading @@ -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')
src/webui/service/templates/device/add.html +130 −85 Original line number Diff line number Diff line Loading @@ -18,7 +18,7 @@ {% block content %} <h1>Add New Device</h1> <br /> <form id="add_device" method="POST"> {{ form.hidden_tag() }} <fieldset> Loading @@ -37,6 +37,7 @@ {% 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"> Loading @@ -48,10 +49,11 @@ {% endfor %} </div> {% else %} {{ form.device_type(class="form-control") }} {{ form.device_type(class="form-select")}} {% endif %} </div> </div> <br /> <div class="row mb-3"> {{ form.operational_status.label(class="col-sm-2 col-form-label") }} <div class="col-sm-10"> Loading @@ -63,44 +65,87 @@ {% endfor %} </div> {% else %} {{ form.operational_status(class="form-control") }} {{ form.operational_status(class="form-select") }} {% endif %} </div> </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> <br /> Configuration Rules <br /> <div class="row mb-3"> {{ form.device_config.label(class="col-sm-2 col-form-label") }} {{ form.device_config_address.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) }} {% 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.errors %} {% for error in form.device_config_address.errors %} <span>{{ error }}</span> {% endfor %} </div> {% else %} {{ form.device_config(class="form-control", rows=5) }} {{ form.device_config_address(class="form-control", rows=5) }} {% endif %} </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> </div> <div class="row mb-3"> {{ form.device_drivers.label(class="col-sm-2 col-form-label") }} {{ form.device_config_port.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) }} {% 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_drivers.errors %} {% for error in form.device_config_port.errors %} <span>{{ error }}</span> {% endfor %} </div> {% else %} {{ form.device_drivers(class="form-control", rows=5) }} {{ form.device_config_port(class="form-control", rows=5) }} {% endif %} </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 }}. </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> <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> Loading
src/webui/service/templates/device/addconfig.html 0 → 100644 +69 −0 Original line number Diff line number Diff line <!-- Copyright 2021-2023 H2020 TeraFlow (https://www.teraflow-h2020.eu/) Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. --> {% extends 'base.html' %} {% block content %} <h1>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