aboutsummaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorJustin Choquette <jchoquette@iol.unh.edu>2022-06-07 16:07:54 -0400
committerJustin Choquette <jchoquette@iol.unh.edu>2022-09-29 13:34:30 -0400
commit4edb8881357e043fd7ea15efeb2d592c9fb55efc (patch)
treeeac25aa9f64e1938348ccd3cbb0cadde4b995837 /src
parentb7df4193fef9adeccf99685af7d7420274d66064 (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.py2
-rw-r--r--src/api/views.py6
-rw-r--r--src/booking/forms.py1
-rw-r--r--src/resource_inventory/resource_manager.py2
-rw-r--r--src/static/js/dashboard.js14
-rw-r--r--src/static/package-lock.json91
-rw-r--r--src/templates/base/resource/steps/pod_definition.html41
-rw-r--r--src/templates/base/workflow/confirm.html8
-rw-r--r--src/templates/base/workflow/viewport-base.html4
-rw-r--r--src/workflow/forms.py16
-rw-r--r--src/workflow/models.py14
-rw-r--r--src/workflow/resource_bundle_workflow.py35
-rw-r--r--src/workflow/workflow_manager.py2
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