Commit 95e19c7e authored by Lucie LONG's avatar Lucie LONG
Browse files

WebUI:

- Improving the Add device form
- Updating the drivers list of the Add device form
parent 38cd8b5d
Loading
Loading
Loading
Loading
+13 −23
Original line number Diff line number Diff line
@@ -14,7 +14,7 @@

# 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 webui.utils.form_validators import key_value_validator
@@ -22,33 +22,23 @@ 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!')
 No newline at end of file

    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}.')
+51 −34
Original line number Diff line number Diff line
@@ -16,12 +16,14 @@ 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

device = Blueprint('device', __name__, url_prefix='/device')
context_client = ContextClient()
@@ -57,60 +59,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 = '_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 = 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/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):
+130 −85
Original line number Diff line number Diff line
@@ -18,7 +18,7 @@

{% block content %}
<h1>Add New Device</h1>

<br />
<form id="add_device" method="POST">
    {{ form.hidden_tag() }}
    <fieldset>
@@ -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">
@@ -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">
@@ -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>
+98 −96
Original line number Diff line number Diff line
@@ -108,7 +108,8 @@


<!-- 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 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">
@@ -120,11 +121,12 @@
            </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>
                <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