diff --git a/proto/load_generator.proto b/proto/load_generator.proto index 395afbb883d4c014432a056ada5ca5bb467738a3..32523b331418813b51fb542d9eb17e29fc2b13d2 100644 --- a/proto/load_generator.proto +++ b/proto/load_generator.proto @@ -48,22 +48,25 @@ message ScalarOrRange { message Parameters { uint64 num_requests = 1; // if == 0, generate infinite requests repeated RequestTypeEnum request_types = 2; - float offered_load = 3; - float holding_time = 4; - float inter_arrival_time = 5; - repeated ScalarOrRange availability = 6; // one from the list is selected - repeated ScalarOrRange capacity_gbps = 7; // one from the list is selected - repeated ScalarOrRange e2e_latency_ms = 8; // one from the list is selected - uint32 max_workers = 9; - bool do_teardown = 10; - bool dry_mode = 11; - bool record_to_dlt = 12; - string dlt_domain_id = 13; + string device_regex = 3; // Only devices and endpoints matching the regex expression will be considered as + string endpoint_regex = 4; // source-destination candidates for the requests generated. + float offered_load = 5; + float holding_time = 6; + float inter_arrival_time = 7; + repeated ScalarOrRange availability = 8; // One from the list is selected to populate the constraint + repeated ScalarOrRange capacity_gbps = 9; // One from the list is selected to populate the constraint + repeated ScalarOrRange e2e_latency_ms = 10; // One from the list is selected to populate the constraint + uint32 max_workers = 11; + bool do_teardown = 12; + bool dry_mode = 13; + bool record_to_dlt = 14; + string dlt_domain_id = 15; } message Status { Parameters parameters = 1; uint64 num_generated = 2; - bool infinite_loop = 3; - bool running = 4; + uint64 num_released = 3; + bool infinite_loop = 4; + bool running = 5; } diff --git a/src/load_generator/command/__main__.py b/src/load_generator/command/__main__.py index a97f081a32269ff824733b9a2a69be21bfb2004f..4fa2094e0fdc94b9665b2cfc86811e67809bcb5f 100644 --- a/src/load_generator/command/__main__.py +++ b/src/load_generator/command/__main__.py @@ -34,6 +34,8 @@ def main(): RequestType.SLICE_L2NM, RequestType.SLICE_L3NM, ], + device_regex=r'.+', + endpoint_regex=r'.+', offered_load = 50, holding_time = 10, availability_ranges = [[0.0, 99.9999]], diff --git a/src/load_generator/load_gen/Parameters.py b/src/load_generator/load_gen/Parameters.py index aca40cd3854fad203f15ce9b07a79715e9ea46f6..5bb7a9b725f955a4186a21201e439f9cfaa71324 100644 --- a/src/load_generator/load_gen/Parameters.py +++ b/src/load_generator/load_gen/Parameters.py @@ -20,16 +20,27 @@ from load_generator.tools.ListScalarRange import Type_ListScalarRange class Parameters: def __init__( - self, num_requests : int, request_types : List[str], offered_load : Optional[float] = None, - inter_arrival_time : Optional[float] = None, holding_time : Optional[float] = None, + self, + num_requests : int, + request_types : List[str], + device_regex : Optional[str] = None, + endpoint_regex : Optional[str] = None, + offered_load : Optional[float] = None, + inter_arrival_time : Optional[float] = None, + holding_time : Optional[float] = None, availability_ranges : Type_ListScalarRange = DEFAULT_AVAILABILITY_RANGES, capacity_gbps_ranges : Type_ListScalarRange = DEFAULT_CAPACITY_GBPS_RANGES, e2e_latency_ms_ranges : Type_ListScalarRange = DEFAULT_E2E_LATENCY_MS_RANGES, - max_workers : int = DEFAULT_MAX_WORKERS, do_teardown : bool = True, dry_mode : bool = False, - record_to_dlt : bool = False, dlt_domain_id : Optional[str] = None + max_workers : int = DEFAULT_MAX_WORKERS, + do_teardown : bool = True, + dry_mode : bool = False, + record_to_dlt : bool = False, + dlt_domain_id : Optional[str] = None ) -> None: self._num_requests = num_requests self._request_types = request_types + self._device_regex = r'.*' if (device_regex is None or len(device_regex) == 0) else device_regex + self._endpoint_regex = r'.*' if (endpoint_regex is None or len(endpoint_regex) == 0) else endpoint_regex self._offered_load = offered_load self._inter_arrival_time = inter_arrival_time self._holding_time = holding_time @@ -62,6 +73,12 @@ class Parameters: @property def request_types(self): return self._request_types + @property + def device_regex(self): return self._device_regex + + @property + def endpoint_regex(self): return self._endpoint_regex + @property def offered_load(self): return self._offered_load diff --git a/src/load_generator/load_gen/RequestGenerator.py b/src/load_generator/load_gen/RequestGenerator.py index ab8f7e30e7e9de61bcfa7f5c52b7a09deb00ba2a..974ce6f130e9c81f273f418b0a1440d148fcfb74 100644 --- a/src/load_generator/load_gen/RequestGenerator.py +++ b/src/load_generator/load_gen/RequestGenerator.py @@ -12,7 +12,7 @@ # See the License for the specific language governing permissions and # limitations under the License. -import logging, json, random, re, threading +import logging, json, random, re, threading, uuid from typing import Dict, Optional, Set, Tuple from common.proto.context_pb2 import Empty, IsolationLevelEnum, TopologyId from common.tools.grpc.Tools import grpc_message_to_json @@ -54,6 +54,7 @@ class RequestGenerator: self._parameters = parameters self._lock = threading.Lock() self._num_generated = 0 + self._num_released = 0 self._available_device_endpoints : Dict[str, Set[str]] = dict() self._used_device_endpoints : Dict[str, Dict[str, str]] = dict() self._endpoint_ids_to_types : Dict[Tuple[str, str], str] = dict() @@ -65,6 +66,9 @@ class RequestGenerator: @property def num_generated(self): return self._num_generated + @property + def num_released(self): return self._num_released + @property def infinite_loop(self): return self._parameters.num_requests == 0 @@ -79,13 +83,21 @@ class RequestGenerator: if self._parameters.record_to_dlt: dlt_domain_id = TopologyId(**json_topology_id('dlt-perf-eval')) + re_device = re.compile(r'^{:s}$'.format(self._parameters.device_regex)) + re_endpoint = re.compile(r'^{:s}$'.format(self._parameters.endpoint_regex)) + devices = context_client.ListDevices(Empty()) for device in devices.devices: + if self._parameters.record_to_dlt: + record_device_to_dlt(dlt_connector_client, dlt_domain_id, device.device_id) + + if re_device.match(device.name) is None: continue device_uuid = device.device_id.device_uuid.uuid self._device_data[device_uuid] = grpc_message_to_json(device) _endpoints = self._available_device_endpoints.setdefault(device_uuid, set()) for endpoint in device.device_endpoints: + if re_endpoint.match(endpoint.name) is None: continue endpoint_uuid = endpoint.endpoint_id.endpoint_uuid.uuid endpoints = self._device_endpoint_data.setdefault(device_uuid, dict()) endpoints[endpoint_uuid] = grpc_message_to_json(endpoint) @@ -94,12 +106,12 @@ class RequestGenerator: _endpoints.add(endpoint_uuid) self._endpoint_ids_to_types.setdefault((device_uuid, endpoint_uuid), endpoint_type) self._endpoint_types_to_ids.setdefault(endpoint_type, set()).add((device_uuid, endpoint_uuid)) - - if self._parameters.record_to_dlt: - record_device_to_dlt(dlt_connector_client, dlt_domain_id, device.device_id) links = context_client.ListLinks(Empty()) for link in links.links: + if self._parameters.record_to_dlt: + record_link_to_dlt(dlt_connector_client, dlt_domain_id, link.link_id) + for endpoint_id in link.link_endpoint_ids: device_uuid = endpoint_id.device_id.device_uuid.uuid endpoint_uuid = endpoint_id.endpoint_uuid.uuid @@ -115,9 +127,6 @@ class RequestGenerator: endpoint_key = (device_uuid, endpoint_uuid) if endpoint_key not in endpoints_for_type: continue endpoints_for_type.discard(endpoint_key) - - if self._parameters.record_to_dlt: - record_link_to_dlt(dlt_connector_client, dlt_domain_id, link.link_id) def dump_state(self) -> None: with self._lock: @@ -192,23 +201,18 @@ class RequestGenerator: if not self.infinite_loop and (self._num_generated >= self._parameters.num_requests): LOGGER.info('Generation Done!') return True, None, None # completed - self._num_generated += 1 - num_request = self._num_generated - #request_uuid = str(uuid.uuid4()) - request_uuid = 'svc_{:d}'.format(num_request) - - # choose request type + request_uuid = str(uuid.uuid4()) request_type = random.choice(self._parameters.request_types) if request_type in { RequestType.SERVICE_L2NM, RequestType.SERVICE_L3NM, RequestType.SERVICE_TAPI, RequestType.SERVICE_MW }: - return False, self._compose_service(num_request, request_uuid, request_type), request_type + return False, self._compose_service(request_uuid, request_type), request_type elif request_type in {RequestType.SLICE_L2NM, RequestType.SLICE_L3NM}: - return False, self._compose_slice(num_request, request_uuid, request_type), request_type + return False, self._compose_slice(request_uuid, request_type), request_type - def _compose_service(self, num_request : int, request_uuid : str, request_type : str) -> Optional[Dict]: + def _compose_service(self, request_uuid : str, request_type : str) -> Optional[Dict]: # choose source endpoint src_endpoint_types = set(ENDPOINT_COMPATIBILITY.keys()) if request_type in {RequestType.SERVICE_TAPI} else None src = self._use_device_endpoint(request_uuid, request_type, endpoint_types=src_endpoint_types) @@ -237,6 +241,10 @@ class RequestGenerator: self._release_device_endpoint(src_device_uuid, src_endpoint_uuid) return None + with self._lock: + self._num_generated += 1 + num_request = self._num_generated + # compose endpoints dst_device_uuid,dst_endpoint_uuid = dst endpoint_ids = [ @@ -383,7 +391,7 @@ class RequestGenerator: return json_service_l2nm_planned( request_uuid, endpoint_ids=endpoint_ids, constraints=[], config_rules=config_rules) - def _compose_slice(self, num_request : int, request_uuid : str, request_type : str) -> Optional[Dict]: + def _compose_slice(self, request_uuid : str, request_type : str) -> Optional[Dict]: # choose source endpoint src = self._use_device_endpoint(request_uuid, request_type) if src is None: @@ -404,6 +412,10 @@ class RequestGenerator: self._release_device_endpoint(src_device_uuid, src_endpoint_uuid) return None + with self._lock: + self._num_generated += 1 + num_request = self._num_generated + # compose endpoints dst_device_uuid,dst_endpoint_uuid = dst endpoint_ids = [ @@ -505,8 +517,15 @@ class RequestGenerator: device_uuid = endpoint_id['device_id']['device_uuid']['uuid'] endpoint_uuid = endpoint_id['endpoint_uuid']['uuid'] self._release_device_endpoint(device_uuid, endpoint_uuid) + + with self._lock: + self._num_released += 1 + elif 'slice_id' in json_request: for endpoint_id in json_request['slice_endpoint_ids']: device_uuid = endpoint_id['device_id']['device_uuid']['uuid'] endpoint_uuid = endpoint_id['endpoint_uuid']['uuid'] self._release_device_endpoint(device_uuid, endpoint_uuid) + + with self._lock: + self._num_released += 1 diff --git a/src/load_generator/load_gen/RequestScheduler.py b/src/load_generator/load_gen/RequestScheduler.py index 08876e29fa7da3f50ba78553a6aee81f0add7b56..340a5411bacee7b0aeb495963cdf65fbb5d14389 100644 --- a/src/load_generator/load_gen/RequestScheduler.py +++ b/src/load_generator/load_gen/RequestScheduler.py @@ -57,6 +57,9 @@ class RequestScheduler: @property def num_generated(self): return min(self._generator.num_generated, self._parameters.num_requests) + @property + def num_released(self): return min(self._generator.num_released, self._parameters.num_requests) + @property def infinite_loop(self): return self._generator.infinite_loop diff --git a/src/load_generator/service/LoadGeneratorServiceServicerImpl.py b/src/load_generator/service/LoadGeneratorServiceServicerImpl.py index 41c12d8e461364c5994b9ba0989c8d4241d3e3fe..866f9f089662598b08c8dd03d04b01fd63108f5a 100644 --- a/src/load_generator/service/LoadGeneratorServiceServicerImpl.py +++ b/src/load_generator/service/LoadGeneratorServiceServicerImpl.py @@ -37,6 +37,8 @@ class LoadGeneratorServiceServicerImpl(LoadGeneratorServiceServicer): self._parameters = LoadGen_Parameters( num_requests = request.num_requests, request_types = [REQUEST_TYPE_MAP[rt] for rt in request.request_types], + device_regex = request.device_regex, + endpoint_regex = request.endpoint_regex, offered_load = request.offered_load if request.offered_load > 1.e-12 else None, holding_time = request.holding_time if request.holding_time > 1.e-12 else None, inter_arrival_time = request.inter_arrival_time if request.inter_arrival_time > 1.e-12 else None, @@ -73,11 +75,14 @@ class LoadGeneratorServiceServicerImpl(LoadGeneratorServiceServicer): status = Status() status.num_generated = self._scheduler.num_generated + status.num_released = self._scheduler.num_released status.infinite_loop = self._scheduler.infinite_loop status.running = self._scheduler.running stat_pars = status.parameters # pylint: disable=no-member stat_pars.num_requests = params.num_requests # pylint: disable=no-member + stat_pars.device_regex = params.device_regex # pylint: disable=no-member + stat_pars.endpoint_regex = params.endpoint_regex # pylint: disable=no-member stat_pars.offered_load = params.offered_load # pylint: disable=no-member stat_pars.holding_time = params.holding_time # pylint: disable=no-member stat_pars.inter_arrival_time = params.inter_arrival_time # pylint: disable=no-member diff --git a/src/webui/service/link/routes.py b/src/webui/service/link/routes.py index 0fda8958e2ab2609969d2c1f68aaae61b7360b68..3ab320d8b13d037e195f00ced9eb63bd14ecc0dd 100644 --- a/src/webui/service/link/routes.py +++ b/src/webui/service/link/routes.py @@ -13,8 +13,8 @@ # limitations under the License. -from flask import render_template, Blueprint, flash, session, redirect, url_for -from common.proto.context_pb2 import Empty, Link, LinkList +from flask import current_app, render_template, Blueprint, flash, session, redirect, url_for +from common.proto.context_pb2 import Empty, Link, LinkId, LinkList from common.tools.context_queries.EndPoint import get_endpoint_names from common.tools.context_queries.Link import get_link from common.tools.context_queries.Topology import get_topology @@ -65,3 +65,25 @@ def detail(link_uuid: str): device_names, endpoints_data = get_endpoint_names(context_client, link_obj.link_endpoint_ids) context_client.close() return render_template('link/detail.html',link=link_obj, device_names=device_names, endpoints_data=endpoints_data) + +@link.get('<path:link_uuid>/delete') +def delete(link_uuid): + try: + + # first, check if link exists! + # request: LinkId = LinkId() + # request.link_uuid.uuid = link_uuid + # response: Link = client.GetLink(request) + # TODO: finalize implementation + + request = LinkId() + request.link_uuid.uuid = link_uuid # pylint: disable=no-member + context_client.connect() + context_client.RemoveLink(request) + context_client.close() + + flash(f'Link "{link_uuid}" deleted successfully!', 'success') + except Exception as e: # pylint: disable=broad-except + flash(f'Problem deleting link "{link_uuid}": {e.details()}', 'danger') + current_app.logger.exception(e) + return redirect(url_for('link.home')) diff --git a/src/webui/service/load_gen/forms.py b/src/webui/service/load_gen/forms.py index 8092b3193563df52e9c538e20890835f799fe8ee..4c5c095cd0ca7b0549a14be394e517694d9b3268 100644 --- a/src/webui/service/load_gen/forms.py +++ b/src/webui/service/load_gen/forms.py @@ -21,9 +21,12 @@ DEFAULT_AVAILABILITY = '0.0..99.9999' DEFAULT_CAPACITY_GBPS = '0.1..100.00' #'10, 40, 50, 100, 400' DEFAULT_E2E_LATENCY_MS = '5.0..100.00' +DEFAULT_REGEX = r'.+' + class LoadGenForm(FlaskForm): num_requests = IntegerField('Num Requests', default=100, validators=[DataRequired(), NumberRange(min=0)]) num_generated = IntegerField('Num Generated', default=0, render_kw={'readonly': True}) + num_released = IntegerField('Num Released', default=0, render_kw={'readonly': True}) request_type_service_l2nm = BooleanField('Service L2NM', default=False) request_type_service_l3nm = BooleanField('Service L3NM', default=False) @@ -32,6 +35,9 @@ class LoadGenForm(FlaskForm): request_type_slice_l2nm = BooleanField('Slice L2NM', default=True) request_type_slice_l3nm = BooleanField('Slice L3NM', default=False) + device_regex = StringField('Device selector [regex]', default=DEFAULT_REGEX) + endpoint_regex = StringField('Endpoint selector [regex]', default=DEFAULT_REGEX) + offered_load = FloatField('Offered Load [Erlang]', default=50, validators=[NumberRange(min=0.0)]) holding_time = FloatField('Holding Time [seconds]', default=10, validators=[NumberRange(min=0.0)]) inter_arrival_time = FloatField('Inter Arrival Time [seconds]', default=0, validators=[NumberRange(min=0.0)]) diff --git a/src/webui/service/load_gen/routes.py b/src/webui/service/load_gen/routes.py index 1b4f9c6ba971c17fc3ac9216aad6cc39b20c8151..3483c2a65d08f3da18b2f630dbf7a59ac0f22ecb 100644 --- a/src/webui/service/load_gen/routes.py +++ b/src/webui/service/load_gen/routes.py @@ -62,26 +62,29 @@ def home(): _e2e_latency_ms = list_scalar_range__grpc_to_str(status.parameters.e2e_latency_ms) form = LoadGenForm() - set_properties(form.num_requests , status.parameters.num_requests , readonly=status.running) - set_properties(form.offered_load , _offered_load , readonly=status.running) - set_properties(form.holding_time , _holding_time , readonly=status.running) - set_properties(form.inter_arrival_time , _inter_arrival_time , readonly=status.running) - set_properties(form.availability , _availability , readonly=status.running) - set_properties(form.capacity_gbps , _capacity_gbps , readonly=status.running) - set_properties(form.e2e_latency_ms , _e2e_latency_ms , readonly=status.running) - set_properties(form.max_workers , status.parameters.max_workers , readonly=status.running) - set_properties(form.do_teardown , status.parameters.do_teardown , disabled=status.running) - set_properties(form.record_to_dlt , status.parameters.record_to_dlt, disabled=status.running) - set_properties(form.dlt_domain_id , status.parameters.dlt_domain_id, readonly=status.running) - set_properties(form.request_type_service_l2nm, _request_type_service_l2nm , disabled=status.running) - set_properties(form.request_type_service_l3nm, _request_type_service_l3nm , disabled=status.running) - set_properties(form.request_type_service_mw , _request_type_service_mw , disabled=status.running) - set_properties(form.request_type_service_tapi, _request_type_service_tapi , disabled=status.running) - set_properties(form.request_type_slice_l2nm , _request_type_slice_l2nm , disabled=status.running) - set_properties(form.request_type_slice_l3nm , _request_type_slice_l3nm , disabled=status.running) - set_properties(form.num_generated , status.num_generated , disabled=True) - set_properties(form.infinite_loop , status.infinite_loop , disabled=True) - set_properties(form.running , status.running , disabled=True) + set_properties(form.num_requests , status.parameters.num_requests , readonly=status.running) + set_properties(form.device_regex , status.parameters.device_regex , readonly=status.running) + set_properties(form.endpoint_regex , status.parameters.endpoint_regex, readonly=status.running) + set_properties(form.offered_load , _offered_load , readonly=status.running) + set_properties(form.holding_time , _holding_time , readonly=status.running) + set_properties(form.inter_arrival_time , _inter_arrival_time , readonly=status.running) + set_properties(form.availability , _availability , readonly=status.running) + set_properties(form.capacity_gbps , _capacity_gbps , readonly=status.running) + set_properties(form.e2e_latency_ms , _e2e_latency_ms , readonly=status.running) + set_properties(form.max_workers , status.parameters.max_workers , readonly=status.running) + set_properties(form.do_teardown , status.parameters.do_teardown , disabled=status.running) + set_properties(form.record_to_dlt , status.parameters.record_to_dlt , disabled=status.running) + set_properties(form.dlt_domain_id , status.parameters.dlt_domain_id , readonly=status.running) + set_properties(form.request_type_service_l2nm, _request_type_service_l2nm , disabled=status.running) + set_properties(form.request_type_service_l3nm, _request_type_service_l3nm , disabled=status.running) + set_properties(form.request_type_service_mw , _request_type_service_mw , disabled=status.running) + set_properties(form.request_type_service_tapi, _request_type_service_tapi , disabled=status.running) + set_properties(form.request_type_slice_l2nm , _request_type_slice_l2nm , disabled=status.running) + set_properties(form.request_type_slice_l3nm , _request_type_slice_l3nm , disabled=status.running) + set_properties(form.num_generated , status.num_generated , disabled=True) + set_properties(form.num_released , status.num_released , disabled=True) + set_properties(form.infinite_loop , status.infinite_loop , disabled=True) + set_properties(form.running , status.running , disabled=True) form.submit.label.text = 'Stop' if status.running else 'Start' form_action = url_for('load_gen.stop') if status.running else url_for('load_gen.start') @@ -98,6 +101,8 @@ def start(): load_gen_params = Parameters() load_gen_params.num_requests = form.num_requests.data + load_gen_params.device_regex = form.device_regex.data + load_gen_params.endpoint_regex = form.endpoint_regex.data load_gen_params.offered_load = form.offered_load.data load_gen_params.holding_time = form.holding_time.data load_gen_params.inter_arrival_time = form.inter_arrival_time.data diff --git a/src/webui/service/templates/link/detail.html b/src/webui/service/templates/link/detail.html index 916abafde05b3ec990346ff7966f207b1dafc10a..8ca7faee3e1871d11b819c6ca95668e654041f8c 100644 --- a/src/webui/service/templates/link/detail.html +++ b/src/webui/service/templates/link/detail.html @@ -13,62 +13,92 @@ See the License for the specific language governing permissions and limitations under the License. --> - {% extends 'base.html' %} - - {% block content %} - <h1>Link {{ link.name }} ({{ link.link_id.link_uuid.uuid }})</h1> - <div class="row mb-3"> - <div class="col-sm-3"> - <button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('link.home') }}'"> - <i class="bi bi-box-arrow-in-left"></i> - Back to link list - </button> - </div> - </div> - <br> - <div class="row mb-3"> - <div class="col-sm-4"> - <b>UUID: </b>{{ link.link_id.link_uuid.uuid }}<br> - <b>Name: </b>{{ link.name }}<br> - </div> - <div class="col-sm-8"> - <table class="table table-striped table-hover"> - <thead> - <tr> - <th scope="col">Endpoint UUID</th> - <th scope="col">Name</th> - <th scope="col">Device</th> - <th scope="col">Endpoint Type</th> - </tr> - </thead> - <tbody> - {% for endpoint in link.link_endpoint_ids %} - <tr> - <td> - {{ endpoint.endpoint_uuid.uuid }} - </td> - <td> - {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, (endpoint.endpoint_uuid.uuid, ''))[0] }} - </td> - <td> - <a href="{{ url_for('device.detail', device_uuid=endpoint.device_id.device_uuid.uuid) }}"> - {{ device_names.get(endpoint.device_id.device_uuid.uuid, endpoint.device_id.device_uuid.uuid) }} - <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16"> - <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/> - <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/> - </svg> - </a> - </td> - <td> - {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, ('', '-'))[1] }} - </td> - </tr> - {% endfor %} - </tbody> - </table> +{% extends 'base.html' %} + +{% block content %} +<h1>Link {{ link.name }} ({{ link.link_id.link_uuid.uuid }})</h1> +<div class="row mb-3"> + <div class="col-sm-3"> + <button type="button" class="btn btn-success" onclick="window.location.href='{{ url_for('link.home') }}'"> + <i class="bi bi-box-arrow-in-left"></i> + Back to link list + </button> + </div> + <div class="col-sm-3"> + <!-- <button type="button" class="btn btn-danger"><i class="bi bi-x-square"></i>Delete link</button> --> + <button type="button" class="btn btn-danger" data-bs-toggle="modal" data-bs-target="#deleteModal"> + <i class="bi bi-x-square"></i> + Delete link + </button> + </div> +</div> + +<br> +<div class="row mb-3"> + <div class="col-sm-4"> + <b>UUID: </b>{{ link.link_id.link_uuid.uuid }}<br> + <b>Name: </b>{{ link.name }}<br> + </div> + <div class="col-sm-8"> + <table class="table table-striped table-hover"> + <thead> + <tr> + <th scope="col">Endpoint UUID</th> + <th scope="col">Name</th> + <th scope="col">Device</th> + <th scope="col">Endpoint Type</th> + </tr> + </thead> + <tbody> + {% for endpoint in link.link_endpoint_ids %} + <tr> + <td> + {{ endpoint.endpoint_uuid.uuid }} + </td> + <td> + {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, (endpoint.endpoint_uuid.uuid, ''))[0] }} + </td> + <td> + <a href="{{ url_for('device.detail', device_uuid=endpoint.device_id.device_uuid.uuid) }}"> + {{ device_names.get(endpoint.device_id.device_uuid.uuid, endpoint.device_id.device_uuid.uuid) }} + <svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" fill="currentColor" class="bi bi-eye" viewBox="0 0 16 16"> + <path d="M16 8s-3-5.5-8-5.5S0 8 0 8s3 5.5 8 5.5S16 8 16 8zM1.173 8a13.133 13.133 0 0 1 1.66-2.043C4.12 4.668 5.88 3.5 8 3.5c2.12 0 3.879 1.168 5.168 2.457A13.133 13.133 0 0 1 14.828 8c-.058.087-.122.183-.195.288-.335.48-.83 1.12-1.465 1.755C11.879 11.332 10.119 12.5 8 12.5c-2.12 0-3.879-1.168-5.168-2.457A13.134 13.134 0 0 1 1.172 8z"/> + <path d="M8 5.5a2.5 2.5 0 1 0 0 5 2.5 2.5 0 0 0 0-5zM4.5 8a3.5 3.5 0 1 1 7 0 3.5 3.5 0 0 1-7 0z"/> + </svg> + </a> + </td> + <td> + {{ endpoints_data.get(endpoint.endpoint_uuid.uuid, ('', '-'))[1] }} + </td> + </tr> + {% endfor %} + </tbody> + </table> + </div> +</div> + + +<!-- Modal --> +<div class="modal fade" id="deleteModal" data-bs-backdrop="static" data-bs-keyboard="false" tabindex="-1" + aria-labelledby="staticBackdropLabel" aria-hidden="true"> + <div class="modal-dialog"> + <div class="modal-content"> + <div class="modal-header"> + <h5 class="modal-title" id="staticBackdropLabel">Delete link?</h5> + <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> + </div> + <div class="modal-body"> + Are you sure you want to delete the link "{{ link.link_id.link_uuid.uuid }}"? + </div> + <div class="modal-footer"> + <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">No</button> + <a type="button" class="btn btn-danger" + href="{{ url_for('link.delete', link_uuid=link.link_id.link_uuid.uuid) }}"><i + class="bi bi-exclamation-diamond"></i>Yes</a> </div> </div> + </div> +</div> - {% endblock %} - \ No newline at end of file +{% endblock %} diff --git a/src/webui/service/templates/load_gen/home.html b/src/webui/service/templates/load_gen/home.html index 046459acf12068b2cb9d8529d4454c36609613d5..cec0a38dba2b4b31d4d07d391e4ce211f0c7ac76 100644 --- a/src/webui/service/templates/load_gen/home.html +++ b/src/webui/service/templates/load_gen/home.html @@ -53,6 +53,21 @@ </div> <br /> + <div class="row mb-3"> + {{ form.num_released.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.num_released.errors %} + {{ form.num_released(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.num_released.errors %}<span>{{ error }}</span>{% endfor %} + </div> + {% else %} + {{ form.num_released(class="form-control") }} + {% endif %} + </div> + </div> + <br /> + <div class="row mb-3"> <div class="col-sm-2 col-form-label">Service Types:</div> <div class="col-sm-10"> @@ -68,6 +83,36 @@ </div> <br /> + <div class="row mb-3"> + {{ form.device_regex.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.device_regex.errors %} + {{ form.device_regex(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.device_regex.errors %}<span>{{ error }}</span>{% endfor %} + </div> + {% else %} + {{ form.device_regex(class="form-control") }} + {% endif %} + </div> + </div> + <br /> + + <div class="row mb-3"> + {{ form.endpoint_regex.label(class="col-sm-2 col-form-label") }} + <div class="col-sm-10"> + {% if form.endpoint_regex.errors %} + {{ form.endpoint_regex(class="form-control is-invalid") }} + <div class="invalid-feedback"> + {% for error in form.endpoint_regex.errors %}<span>{{ error }}</span>{% endfor %} + </div> + {% else %} + {{ form.endpoint_regex(class="form-control") }} + {% endif %} + </div> + </div> + <br /> + <div class="row mb-3"> {{ form.offered_load.label(class="col-sm-2 col-form-label") }} <div class="col-sm-10">