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/', 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('/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('/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 %} -

Add New Device

- -
- {{ form.hidden_tag() }} -
-
- {{ form.device_id.label(class="col-sm-2 col-form-label") }} -
- {% if form.device_id.errors %} - {{ form.device_id(class="form-control is-invalid") }} -
- {% for error in form.device_id.errors %} - {{ error }} - {% endfor %} -
- {% else %} - {{ form.device_id(class="form-control") }} - {% endif %} +

Add New Device

+
+ + {{ form.hidden_tag() }} +
+
+ {{ form.device_id.label(class="col-sm-2 col-form-label") }} +
+ {% if form.device_id.errors %} + {{ form.device_id(class="form-control is-invalid") }} +
+ {% for error in form.device_id.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.device_id(class="form-control") }} + {% endif %} +
+
+
+
+ {{ form.device_type.label(class="col-sm-2 col-form-label") }} +
+ {% if form.device_type.errors %} + {{ form.device_type(class="form-control is-invalid") }} +
+ {% for error in form.device_type.errors %} + {{ error }} + {% endfor %}
+ {% else %} + {{ form.device_type(class="form-select")}} + {% endif %}
-
- {{ form.device_type.label(class="col-sm-2 col-form-label") }} -
- {% if form.device_type.errors %} - {{ form.device_type(class="form-control is-invalid") }} -
- {% for error in form.device_type.errors %} - {{ error }} - {% endfor %} -
- {% else %} - {{ form.device_type(class="form-control") }} - {% endif %} +
+
+
+ {{ form.operational_status.label(class="col-sm-2 col-form-label") }} +
+ {% if form.operational_status.errors %} + {{ form.operational_status(class="form-control is-invalid") }} +
+ {% for error in form.operational_status.errors %} + {{ error }} + {% endfor %}
+ {% else %} + {{ form.operational_status(class="form-select") }} + {% endif %}
-
- {{ form.operational_status.label(class="col-sm-2 col-form-label") }} -
- {% if form.operational_status.errors %} - {{ form.operational_status(class="form-control is-invalid") }} -
- {% for error in form.operational_status.errors %} - {{ error }} - {% endfor %} -
- {% else %} - {{ form.operational_status(class="form-control") }} - {% endif %} +
+
+
+
Drivers
+
+ {% if form.device_drivers_undefined.errors %} + {{ form.device_drivers_undefined(class="form-control is-invalid") }} +
+ {% for error in form.device_drivers_undefined.errors %} + {{ error }} + {% endfor %}
+ {% 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") }} +
{{ 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") }}
+ {{ form.device_drivers_xr }} {{ form.device_drivers_xr.label(class="col-sm-3 + col-form-label") }} + {% endif %}
-
- {{ form.device_config.label(class="col-sm-2 col-form-label") }} -
- {% if form.device_config.errors %} - {{ form.device_config(class="form-control is-invalid", rows=5) }} -
- {% for error in form.device_config.errors %} - {{ error }} - {% endfor %} -
- {% else %} - {{ form.device_config(class="form-control", rows=5) }} - {% endif %} +
+
+ Configuration Rules
+
+ {{ form.device_config_address.label(class="col-sm-2 col-form-label") }} +
+ {% if form.device_config_address.errors %} + {{ form.device_config_address(class="form-control is-invalid", rows=5) }} +
+ {% for error in form.device_config_address.errors %} + {{ error }} + {% endfor %}
-
The device configurations should follow a key=value format, one configuration per line.
+ {% else %} + {{ form.device_config_address(class="form-control", rows=5) }} + {% endif %}
-
- {{ form.device_drivers.label(class="col-sm-2 col-form-label") }} -
- {% if form.device_drivers.errors %} - {{ form.device_drivers(class="form-control is-invalid", rows=5) }} -
- {% for error in form.device_drivers.errors %} - {{ error }} - {% endfor %} -
- {% else %} - {{ form.device_drivers(class="form-control", rows=5) }} - {% endif %} +
+
+ {{ form.device_config_port.label(class="col-sm-2 col-form-label") }} +
+ {% if form.device_config_port.errors %} + {{ form.device_config_port(class="form-control is-invalid", rows=5) }} +
+ {% for error in form.device_config_port.errors %} + {{ error }} + {% endfor %}
-
- 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 %} +
+
+
+ {{ form.device_config_settings.label(class="col-sm-2 col-form-label") }} +
+ {% if form.device_config_settings.errors %} + {{ form.device_config_settings(class="form-control is-invalid", rows=5) }} +
+ {% for error in form.device_config_settings.errors %} + {{ error }} + {% endfor %}
+ {% else %} + {{ form.device_config_settings(class="form-control", rows=5) }} + {% endif %}
-
- - -
-
- +
+
+
+ + +
+
+ {% 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 @@ + + +{% extends 'base.html' %} + +{% block content %} +

Add New Configuration

+
+
+ {{ form.hidden_tag() }} +
+
+ {{ form.device_key_config.label(class="col-sm-2 col-form-label") }} +
+ {% if form.device_key_config.errors %} + {{ form.device_key_config(class="form-control is-invalid") }} +
+ {% for error in form.device_key_config.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.device_key_config(class="form-control") }} + {% endif %} +
+
+
+
+ {{ form.device_value_config.label(class="col-sm-2 col-form-label") }} +
+ {% if form.device_value_config.errors %} + {{ form.device_value_config(class="form-control is-invalid") }} +
+ {% for error in form.device_value_config.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.device_value_config(class="form-control") }} + {% endif %} +
+
+
+
+ + +
+
+
+{% 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 %} -

Device {{ device.device_id.device_uuid.uuid }}

- -
-
- -
- -
- - -
-
-
-
-
- UUID: {{ device.device_id.device_uuid.uuid }}

- Type: {{ device.device_type }}

- Status: {{ dose.Name(device.device_operational_status).replace('DEVICEOPERATIONALSTATUS_', '') }}
- Drivers: -
    - {% for driver in device.device_drivers %} -
  • {{ dde.Name(driver).replace('DEVICEDRIVER_', '').replace('UNDEFINED', 'EMULATED') }}
  • - {% endfor %} -
-
-
- - - - - - - - - {% for endpoint in device.device_endpoints %} - - - - - {% endfor %} - -
EndpointsType
- {{ endpoint.endpoint_id.endpoint_uuid.uuid }} - - {{ endpoint.endpoint_type }} -
-
-
- +{% extends 'base.html' %} + +{% block content %} +

Device {{ device.device_id.device_uuid.uuid }}

+ +
+
+ +
+ +
+ + +
+
- Configurations: +
+
+
+ UUID: {{ device.device_id.device_uuid.uuid }}

+ Type: {{ device.device_type }}

+ Status: {{ dose.Name(device.device_operational_status).replace('DEVICEOPERATIONALSTATUS_', '') }}
+ Drivers: +
    + {% for driver in device.device_drivers %} +
  • {{ dde.Name(driver).replace('DEVICEDRIVER_', '').replace('UNDEFINED', 'EMULATED') }}
  • + {% endfor %} +
+
+
- - + + - {% for config in device.device_config.config_rules %} - {% if config.WhichOneof('config_rule') == 'custom' %} + {% for endpoint in device.device_endpoints %} - {% endif %} {% endfor %}
KeyValueEndpointsType
- {{ config.custom.resource_key }} + {{ endpoint.endpoint_id.endpoint_uuid.uuid }} -
    - {% for key, value in (config.custom.resource_value | from_json).items() %} -
  • {{ key }}: {{ value }}
  • - {% endfor %} -
+ {{ endpoint.endpoint_type }}
+
+
+ +Configurations: - - - - {% endblock %} - \ No newline at end of file + + + + + + + + + + + {% for config in device.device_config.config_rules %} + {% if config.WhichOneof('config_rule') == 'custom' %} + + + + + + + {% endif %} + {% endfor %} + +
KeyValue
+ {{ config.custom.resource_key }} + +
    + {% for key, value in (config.custom.resource_value | from_json).items() %} +
  • {{ key }}: {{ value }}
  • + {% endfor %} +
+
+ + + + + + +
+ + + + + + +{% 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 %} +

Update Device {{ device.device_id.device_uuid.uuid }}

+
+
+ {{ form.hidden_tag() }} +
+
+ {{ form.update_operational_status.label(class="col-sm-2 col-form-label") }} +
+ {% if form.update_operational_status.errors %} + {{ form.update_operational_status(class="form-control is-invalid") }} +
+ {% for error in form.update_operational_status.errors %} + {{ error }} + {% endfor %} +
+ {% else %} + {{ form.update_operational_status(class="form-select") }} + {% endif %} +
+
+ + + +
+
+{% 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 @@ + + + +{% 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