diff options
author | Justin Choquette <jchoquette@iol.unh.edu> | 2022-06-07 16:07:54 -0400 |
---|---|---|
committer | Justin Choquette <jchoquette@iol.unh.edu> | 2022-09-29 13:34:30 -0400 |
commit | 4edb8881357e043fd7ea15efeb2d592c9fb55efc (patch) | |
tree | eac25aa9f64e1938348ccd3cbb0cadde4b995837 /src | |
parent | b7df4193fef9adeccf99685af7d7420274d66064 (diff) |
Laas Dashboard Front End Improvements
Change-Id: Ib9aa21747bd57faef94db7795cd89119ad4b0a9d
Signed-off-by: Justin Choquette <jchoquette@iol.unh.edu>
Diffstat (limited to 'src')
-rw-r--r-- | src/api/tests/test_models_unittest.py | 2 | ||||
-rw-r--r-- | src/api/views.py | 6 | ||||
-rw-r--r-- | src/booking/forms.py | 1 | ||||
-rw-r--r-- | src/resource_inventory/resource_manager.py | 2 | ||||
-rw-r--r-- | src/static/js/dashboard.js | 14 | ||||
-rw-r--r-- | src/static/package-lock.json | 91 | ||||
-rw-r--r-- | src/templates/base/resource/steps/pod_definition.html | 41 | ||||
-rw-r--r-- | src/templates/base/workflow/confirm.html | 8 | ||||
-rw-r--r-- | src/templates/base/workflow/viewport-base.html | 4 | ||||
-rw-r--r-- | src/workflow/forms.py | 16 | ||||
-rw-r--r-- | src/workflow/models.py | 14 | ||||
-rw-r--r-- | src/workflow/resource_bundle_workflow.py | 35 | ||||
-rw-r--r-- | src/workflow/workflow_manager.py | 2 |
13 files changed, 180 insertions, 56 deletions
diff --git a/src/api/tests/test_models_unittest.py b/src/api/tests/test_models_unittest.py index 2a6fa0b..2dee29b 100644 --- a/src/api/tests/test_models_unittest.py +++ b/src/api/tests/test_models_unittest.py @@ -116,7 +116,7 @@ class ValidBookingCreatesValidJob(TestCase): count = hostprofile.interfaceprofile.all().count() for i in range(count): network_struct.append([]) - while(nets): + while (nets): index = len(nets) % count network_struct[index].append(nets.pop()) diff --git a/src/api/views.py b/src/api/views.py index 1516374..ffa9b3f 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -430,7 +430,11 @@ def auth_and_log(request, endpoint): token = Token.objects.get(key=user_token) except Token.DoesNotExist: token = None - response = HttpResponse('Unauthorized', status=401) + # Added logic to detect malformed token + if len(str(user_token)) != 40: + response = HttpResponse('Malformed Token', status=401) + else: + response = HttpResponse('Unauthorized', status=401) x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR') if x_forwarded_for: diff --git a/src/booking/forms.py b/src/booking/forms.py index ff829b2..9c9b053 100644 --- a/src/booking/forms.py +++ b/src/booking/forms.py @@ -19,6 +19,7 @@ from booking.lib import get_user_items, get_user_field_opts class QuickBookingForm(forms.Form): + # Django Form class for Express Booking purpose = forms.CharField(max_length=1000) project = forms.CharField(max_length=400) hostname = forms.CharField(required=False, max_length=400) diff --git a/src/resource_inventory/resource_manager.py b/src/resource_inventory/resource_manager.py index 52af824..16c106e 100644 --- a/src/resource_inventory/resource_manager.py +++ b/src/resource_inventory/resource_manager.py @@ -176,7 +176,7 @@ class ResourceManager: vlan_manager = template.lab.vlan_manager for net_name, vlan_id in vlans.items(): net = Network.objects.get(name=net_name, bundle=template) - if(net.is_public): + if (net.is_public): vlan_manager.release_public_vlan(vlan_id) else: vlan_manager.release_vlans(vlan_id) diff --git a/src/static/js/dashboard.js b/src/static/js/dashboard.js index e3978e3..a63c71b 100644 --- a/src/static/js/dashboard.js +++ b/src/static/js/dashboard.js @@ -41,7 +41,7 @@ function update_side_buttons(meta) { const step = meta.active; const page_count = meta.steps.length; - const back_button = document.getElementById("gob"); + const back_button = document.getElementById("workflow-nav-back"); if (step == 0) { back_button.classList.add("disabled"); back_button.disabled = true; @@ -50,7 +50,7 @@ function update_side_buttons(meta) { back_button.disabled = false; } - const forward_btn = document.getElementById("gof"); + const forward_btn = document.getElementById("workflow-nav-next"); if (step == page_count - 1) { forward_btn.classList.add("disabled"); forward_btn.disabled = true; @@ -120,9 +120,18 @@ function update_description(title, desc) { } function update_message(message, stepstatus) { + let color_code; + if (stepstatus == 'valid') { + color_code = 'text-success'; + } else if (stepstatus == 'invalid') { + color_code = 'text-danger'; + } else { + color_code = 'none'; + } document.getElementById("view_message").innerText = message; document.getElementById("view_message").className = "step_message"; document.getElementById("view_message").classList.add("message_" + stepstatus); + document.getElementById("view_message").classList.add(color_code); } function submitStepForm(next_step = "current"){ @@ -795,6 +804,7 @@ class NetworkStep { tagged.type = "radio"; tagged.name = "tagged"; tagged.value = "True"; + tagged.checked = "True"; form.appendChild(tagged); form.appendChild(document.createTextNode(" Tagged")); form.appendChild(document.createElement("br")); diff --git a/src/static/package-lock.json b/src/static/package-lock.json index f8eabe4..89a26db 100644 --- a/src/static/package-lock.json +++ b/src/static/package-lock.json @@ -1,8 +1,97 @@ { "name": "laas", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 2, "requires": true, + "packages": { + "": { + "name": "laas", + "version": "1.0.0", + "license": "Apache-2.0", + "dependencies": { + "@fortawesome/fontawesome-free": "^5.12.0", + "bootstrap": "^4.4.1", + "datatables.net-bs4": "^1.10.20", + "datatables.net-responsive-bs4": "^2.2.3", + "jquery": "^3.4.1", + "mxgraph": "^4.0.6", + "plotly.js-dist": "^1.51.3", + "popper.js": "^1.16.0" + } + }, + "node_modules/@fortawesome/fontawesome-free": { + "version": "5.12.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.12.0.tgz", + "integrity": "sha512-vKDJUuE2GAdBERaQWmmtsciAMzjwNrROXA5KTGSZvayAsmuTGjam5z6QNqNPCwDfVljLWuov1nEC3mEQf/n6fQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/bootstrap": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-4.4.1.tgz", + "integrity": "sha512-tbx5cHubwE6e2ZG7nqM3g/FZ5PQEDMWmMGNrCUBVRPHXTJaH7CBDdsLeu3eCh3B1tzAxTnAbtmrzvWEvT2NNEA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/datatables.net": { + "version": "1.10.20", + "resolved": "https://registry.npmjs.org/datatables.net/-/datatables.net-1.10.20.tgz", + "integrity": "sha512-4E4S7tTU607N3h0fZPkGmAtr9mwy462u+VJ6gxYZ8MxcRIjZqHy3Dv1GNry7i3zQCktTdWbULVKBbkAJkuHEnQ==", + "dependencies": { + "jquery": "3.4.1" + } + }, + "node_modules/datatables.net-bs4": { + "version": "1.10.20", + "resolved": "https://registry.npmjs.org/datatables.net-bs4/-/datatables.net-bs4-1.10.20.tgz", + "integrity": "sha512-kQmMUMsHMOlAW96ztdoFqjSbLnlGZQ63iIM82kHbmldsfYdzuyhbb4hTx6YNBi481WCO3iPSvI6YodNec46ZAw==", + "dependencies": { + "datatables.net": "1.10.20", + "jquery": "3.4.1" + } + }, + "node_modules/datatables.net-responsive": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/datatables.net-responsive/-/datatables.net-responsive-2.2.3.tgz", + "integrity": "sha512-8D6VtZcyuH3FG0Hn5A4LPZQEOX3+HrRFM7HjpmsQc/nQDBbdeBLkJX4Sh/o1nzFTSneuT1Wh/lYZHVPpjcN+Sw==", + "dependencies": { + "datatables.net": "1.10.20", + "jquery": "3.4.1" + } + }, + "node_modules/datatables.net-responsive-bs4": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/datatables.net-responsive-bs4/-/datatables.net-responsive-bs4-2.2.3.tgz", + "integrity": "sha512-SQaWI0uLuPcaiBBin9zX+MuQfTSIkK1bYxbXqUV6NLkHCVa6PMQK7Rvftj0ywG4R7uOtjbzY8nSVqxEKvQI0Vg==", + "dependencies": { + "datatables.net-bs4": "1.10.20", + "datatables.net-responsive": "2.2.3", + "jquery": "3.4.1" + } + }, + "node_modules/jquery": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.4.1.tgz", + "integrity": "sha512-36+AdBzCL+y6qjw5Tx7HgzeGCzC81MDDgaUP8ld2zhx58HdqXGoBd+tHdrBMiyjGQs0Hxs/MLZTu/eHNJJuWPw==" + }, + "node_modules/mxgraph": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/mxgraph/-/mxgraph-4.0.6.tgz", + "integrity": "sha512-5XZXeAkA4k6n4BS05Fxd2cNhMw+3dnlRqAaLtsuXdT0g8BvvEa1VT4jjuGtUW4QTt38Q+I2Dr/3EWiAaGRfAXw==" + }, + "node_modules/plotly.js-dist": { + "version": "1.51.3", + "resolved": "https://registry.npmjs.org/plotly.js-dist/-/plotly.js-dist-1.51.3.tgz", + "integrity": "sha512-Bxz0XBg963gpnbt7FVPEhYvT33JsaKa0hEozXBnQZkiKtsiM2M1lZN6tkEHmq6o1N2K6qJXFtdzCXbZ/hLGV0Q==" + }, + "node_modules/popper.js": { + "version": "1.16.0", + "resolved": "https://registry.npmjs.org/popper.js/-/popper.js-1.16.0.tgz", + "integrity": "sha512-+G+EkOPoE5S/zChTpmBSSDYmhXJ5PsW8eMhH8cP/CQHMFPBG/kC9Y5IIw6qNYgdJ+/COf0ddY2li28iHaZRSjw==" + } + }, "dependencies": { "@fortawesome/fontawesome-free": { "version": "5.12.0", diff --git a/src/templates/base/resource/steps/pod_definition.html b/src/templates/base/resource/steps/pod_definition.html index 83c4fcb..233d995 100644 --- a/src/templates/base/resource/steps/pod_definition.html +++ b/src/templates/base/resource/steps/pod_definition.html @@ -39,27 +39,32 @@ </form> <script> //gather context data - let debug = false; - {% if debug %} - debug = true; - {% endif %} + try { + let debug = false; + {% if debug %} + debug = true; + {% endif %} - const False = false; - const True = true; + const False = false; + const True = true; - let resources = {{resources|safe}}; - let networks = {{networks|safe}}; + let resources = {{resources|safe}}; + let networks = {{networks|safe}}; + + network_step = new NetworkStep( + debug, + resources, + networks, + document.getElementById('graphContainer'), + document.getElementById('outlineContainer'), + document.getElementById('toolbarContainer'), + document.getElementById('sidebarContainer') + ); + form_submission_callbacks.push(() => network_step.prepareForm()); + } catch (e) { + console.log(e) + } - network_step = new NetworkStep( - debug, - resources, - networks, - document.getElementById('graphContainer'), - document.getElementById('outlineContainer'), - document.getElementById('toolbarContainer'), - document.getElementById('sidebarContainer') - ); - form_submission_callbacks.push(() => network_step.prepareForm()); </script> {% endblock content %} {% block onleave %} diff --git a/src/templates/base/workflow/confirm.html b/src/templates/base/workflow/confirm.html index 2f99a41..bc8e4e3 100644 --- a/src/templates/base/workflow/confirm.html +++ b/src/templates/base/workflow/confirm.html @@ -43,16 +43,10 @@ { select.value = "True"; submitStepForm(); + pop_workflow(); } function formcancel() { - select.value = "False"; - submitStepForm(); - } - - var confirmed = {{confirm_succeeded|default:"false"}}; - if( confirmed ) - { pop_workflow(); } </script> diff --git a/src/templates/base/workflow/viewport-base.html b/src/templates/base/workflow/viewport-base.html index d9648c2..88229ca 100644 --- a/src/templates/base/workflow/viewport-base.html +++ b/src/templates/base/workflow/viewport-base.html @@ -10,7 +10,7 @@ <div class="col"> <nav> <ul class="pagination d-flex flex-row" id="topPagination"> - <li class="page-item flex-shrink-1 page-control"> + <li class="page-item flex-shrink-1 page-control" id="workflow-nav-back"> <a class="page-link" href="#" id="gob" onclick="submit_and_go('prev')"> <i class="fas fa-backward"></i> Back </a> @@ -20,7 +20,7 @@ <i class="far"></i> </a> </li> - <li class="page-item flex-shrink-1 page-control"> + <li class="page-item flex-shrink-1 page-control" id="workflow-nav-next"> <a class="page-link text-right" href="#" id="gof" onclick="submit_and_go('next')"> Next <i class="fas fa-forward"></i> </a> diff --git a/src/workflow/forms.py b/src/workflow/forms.py index 9b56f93..62abad6 100644 --- a/src/workflow/forms.py +++ b/src/workflow/forms.py @@ -222,7 +222,7 @@ class ResourceSelectorForm(SearchableSelectAbstractForm): class BookingMetaForm(forms.Form): - + # Django Form class for Book a Pod length = forms.IntegerField( widget=NumberInput( attrs={ @@ -380,7 +380,7 @@ class PodDefinitionForm(forms.Form): class ResourceMetaForm(forms.Form): bundle_name = forms.CharField(label="POD Name") - bundle_description = forms.CharField(label="POD Description", widget=forms.Textarea) + bundle_description = forms.CharField(label="POD Description", widget=forms.Textarea, max_length=1000) class GenericHostMetaForm(forms.Form): @@ -400,8 +400,12 @@ class NetworkConfigurationForm(forms.Form): class HostSoftwareDefinitionForm(forms.Form): - - host_name = forms.CharField(max_length=200, disabled=False, required=True) + # Django Form class for Design a Pod + host_name = forms.CharField( + max_length=200, + disabled=False, + required=True + ) headnode = forms.BooleanField(required=False, widget=forms.HiddenInput) def __init__(self, *args, **kwargs): @@ -441,8 +445,8 @@ class ConfirmationForm(forms.Form): confirm = forms.ChoiceField( choices=( - (True, "Confirm"), - (False, "Cancel") + (False, "Cancel"), + (True, "Confirm") ) ) diff --git a/src/workflow/models.py b/src/workflow/models.py index 91a216c..e065202 100644 --- a/src/workflow/models.py +++ b/src/workflow/models.py @@ -326,11 +326,15 @@ class Confirmation_Step(WorkflowStep): def get_context(self): context = super(Confirmation_Step, self).get_context() context['form'] = ConfirmationForm() - context['confirmation_info'] = yaml.dump( - self.repo_get(self.repo.CONFIRMATION), - default_flow_style=False - ).strip() - + # Summary of submitted form data shown on the 'confirm' step of the workflow + confirm_details = "\nPod:\n Name: '{name}'\n Description: '{desc}'\nLab: '{lab}'".format( + name=self.repo_get(self.repo.CONFIRMATION)['resource']['name'], + desc=self.repo_get(self.repo.CONFIRMATION)['resource']['description'], + lab=self.repo_get(self.repo.CONFIRMATION)['template']['lab']) + confirm_details += "\nResources:" + for i, device in enumerate(self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS)['resources']): + confirm_details += "\n " + str(device) + ": " + str(self.repo_get(self.repo.CONFIRMATION)['template']['resources'][i]['profile']) + context['confirmation_info'] = confirm_details if self.valid == WorkflowStepStatus.VALID: context["confirm_succeeded"] = "true" diff --git a/src/workflow/resource_bundle_workflow.py b/src/workflow/resource_bundle_workflow.py index a461e9a..4e288b5 100644 --- a/src/workflow/resource_bundle_workflow.py +++ b/src/workflow/resource_bundle_workflow.py @@ -14,6 +14,7 @@ from django.core.exceptions import ValidationError from typing import List +import re import json from xml.dom import minidom import traceback @@ -172,7 +173,8 @@ class Define_Hardware(WorkflowStep): except Exception as e: print("Caught exception: " + str(e)) traceback.print_exc() - self.set_invalid(str(e)) + self.form = None + self.set_invalid("Please select a lab.") class Define_Software(WorkflowStep): @@ -208,12 +210,15 @@ class Define_Software(WorkflowStep): hosts_initial = [] configs = self.repo_get(self.repo.RESOURCE_TEMPLATE_MODELS, {}).get("resources") if configs: - for config in configs: + for i in range(len(configs)): + default_name = 'laas-node' + if i > 0: + default_name = default_name + "-" + str(i + 1) hosts_initial.append({ - 'host_id': config.id, - 'host_name': config.name, - 'headnode': config.is_head_node, - 'image': config.image + 'host_id': configs[i].id, + 'host_name': default_name, + 'headnode': False, + 'image': configs[i].image }) else: for host in hostlist: @@ -248,9 +253,6 @@ class Define_Software(WorkflowStep): def post(self, post_data, user): hosts = self.get_host_list() - - # TODO: fix headnode in form, currently doesn't return a selected one - # models['headnode_index'] = post_data.get("headnode", 1) formset = self.create_hostformset(hosts, data=post_data) has_headnode = False if formset.is_valid(): @@ -264,6 +266,17 @@ class Define_Software(WorkflowStep): host.is_head_node = headnode host.name = hostname host.image = image + # RFC921: They must start with a letter, end with a letter or digit and have only letters or digits or hyphen as interior characters + if bool(re.match("^[A-Za-z0-9-]*$", hostname)) is False: + self.set_invalid("Device names must only contain alphanumeric characters and dashes.") + return + if not hostname[0].isalpha() or not hostname[-1].isalnum(): + self.set_invalid("Device names must start with a letter and end with a letter or digit.") + return + for j in range(i): + if j != i and hostname == hosts[j].name: + self.set_invalid("Devices must have unique names. Please try again.") + return host.save() if not has_headnode and len(hosts) > 0: @@ -272,7 +285,7 @@ class Define_Software(WorkflowStep): self.set_valid("Completed") else: - self.set_invalid("Please complete all fields") + self.set_invalid("Please complete all fields.") class Define_Nets(WorkflowStep): @@ -598,4 +611,4 @@ class Resource_Meta_Info(WorkflowStep): self.repo_put(self.repo.CONFIRMATION, confirm) self.set_valid("Step Completed") else: - self.set_invalid("Please correct the fields highlighted in red to continue") + self.set_invalid("Please complete all fields.") diff --git a/src/workflow/workflow_manager.py b/src/workflow/workflow_manager.py index a48efe5..40be9d6 100644 --- a/src/workflow/workflow_manager.py +++ b/src/workflow/workflow_manager.py @@ -48,7 +48,7 @@ class SessionManager(): def add_workflow(self, workflow_type=None, **kwargs): repo = Repository() - if(len(self.workflows) >= 1): + if (len(self.workflows) >= 1): defaults = self.workflows[-1].repository.get_child_defaults() repo.set_defaults(defaults) repo.el[repo.HAS_RESULT] = False |