Skip to content
Snippets Groups Projects
Commit 93af7f2a authored by Carlos Natalino Da Silva's avatar Carlos Natalino Da Silva
Browse files

Implementing the add device form. Now need to integrate with the device component.

parent 3a438c6f
No related branches found
No related tags found
1 merge request!54Release 2.0.0
...@@ -20,7 +20,7 @@ DESCRIPTOR = _descriptor.FileDescriptor( ...@@ -20,7 +20,7 @@ DESCRIPTOR = _descriptor.FileDescriptor(
syntax='proto3', syntax='proto3',
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
serialized_pb=b'\n\rservice.proto\x12\x07service\x1a\rcontext.proto2\xed\x02\n\x0eServiceService\x12\x38\n\x0eGetServiceList\x12\x0e.context.Empty\x1a\x14.context.ServiceList\"\x00\x12\x37\n\rCreateService\x12\x10.context.Service\x1a\x12.context.ServiceId\"\x00\x12\x37\n\rUpdateService\x12\x10.context.Service\x1a\x12.context.ServiceId\"\x00\x12\x35\n\rDeleteService\x12\x12.context.ServiceId\x1a\x0e.context.Empty\"\x00\x12\x38\n\x0eGetServiceById\x12\x12.context.ServiceId\x1a\x10.context.Service\"\x00\x12>\n\x11GetConnectionList\x12\x0e.context.Empty\x1a\x17.context.ConnectionList\"\x00\x62\x06proto3' serialized_pb=b'\n\rservice.proto\x12\x07service\x1a\rcontext.proto2\xfd\x01\n\x0eServiceService\x12\x37\n\rCreateService\x12\x10.context.Service\x1a\x12.context.ServiceId\"\x00\x12\x37\n\rUpdateService\x12\x10.context.Service\x1a\x12.context.ServiceId\"\x00\x12\x35\n\rDeleteService\x12\x12.context.ServiceId\x1a\x0e.context.Empty\"\x00\x12\x42\n\x11GetConnectionList\x12\x12.context.ServiceId\x1a\x17.context.ConnectionList\"\x00\x62\x06proto3'
, ,
dependencies=[context__pb2.DESCRIPTOR,]) dependencies=[context__pb2.DESCRIPTOR,])
...@@ -38,22 +38,12 @@ _SERVICESERVICE = _descriptor.ServiceDescriptor( ...@@ -38,22 +38,12 @@ _SERVICESERVICE = _descriptor.ServiceDescriptor(
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
serialized_start=42, serialized_start=42,
serialized_end=407, serialized_end=295,
methods=[ methods=[
_descriptor.MethodDescriptor(
name='GetServiceList',
full_name='service.ServiceService.GetServiceList',
index=0,
containing_service=None,
input_type=context__pb2._EMPTY,
output_type=context__pb2._SERVICELIST,
serialized_options=None,
create_key=_descriptor._internal_create_key,
),
_descriptor.MethodDescriptor( _descriptor.MethodDescriptor(
name='CreateService', name='CreateService',
full_name='service.ServiceService.CreateService', full_name='service.ServiceService.CreateService',
index=1, index=0,
containing_service=None, containing_service=None,
input_type=context__pb2._SERVICE, input_type=context__pb2._SERVICE,
output_type=context__pb2._SERVICEID, output_type=context__pb2._SERVICEID,
...@@ -63,7 +53,7 @@ _SERVICESERVICE = _descriptor.ServiceDescriptor( ...@@ -63,7 +53,7 @@ _SERVICESERVICE = _descriptor.ServiceDescriptor(
_descriptor.MethodDescriptor( _descriptor.MethodDescriptor(
name='UpdateService', name='UpdateService',
full_name='service.ServiceService.UpdateService', full_name='service.ServiceService.UpdateService',
index=2, index=1,
containing_service=None, containing_service=None,
input_type=context__pb2._SERVICE, input_type=context__pb2._SERVICE,
output_type=context__pb2._SERVICEID, output_type=context__pb2._SERVICEID,
...@@ -73,29 +63,19 @@ _SERVICESERVICE = _descriptor.ServiceDescriptor( ...@@ -73,29 +63,19 @@ _SERVICESERVICE = _descriptor.ServiceDescriptor(
_descriptor.MethodDescriptor( _descriptor.MethodDescriptor(
name='DeleteService', name='DeleteService',
full_name='service.ServiceService.DeleteService', full_name='service.ServiceService.DeleteService',
index=3, index=2,
containing_service=None, containing_service=None,
input_type=context__pb2._SERVICEID, input_type=context__pb2._SERVICEID,
output_type=context__pb2._EMPTY, output_type=context__pb2._EMPTY,
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
), ),
_descriptor.MethodDescriptor(
name='GetServiceById',
full_name='service.ServiceService.GetServiceById',
index=4,
containing_service=None,
input_type=context__pb2._SERVICEID,
output_type=context__pb2._SERVICE,
serialized_options=None,
create_key=_descriptor._internal_create_key,
),
_descriptor.MethodDescriptor( _descriptor.MethodDescriptor(
name='GetConnectionList', name='GetConnectionList',
full_name='service.ServiceService.GetConnectionList', full_name='service.ServiceService.GetConnectionList',
index=5, index=3,
containing_service=None, containing_service=None,
input_type=context__pb2._EMPTY, input_type=context__pb2._SERVICEID,
output_type=context__pb2._CONNECTIONLIST, output_type=context__pb2._CONNECTIONLIST,
serialized_options=None, serialized_options=None,
create_key=_descriptor._internal_create_key, create_key=_descriptor._internal_create_key,
......
# external imports # external imports
from flask_wtf import FlaskForm from flask_wtf import FlaskForm
from wtforms import StringField, SelectField, SubmitField from wtforms import StringField, SelectField, TextAreaField, SubmitField
from wtforms.validators import DataRequired, Length, NumberRange from wtforms.validators import DataRequired, Length, NumberRange, Regexp, ValidationError
from webui.utils.form_validators import key_value_validator
from webui.proto.context_pb2 import (DeviceDriverEnum, DeviceOperationalStatusEnum)
class AddDeviceForm(FlaskForm): class AddDeviceForm(FlaskForm):
device_id = StringField('Device ID', device_id = StringField('ID',
validators=[DataRequired(), Length(min=5)]) validators=[DataRequired(), Length(min=5)])
device_type = StringField('Device Type', device_type = StringField('Type',
validators=[DataRequired(), Length(min=5)]) validators=[DataRequired(), Length(min=5)])
device_config = TextAreaField('Configurations', validators=[key_value_validator()])
operational_status = SelectField('Operational Status', operational_status = SelectField('Operational Status',
choices=[(-1, 'Select...'), (0, 'Undefined'), (1, 'Disabled'), (2, 'Enabled')], # choices=[(-1, 'Select...'), (0, 'Undefined'), (1, 'Disabled'), (2, 'Enabled')],
coerce=int, coerce=int,
validators=[DataRequired(), NumberRange(min=0)]) validators=[NumberRange(min=0)])
device_drivers = TextAreaField('Drivers', validators=[DataRequired(), Regexp('^\d+(,\d+)*$')])
submit = SubmitField('Add') 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}.')
from flask import render_template, Blueprint, flash, session from flask import render_template, Blueprint, flash, session
from context.proto.context_pb2 import ConfigActionEnum, ConfigRule, TopologyIdList, TopologyList
from webui.Config import CONTEXT_SERVICE_ADDRESS, CONTEXT_SERVICE_PORT from webui.Config import CONTEXT_SERVICE_ADDRESS, CONTEXT_SERVICE_PORT
from context.client.ContextClient import ContextClient from context.client.ContextClient import ContextClient
from webui.proto.context_pb2 import (ContextId, DeviceList, DeviceId, from webui.proto.context_pb2 import (ContextId, DeviceList, DeviceId,
...@@ -21,10 +22,63 @@ def home(): ...@@ -21,10 +22,63 @@ def home():
@device.route('add', methods=['GET', 'POST']) @device.route('add', methods=['GET', 'POST'])
def add(): def add():
form = AddDeviceForm() form = AddDeviceForm()
request: ContextId = ContextId()
request.context_uuid.uuid = session['context_uuid']
client: ContextClient = ContextClient(CONTEXT_SERVICE_ADDRESS, CONTEXT_SERVICE_PORT)
response: TopologyIdList = client.ListTopologyIds(request)
client.close()
# listing enum values
form.operational_status.choices = [(-1, 'Select...')]
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)
if form.validate_on_submit(): if form.validate_on_submit():
pass device: Device = Device()
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.resource_key = parts[0].strip()
config_rule.resource_value = parts[1].strip()
device.device_config.config_rules.extend([config_rule])
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.extend([int(driver)])
client: ContextClient = ContextClient(CONTEXT_SERVICE_ADDRESS, CONTEXT_SERVICE_PORT)
response: DeviceId = client.SetDevice(request)
client.close()
flash(f'New device was created with ID "{response.device_uuid.uuid}".', 'success')
return render_template('device/add.html', form=form, return render_template('device/add.html', form=form,
submit_text='Add New Device') submit_text='Add New Device',
device_driver_ids=device_driver_ids)
@device.route('detail/<device_uuid>', methods=['GET', 'POST']) @device.route('detail/<device_uuid>', methods=['GET', 'POST'])
def detail(device_uuid: str): def detail(device_uuid: str):
...@@ -32,6 +86,5 @@ def detail(device_uuid: str): ...@@ -32,6 +86,5 @@ def detail(device_uuid: str):
request.device_uuid.uuid = device_uuid request.device_uuid.uuid = device_uuid
client: ContextClient = ContextClient(CONTEXT_SERVICE_ADDRESS, CONTEXT_SERVICE_PORT) client: ContextClient = ContextClient(CONTEXT_SERVICE_ADDRESS, CONTEXT_SERVICE_PORT)
response: Device = client.GetDevice(request) response: Device = client.GetDevice(request)
print(response)
client.close() client.close()
return render_template('device/detail.html', device=response) return render_template('device/detail.html', device=response)
var tooltipTriggerList = [].slice.call(document.querySelectorAll('[data-bs-toggle="tooltip"]'))
var tooltipList = tooltipTriggerList.map(function (tooltipTriggerEl) {
return new bootstrap.Tooltip(tooltipTriggerEl)
})
...@@ -108,6 +108,7 @@ ...@@ -108,6 +108,7 @@
<!-- Option 1: Bootstrap Bundle with Popper --> <!-- Option 1: Bootstrap Bundle with Popper -->
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-kQtW33rZJAHjgefvhyyzcGF3C5TFyBQBA13V1RKPf4uH+bwyzQxZ6CmMZHmNBEfJ" crossorigin="anonymous"></script> <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.1.2/dist/js/bootstrap.bundle.min.js" integrity="sha384-kQtW33rZJAHjgefvhyyzcGF3C5TFyBQBA13V1RKPf4uH+bwyzQxZ6CmMZHmNBEfJ" crossorigin="anonymous"></script>
<!-- <script src="{{ url_for('static', filename='site.js') }}"/> -->
<!-- Option 2: Separate Popper and Bootstrap JS --> <!-- Option 2: Separate Popper and Bootstrap JS -->
<!-- <!--
......
...@@ -21,6 +21,70 @@ ...@@ -21,6 +21,70 @@
{% endif %} {% endif %}
</div> </div>
</div> </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>
</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>
</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>
<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") }}
<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 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>
<div class="row mb-3"> <div class="row mb-3">
<button type="submit" class="btn btn-primary">{{ submit_text }}</button> <button type="submit" class="btn btn-primary">{{ submit_text }}</button>
</div> </div>
......
...@@ -68,7 +68,7 @@ def test_device_detail_page(client, device_id): ...@@ -68,7 +68,7 @@ def test_device_detail_page(client, device_id):
assert rw.status_code == 200 assert rw.status_code == 200
assert b'Device' in rw.data assert b'Device' in rw.data
assert device_id in rw.data.decode() assert device_id in rw.data.decode()
assert b'Endpoints' in rw.data, 'Missing information on the device detail page.' assert b'Endpoints' in rw.data, 'Missing endpoint information on the device detail page.'
# assert b'Add New Device' in rw.data # assert b'Add New Device' in rw.data
def test_device_add_page(client): def test_device_add_page(client):
...@@ -78,3 +78,6 @@ def test_device_add_page(client): ...@@ -78,3 +78,6 @@ def test_device_add_page(client):
assert rw.status_code == 200 assert rw.status_code == 200
assert b'Add New Device' in rw.data assert b'Add New Device' in rw.data
assert b'Operational Status' in rw.data, 'Form is not correctly implemented.' assert b'Operational Status' in rw.data, 'Form is not correctly implemented.'
assert b'Type' in rw.data, 'Form is not correctly implemented.'
assert b'Configurations' in rw.data, 'Form is not correctly implemented.'
assert b'Drivers' in rw.data, 'Form is not correctly implemented.'
from wtforms.validators import ValidationError
def key_value_validator():
def _validate(form, field):
if len(field.data) > 0:
if '\n' not in field.data: # case in which there is only one configuration
if '=' not in field.data:
raise ValidationError(f'Configuration "{field.data}" does not follow the key=value pattern.')
else: # case in which there are several configurations
configurations = field.data.split('\n')
for configutation in configurations:
if '=' not in configutation:
raise ValidationError(f'Configuration "{configutation}" does not follow the key=value pattern.')
return _validate
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