From 7aac5a5b704793263bdc003f7d20c087fb426835 Mon Sep 17 00:00:00 2001 From: Sawyer Bergeron Date: Wed, 22 May 2019 10:13:03 -0400 Subject: Make steps possible to hide/show Change-Id: Ice5036ea9801655032cb080537fbd471fb3fda3e Signed-off-by: Sawyer Bergeron --- .../src/templates/workflow/viewport-base.html | 94 +++++++++++++++------- dashboard/src/workflow/booking_workflow.py | 22 ++--- dashboard/src/workflow/models.py | 49 +++++++++-- dashboard/src/workflow/opnfv_workflow.py | 26 +++--- dashboard/src/workflow/resource_bundle_workflow.py | 18 ++--- dashboard/src/workflow/snapshot_workflow.py | 10 +-- dashboard/src/workflow/sw_bundle_workflow.py | 12 +-- dashboard/src/workflow/views.py | 7 +- dashboard/src/workflow/workflow_factory.py | 46 ++--------- dashboard/src/workflow/workflow_manager.py | 40 +++++++-- 10 files changed, 195 insertions(+), 129 deletions(-) (limited to 'dashboard') diff --git a/dashboard/src/templates/workflow/viewport-base.html b/dashboard/src/templates/workflow/viewport-base.html index 1329595..beea7d2 100644 --- a/dashboard/src/templates/workflow/viewport-base.html +++ b/dashboard/src/templates/workflow/viewport-base.html @@ -52,64 +52,104 @@ } #breadcrumbs { - padding: 4px; + margin-bottom: 0; } + + .btn_wrapper { + margin: 0; + } + .step{ - background: #DEEED3; display: inline; - padding: 5px; + padding: 7px; margin: 1px; + font-size: 14pt; + cursor: default; + } + .step:active { + -webkit-box-shadow: inherit; + box-shadow: inherit; + } + .step_active:active { + -webkit-box-shadow: inherit; + box-shadow: inherit; } .step_active{ - background: #5EC392; display: inline; - padding: 5px; + padding: 7px; margin: 1px; - font-weight: bold; + cursor: default; + font-size: 14pt; + padding-bottom: 4px !important; + border-bottom: 4px solid #41ba78 !important; } - .step_untouched + .step_hidden { - background: #DDDDDD; + background: #EFEFEF; + color: #999999; } - .step_invalid + .step_invalid::after { - background: #CC3300; + content: " \2612"; + color: #CC3300; } - .step_valid + .step_valid::after { - background: #0FD57D; + content: " \2611"; + color: #41ba78; } - .iframe_div { + .step_untouched::after + { + content: " \2610"; + } + .iframe_div { width: calc(100% - 450px); margin-left: 70px; height: calc(100vh - 155px); position: absolute; border: none; } + .iframe_elem { width: 100%; height: calc(100vh - 155px); border: none; } + + #breadcrumbs { + background-color: inherit; + } + + #breadcrumbs.breadcrumb > li { + border: 1px solid #cccccc; + border-left: none; + } + #breadcrumbs.breadcrumb > li:first-child { + border-left: 1px solid #cccccc; + } + #breadcrumbs.breadcrumb > li + li:before { + content: ""; + width: 0; + margin: 0; + padding: 0; + } - - + +
- +
{% csrf_token %} @@ -153,14 +193,7 @@ return; } } - if( to >= page_count ) - { - to = page_count-1; - } - else if( to < 0 ) - { - to = 0; - } + var problem = function() { alert("There was a problem"); } @@ -271,7 +304,7 @@ } function create_step(step_json, active){ - var step_dom = document.createElement("DIV"); + var step_dom = document.createElement("li"); if(active){ step_dom.className = "step_active"; @@ -301,6 +334,10 @@ stat = "valid"; msg = step_json['message']; } + if( step_json['enabled'] == false ) + { + step_dom.classList.add("step_hidden"); + } if(active) { update_message(msg, stat); @@ -308,7 +345,6 @@ step_dom.classList.add("btn"); var step_number = step_json['index']; - step_dom.onclick = function(){ go(step_number); } return step_dom; } diff --git a/dashboard/src/workflow/booking_workflow.py b/dashboard/src/workflow/booking_workflow.py index 8be7720..eb87728 100644 --- a/dashboard/src/workflow/booking_workflow.py +++ b/dashboard/src/workflow/booking_workflow.py @@ -59,11 +59,11 @@ class Resource_Select(WorkflowStep): data = form.cleaned_data['generic_resource_bundle'] data = data[2:-2] if not data: - self.metastep.set_invalid("Please select a valid bundle") + self.set_invalid("Please select a valid bundle") return render(request, self.template, context) selected_bundle = json.loads(data) if len(selected_bundle) < 1: - self.metastep.set_invalid("Please select a valid bundle") + self.set_invalid("Please select a valid bundle") return render(request, self.template, context) selected_id = selected_bundle[0]['id'] gresource_bundle = None @@ -86,11 +86,11 @@ class Resource_Select(WorkflowStep): confirm[self.confirm_key]["resource name"] = gresource_bundle.name self.repo_put(self.repo.CONFIRMATION, confirm) messages.add_message(request, messages.SUCCESS, 'Form Validated Successfully', fail_silently=True) - self.metastep.set_valid("Step Completed") + self.set_valid("Step Completed") return render(request, self.template, context) else: messages.add_message(request, messages.ERROR, "Form Didn't Validate", fail_silently=True) - self.metastep.set_invalid("Please complete the fields highlighted in red to continue") + self.set_invalid("Please complete the fields highlighted in red to continue") return render(request, self.template, context) @@ -135,11 +135,11 @@ class SWConfig_Select(WorkflowStep): bundle_json = form.cleaned_data['software_bundle'] bundle_json = bundle_json[2:-2] # Stupid django string bug if not bundle_json: - self.metastep.set_invalid("Please select a valid config") + self.set_invalid("Please select a valid config") return self.render(request) bundle_json = json.loads(bundle_json) if len(bundle_json) < 1: - self.metastep.set_invalid("Please select a valid config") + self.set_invalid("Please select a valid config") return self.render(request) bundle = None id = int(bundle_json[0]['id']) @@ -148,7 +148,7 @@ class SWConfig_Select(WorkflowStep): grb = self.repo_get(self.repo.SELECTED_GRESOURCE_BUNDLE) if grb and bundle.bundle != grb: - self.metastep.set_invalid("Incompatible config selected for resource bundle") + self.set_invalid("Incompatible config selected for resource bundle") return self.render(request) if not grb: self.repo_set(self.repo.SELECTED_GRESOURCE_BUNDLE, bundle.bundle) @@ -163,10 +163,10 @@ class SWConfig_Select(WorkflowStep): confirm['booking'] = {} confirm['booking']["configuration name"] = bundle.name self.repo_put(self.repo.CONFIRMATION, confirm) - self.metastep.set_valid("Step Completed") + self.set_valid("Step Completed") messages.add_message(request, messages.SUCCESS, 'Form Validated Successfully', fail_silently=True) else: - self.metastep.set_invalid("Please select or create a valid config") + self.set_invalid("Please select or create a valid config") messages.add_message(request, messages.ERROR, "Form Didn't Validate", fail_silently=True) return self.render(request) @@ -270,9 +270,9 @@ class Booking_Meta(WorkflowStep): self.repo_put(self.repo.BOOKING_MODELS, models) self.repo_put(self.repo.CONFIRMATION, confirm) messages.add_message(request, messages.SUCCESS, 'Form Validated', fail_silently=True) - self.metastep.set_valid("Step Completed") + self.set_valid("Step Completed") else: messages.add_message(request, messages.ERROR, "Form didn't validate", fail_silently=True) - self.metastep.set_invalid("Please complete the fields highlighted in red to continue") + self.set_invalid("Please complete the fields highlighted in red to continue") context['form'] = form # TODO: store this form return render(request, self.template, context) diff --git a/dashboard/src/workflow/models.py b/dashboard/src/workflow/models.py index bf5751d..25d7e84 100644 --- a/dashboard/src/workflow/models.py +++ b/dashboard/src/workflow/models.py @@ -158,12 +158,52 @@ class BookingAuthManager(): return False +class WorkflowStepStatus(object): + UNTOUCHED = 0 + INVALID = 100 + VALID = 200 + + class WorkflowStep(object): template = 'bad_request.html' title = "Generic Step" description = "You were led here by mistake" short_title = "error" metastep = None + # phasing out metastep: + + valid = WorkflowStepStatus.UNTOUCHED + message = "" + + enabled = True + + def cleanup(self): + raise Exception("WorkflowStep subclass of type " + str(type(self)) + " has no concrete implemented cleanup() method") + + def enable(self): + if not self.enabled: + self.enabled = True + + def disable(self): + if self.enabled: + self.cleanup() + self.enabled = False + + def set_invalid(self, message, code=WorkflowStepStatus.INVALID): + self.valid = code + self.message = message + + def set_valid(self, message, code=WorkflowStepStatus.VALID): + self.valid = code + self.message = message + + def to_json(self): + return { + 'title': self.short_title, + 'enabled': self.enabled, + 'valid': self.valid, + 'message': self.message, + } def __init__(self, id, repo=None): self.repo = repo @@ -205,6 +245,8 @@ class Confirmation_Step(WorkflowStep): title = "Confirm Changes" description = "Does this all look right?" + short_title = "confirm" + def get_context(self): context = super(Confirmation_Step, self).get_context() context['form'] = ConfirmationForm() @@ -245,12 +287,6 @@ class Confirmation_Step(WorkflowStep): pass -class Workflow(): - - steps = [] - active_index = 0 - - class Repository(): EDIT = "editing" @@ -271,6 +307,7 @@ class Repository(): CONFIG_MODELS = "configuration bundle models" OPNFV_MODELS = "opnfv configuration models" SESSION_USER = "session owner user account" + SESSION_MANAGER = "session manager for current session" VALIDATED_MODEL_GRB = "valid grb config model instance in db" VALIDATED_MODEL_CONFIG = "valid config model instance in db" VALIDATED_MODEL_BOOKING = "valid booking model instance in db" diff --git a/dashboard/src/workflow/opnfv_workflow.py b/dashboard/src/workflow/opnfv_workflow.py index 26e1d7c..490d2f0 100644 --- a/dashboard/src/workflow/opnfv_workflow.py +++ b/dashboard/src/workflow/opnfv_workflow.py @@ -41,11 +41,11 @@ class OPNFV_Resource_Select(WorkflowStep): bundle_json = form.cleaned_data['software_bundle'] bundle_json = bundle_json[2:-2] # Stupid django string bug if not bundle_json: - self.metastep.set_invalid("Please select a valid config") + self.set_invalid("Please select a valid config") return self.render(request) bundle_json = json.loads(bundle_json) if len(bundle_json) < 1: - self.metastep.set_invalid("Please select a valid config") + self.set_invalid("Please select a valid config") return self.render(request) bundle = None id = int(bundle_json[0]['id']) @@ -53,11 +53,11 @@ class OPNFV_Resource_Select(WorkflowStep): models['configbundle'] = bundle self.repo_put(self.repo.OPNFV_MODELS, models) - self.metastep.set_valid("Step Completed") + self.set_valid("Step Completed") messages.add_message(request, messages.SUCCESS, 'Form Validated Successfully', fail_silently=True) self.update_confirmation() else: - self.metastep.set_invalid("Please select or create a valid config") + self.set_invalid("Please select or create a valid config") messages.add_message(request, messages.ERROR, "Form Didn't Validate", fail_silently=True) return self.render(request) @@ -111,9 +111,9 @@ class Pick_Installer(WorkflowStep): models['scenario_chosen'] = scenario self.repo_put(self.repo.OPNFV_MODELS, models) self.update_confirmation() - self.metastep.set_valid("Step Completed") + self.set_valid("Step Completed") else: - self.metastep.set_invalid("Please select an Installer and Scenario") + self.set_invalid("Please select an Installer and Scenario") return self.render(request) @@ -190,11 +190,11 @@ class Assign_Network_Roles(WorkflowStep): "network": form.cleaned_data['network'] }) models['network_roles'] = results - self.metastep.set_valid("Completed") + self.set_valid("Completed") self.repo_put(self.repo.OPNFV_MODELS, models) self.update_confirmation() else: - self.metastep.set_invalid("Please complete all fields") + self.set_invalid("Please complete all fields") return self.render(request) @@ -276,11 +276,11 @@ class Assign_Host_Roles(WorkflowStep): # taken verbatim from Define_Software in self.update_confirmation() if not has_jumphost: - self.metastep.set_invalid('Must have at least one "Jumphost" per POD') + self.set_invalid('Must have at least one "Jumphost" per POD') else: - self.metastep.set_valid("Completed") + self.set_valid("Completed") else: - self.metastep.set_invalid("Please complete all fields") + self.set_invalid("Please complete all fields") return self.render(request) @@ -319,9 +319,9 @@ class MetaInfo(WorkflowStep): models['meta'] = info self.repo_put(self.repo.OPNFV_MODELS, models) self.update_confirmation() - self.metastep.set_valid("Complete") + self.set_valid("Complete") else: - self.metastep.set_invalid("Please correct the errors shown below") + self.set_invalid("Please correct the errors shown below") self.repo_put(self.repo.OPNFV_MODELS, models) return self.render(request) diff --git a/dashboard/src/workflow/resource_bundle_workflow.py b/dashboard/src/workflow/resource_bundle_workflow.py index 536187f..ced355f 100644 --- a/dashboard/src/workflow/resource_bundle_workflow.py +++ b/dashboard/src/workflow/resource_bundle_workflow.py @@ -134,16 +134,16 @@ class Define_Hardware(WorkflowStep): self.form = HardwareDefinitionForm(request.POST) if self.form.is_valid(): if len(json.loads(self.form.cleaned_data['filter_field'])['labs']) != 1: - self.metastep.set_invalid("Please select one lab") + self.set_invalid("Please select one lab") else: self.update_models(self.form.cleaned_data) self.update_confirmation() - self.metastep.set_valid("Step Completed") + self.set_valid("Step Completed") else: - self.metastep.set_invalid("Please complete the fields highlighted in red to continue") + self.set_invalid("Please complete the fields highlighted in red to continue") pass except Exception as e: - self.metastep.set_invalid(str(e)) + self.set_invalid(str(e)) self.context = self.get_context() return render(request, self.template, self.context) @@ -239,11 +239,11 @@ class Define_Nets(WorkflowStep): xmlData = request.POST.get("xml") self.updateModels(xmlData) # update model with xml - self.metastep.set_valid("Networks applied successfully") + self.set_valid("Networks applied successfully") except ResourceAvailabilityException: - self.metastep.set_invalid("Public network not availble") + self.set_invalid("Public network not availble") except Exception as e: - self.metastep.set_invalid("An error occurred when applying networks: " + str(e)) + self.set_invalid("An error occurred when applying networks: " + str(e)) return self.render(request) def updateModels(self, xmlData): @@ -425,10 +425,10 @@ class Resource_Meta_Info(WorkflowStep): tmp = tmp[:60] + "..." confirm_info["description"] = tmp self.repo_put(self.repo.CONFIRMATION, confirm) - self.metastep.set_valid("Step Completed") + self.set_valid("Step Completed") else: - self.metastep.set_invalid("Please correct the fields highlighted in red to continue") + self.set_invalid("Please correct the fields highlighted in red to continue") pass return self.render(request) diff --git a/dashboard/src/workflow/snapshot_workflow.py b/dashboard/src/workflow/snapshot_workflow.py index 34ac3a5..5414784 100644 --- a/dashboard/src/workflow/snapshot_workflow.py +++ b/dashboard/src/workflow/snapshot_workflow.py @@ -52,11 +52,11 @@ class Select_Host_Step(WorkflowStep): def post_render(self, request): host_data = request.POST.get("host") if not host_data: - self.metastep.set_invalid("Please select a host") + self.set_invalid("Please select a host") return self.render(request) host = json.loads(host_data) if 'name' not in host or 'booking' not in host: - self.metastep.set_invalid("Invalid host selected") + self.set_invalid("Invalid host selected") return self.render(request) name = host['name'] booking_id = host['booking'] @@ -75,7 +75,7 @@ class Select_Host_Step(WorkflowStep): snap_confirm['host'] = name confirm['snapshot'] = snap_confirm self.repo_put(self.repo.CONFIRMATION, confirm) - self.metastep.set_valid("Success") + self.set_valid("Success") return self.render(request) @@ -112,8 +112,8 @@ class Image_Meta_Step(WorkflowStep): confirm['snapshot'] = snap_confirm self.repo_put(self.repo.CONFIRMATION, confirm) - self.metastep.set_valid("Success") + self.set_valid("Success") else: - self.metastep.set_invalid("Please Fill out the Form") + self.set_invalid("Please Fill out the Form") return self.render(request) diff --git a/dashboard/src/workflow/sw_bundle_workflow.py b/dashboard/src/workflow/sw_bundle_workflow.py index a6a7464..329b716 100644 --- a/dashboard/src/workflow/sw_bundle_workflow.py +++ b/dashboard/src/workflow/sw_bundle_workflow.py @@ -113,7 +113,7 @@ class Define_Software(WorkflowStep): context['headnode'] = self.repo_get(self.repo.CONFIG_MODELS, {}).get("headnode_index", 1) else: context["error"] = "Please select a resource first" - self.metastep.set_invalid("Step requires information that is not yet provided by previous step") + self.set_invalid("Step requires information that is not yet provided by previous step") return context @@ -152,7 +152,7 @@ class Define_Software(WorkflowStep): }) if not has_headnode: - self.metastep.set_invalid('Must have one "Headnode" per POD') + self.set_invalid('Must have one "Headnode" per POD') return self.render(request) self.repo_put(self.repo.CONFIG_MODELS, models) @@ -160,9 +160,9 @@ class Define_Software(WorkflowStep): confirm['configuration'] = {} confirm['configuration']['hosts'] = confirm_hosts self.repo_put(self.repo.CONFIRMATION, confirm) - self.metastep.set_valid("Completed") + self.set_valid("Completed") else: - self.metastep.set_invalid("Please complete all fields") + self.set_invalid("Please complete all fields") return self.render(request) @@ -201,9 +201,9 @@ class Config_Software(WorkflowStep): confirm['configuration']['name'] = form.cleaned_data['name'] confirm['configuration']['description'] = form.cleaned_data['description'] - self.metastep.set_valid("Complete") + self.set_valid("Complete") else: - self.metastep.set_invalid("Please correct the errors shown below") + self.set_invalid("Please correct the errors shown below") self.repo_put(self.repo.CONFIG_MODELS, models) self.repo_put(self.repo.CONFIRMATION, confirm) diff --git a/dashboard/src/workflow/views.py b/dashboard/src/workflow/views.py index 6d59b1c..f2e37ef 100644 --- a/dashboard/src/workflow/views.py +++ b/dashboard/src/workflow/views.py @@ -54,7 +54,12 @@ def step_view(request): # no manager found, redirect to "lost" page return no_workflow(request) if request.GET.get('step') is not None: - manager.goto(int(request.GET.get('step'))) + if request.GET.get('step') == 'next': + manager.go_next() + elif request.GET.get('step') == 'prev': + manager.go_prev() + else: + raise Exception("requested action for new step had malformed contents: " + request.GET.get('step')) return manager.render(request) diff --git a/dashboard/src/workflow/workflow_factory.py b/dashboard/src/workflow/workflow_factory.py index db2bba1..08cf296 100644 --- a/dashboard/src/workflow/workflow_factory.py +++ b/dashboard/src/workflow/workflow_factory.py @@ -21,27 +21,6 @@ import logging logger = logging.getLogger(__name__) -class BookingMetaWorkflow(object): - workflow_type = 0 - color = "#0099ff" - is_child = False - - -class ResourceMetaWorkflow(object): - workflow_type = 1 - color = "#ff6600" - - -class ConfigMetaWorkflow(object): - workflow_type = 2 - color = "#00ffcc" - - -class OPNFVMetaWorkflow(object): - workflow_type = 3 - color = "000000" - - class MetaStep(object): UNTOUCHED = 0 @@ -60,6 +39,7 @@ class MetaStep(object): self.short_title = "error" self.skip_step = 0 self.valid = 0 + self.hidden = False self.message = "" self.id = uuid.uuid4() @@ -85,10 +65,9 @@ class MetaStep(object): class Workflow(object): - def __init__(self, steps, metasteps, repository): + def __init__(self, steps, repository): self.repository = repository self.steps = steps - self.metasteps = metasteps self.active_index = 0 @@ -134,18 +113,13 @@ class WorkflowFactory(): ] steps = self.make_steps(workflow_types[workflow_type], repository=repo) - meta_steps = self.metaize(steps=steps, wf_type=workflow_type) - return steps, meta_steps + return steps def create_workflow(self, workflow_type=None, repo=None): - steps, meta_steps = self.conjure(workflow_type, repo) + steps = self.conjure(workflow_type, repo) c_step = self.make_step(Confirmation_Step, repo) - metaconfirm = MetaStep() - metaconfirm.short_title = "confirm" - metaconfirm.index = len(steps) steps.append(c_step) - meta_steps.append(metaconfirm) - return Workflow(steps, meta_steps, repo) + return Workflow(steps, repo) def make_steps(self, step_types, repository): steps = [] @@ -157,13 +131,3 @@ class WorkflowFactory(): def make_step(self, step_type, repository): iden = step_type.description + step_type.title + step_type.template return step_type(iden, repository) - - def metaize(self, steps, wf_type): - meta_dict = [] - for step in steps: - meta_step = MetaStep() - meta_step.short_title = step.short_title - meta_dict.append(meta_step) - step.metastep = meta_step - - return meta_dict diff --git a/dashboard/src/workflow/workflow_manager.py b/dashboard/src/workflow/workflow_manager.py index 89a9d96..525aa6f 100644 --- a/dashboard/src/workflow/workflow_manager.py +++ b/dashboard/src/workflow/workflow_manager.py @@ -35,6 +35,16 @@ class SessionManager(): self.factory = WorkflowFactory() + def set_step_statuses(self, superclass_type, desired_enabled=True): + workflow = self.active_workflow() + steps = workflow.steps + for step in steps: + if isinstance(step, superclass_type): + if desired_enabled: + step.enable() + else: + step.disable() + def add_workflow(self, workflow_type=None, target_id=None, **kwargs): if target_id is not None: self.prefill_repo(target_id, workflow_type) @@ -45,6 +55,7 @@ class SessionManager(): repo.set_defaults(defaults) repo.el[repo.HAS_RESULT] = False repo.el[repo.SESSION_USER] = self.owner + repo.el[repo.SESSION_MANAGER] = self self.workflows.append( self.factory.create_workflow( workflow_type=workflow_type, @@ -65,11 +76,11 @@ class SessionManager(): def status(self, request): try: - meta_steps = [] - for step in self.active_workflow().metasteps: - meta_steps.append(step.to_json()) + meta_json = [] + for step in self.active_workflow().steps: + meta_json.append(step.to_json()) responsejson = {} - responsejson["steps"] = meta_steps + responsejson["steps"] = meta_json responsejson["active"] = self.active_workflow().repository.el['active_step'] responsejson["workflow_count"] = len(self.workflows) return JsonResponse(responsejson, safe=False) @@ -86,10 +97,23 @@ class SessionManager(): def post_render(self, request): return self.active_workflow().steps[self.active_workflow().active_index].post_render(request) - def goto(self, num, **kwargs): - self.active_workflow().repository.el['active_step'] = int(num) - self.active_workflow().active_index = int(num) - # TODO: change to include some checking + def go_next(self, **kwargs): + next_step = self.active_workflow().active_index + 1 + if next_step >= len(self.active_workflow().steps): + raise Exception("Out of bounds request for step") + while not self.active_workflow().steps[next_step].enabled: + next_step += 1 + self.active_workflow().repository.el['active_step'] = next_step + self.active_workflow().active_index = next_step + + def go_prev(self, **kwargs): + prev_step = self.active_workflow().active_index - 1 + if prev_step < 0: + raise Exception("Out of bounds request for step") + while not self.active_workflow().steps[prev_step].enabled: + prev_step -= 1 + self.active_workflow().repository.el['active_step'] = prev_step + self.active_workflow().active_index = prev_step def prefill_repo(self, target_id, workflow_type): self.repository.el[self.repository.EDIT] = True -- cgit 1.2.3-korg