From 8864dae63b9512835862aabbe7f288fbe3c661e0 Mon Sep 17 00:00:00 2001 From: Sawyer Bergeron Date: Tue, 9 Apr 2019 16:30:57 -0400 Subject: Implement OPNFV workflow This is a counterpart to an update to network models, and allows for configuring baremetal OPNFV and Openstack deploys Change-Id: I0185dbfa6c9105d7e63a7e7d7dd1f5cf228a8877 Signed-off-by: Sawyer Bergeron Signed-off-by: Parker Berberian --- .../migrations/0007_opnfvapiconfig_opnfv_config.py | 20 ++ dashboard/src/api/models.py | 54 ++-- .../migrations/0006_booking_opnfv_config.py | 20 ++ dashboard/src/booking/models.py | 3 +- dashboard/src/booking/quick_deployer.py | 64 ++-- dashboard/src/resource_inventory/idf_templater.py | 14 +- .../migrations/0010_auto_20190430_1405.py | 54 ++++ dashboard/src/resource_inventory/models.py | 20 +- dashboard/src/resource_inventory/pdf_templater.py | 34 ++- dashboard/src/templates/base.html | 1 + dashboard/src/templates/booking/booking_table.html | 2 +- .../config_bundle/steps/assign_host_roles.html | 22 ++ .../config_bundle/steps/assign_network_roles.html | 22 ++ .../config_bundle/steps/config_software.html | 50 +--- .../config_bundle/steps/define_software.html | 129 +++----- .../config_bundle/steps/pick_installer.html | 32 ++ .../config_bundle/steps/table_formset.html | 63 ++++ .../src/templates/resource/steps/meta_info.html | 2 +- dashboard/src/workflow/forms.py | 53 +++- dashboard/src/workflow/models.py | 61 +++- dashboard/src/workflow/opnfv_workflow.py | 327 +++++++++++++++++++++ dashboard/src/workflow/snapshot_workflow.py | 8 +- dashboard/src/workflow/sw_bundle_workflow.py | 193 +++++------- dashboard/src/workflow/workflow_factory.py | 15 + 24 files changed, 920 insertions(+), 343 deletions(-) create mode 100644 dashboard/src/api/migrations/0007_opnfvapiconfig_opnfv_config.py create mode 100644 dashboard/src/booking/migrations/0006_booking_opnfv_config.py create mode 100644 dashboard/src/resource_inventory/migrations/0010_auto_20190430_1405.py create mode 100644 dashboard/src/templates/config_bundle/steps/assign_host_roles.html create mode 100644 dashboard/src/templates/config_bundle/steps/assign_network_roles.html create mode 100644 dashboard/src/templates/config_bundle/steps/pick_installer.html create mode 100644 dashboard/src/templates/config_bundle/steps/table_formset.html create mode 100644 dashboard/src/workflow/opnfv_workflow.py diff --git a/dashboard/src/api/migrations/0007_opnfvapiconfig_opnfv_config.py b/dashboard/src/api/migrations/0007_opnfvapiconfig_opnfv_config.py new file mode 100644 index 0000000..46f3631 --- /dev/null +++ b/dashboard/src/api/migrations/0007_opnfvapiconfig_opnfv_config.py @@ -0,0 +1,20 @@ +# Generated by Django 2.1 on 2019-05-01 18:53 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0010_auto_20190430_1405'), + ('api', '0006_auto_20190313_1729'), + ] + + operations = [ + migrations.AddField( + model_name='opnfvapiconfig', + name='opnfv_config', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.OPNFVConfig'), + ), + ] diff --git a/dashboard/src/api/models.py b/dashboard/src/api/models.py index 4ce8c3e..e17a911 100644 --- a/dashboard/src/api/models.py +++ b/dashboard/src/api/models.py @@ -23,7 +23,8 @@ from resource_inventory.models import ( Host, Image, Interface, - RemoteInfo + RemoteInfo, + OPNFVConfig ) from resource_inventory.idf_templater import IDFTemplater from resource_inventory.pdf_templater import PDFTemplater @@ -93,7 +94,7 @@ class LabManager(object): return {"status": "success"} def update_xdf(self, booking): - booking.pdf = PDFTemplater.makePDF(booking.resource) + booking.pdf = PDFTemplater.makePDF(booking) booking.idf = IDFTemplater().makeIDF(booking) booking.save() @@ -356,9 +357,12 @@ class OpnfvApiConfig(models.Model): scenario = models.CharField(max_length=300) roles = models.ManyToManyField(Host) delta = models.TextField() + opnfv_config = models.ForeignKey(OPNFVConfig, null=True, on_delete=models.SET_NULL) def to_dict(self): d = {} + if not self.opnfv_config: + return d if self.installer: d['installer'] = self.installer if self.scenario: @@ -367,8 +371,12 @@ class OpnfvApiConfig(models.Model): hosts = self.roles.all() if hosts.exists(): d['roles'] = [] - for host in self.roles.all(): - d['roles'].append({host.labid: host.config.opnfvRole.name}) + for host in hosts: + d['roles'].append({ + host.labid: self.opnfv_config.host_opnfv_config.get( + host_config__pk=host.config.pk + ).role.name + }) return d @@ -818,6 +826,7 @@ class JobFactory(object): ) cls.makeSoftware( hosts=hosts, + booking=booking, job=job ) all_users = list(booking.collaborators.all()) @@ -908,28 +917,19 @@ class JobFactory(object): network_config.save() @classmethod - def makeSoftware(cls, hosts=[], job=Job()): - def init_config(host): - opnfv_config = OpnfvApiConfig() - if host is not None: - opnfv = host.config.bundle.opnfv_config.first() - opnfv_config.installer = opnfv.installer.name - opnfv_config.scenario = opnfv.scenario.name - opnfv_config.save() - return opnfv_config - - try: - host = None - if len(hosts) > 0: - host = hosts[0] - opnfv_config = init_config(host) + def makeSoftware(cls, hosts=[], booking=None, job=Job()): - for host in hosts: - opnfv_config.roles.add(host) - software_config = SoftwareConfig.objects.create(opnfv=opnfv_config) - software_config.save() - software_relation = SoftwareRelation.objects.create(job=job, config=software_config) - software_relation.save() - return software_relation - except Exception: + if not booking.opnfv_config: return None + + opnfv_api_config = OpnfvApiConfig.objects.create( + opnfv_config=booking.opnfv_config, + installer=booking.opnfv_config.installer, + scenario=booking.opnfv_config.scenario, + ) + + for host in hosts: + opnfv_api_config.roles.add(host) + software_config = SoftwareConfig.objects.create(opnfv=opnfv_api_config) + software_relation = SoftwareRelation.objects.create(job=job, config=software_config) + return software_relation diff --git a/dashboard/src/booking/migrations/0006_booking_opnfv_config.py b/dashboard/src/booking/migrations/0006_booking_opnfv_config.py new file mode 100644 index 0000000..e5ffc71 --- /dev/null +++ b/dashboard/src/booking/migrations/0006_booking_opnfv_config.py @@ -0,0 +1,20 @@ +# Generated by Django 2.1 on 2019-05-01 18:02 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0010_auto_20190430_1405'), + ('booking', '0005_booking_idf'), + ] + + operations = [ + migrations.AddField( + model_name='booking', + name='opnfv_config', + field=models.ForeignKey(null=True, on_delete=django.db.models.deletion.SET_NULL, to='resource_inventory.OPNFVConfig'), + ), + ] diff --git a/dashboard/src/booking/models.py b/dashboard/src/booking/models.py index 02e03cc..9836730 100644 --- a/dashboard/src/booking/models.py +++ b/dashboard/src/booking/models.py @@ -9,7 +9,7 @@ ############################################################################## -from resource_inventory.models import ResourceBundle, ConfigBundle +from resource_inventory.models import ResourceBundle, ConfigBundle, OPNFVConfig from account.models import Lab from django.contrib.auth.models import User from django.db import models @@ -29,6 +29,7 @@ class Booking(models.Model): ext_count = models.IntegerField(default=2) resource = models.ForeignKey(ResourceBundle, on_delete=models.SET_NULL, null=True) config_bundle = models.ForeignKey(ConfigBundle, on_delete=models.SET_NULL, null=True) + opnfv_config = models.ForeignKey(OPNFVConfig, on_delete=models.SET_NULL, null=True) project = models.CharField(max_length=100, default="", blank=True, null=True) lab = models.ForeignKey(Lab, null=True, on_delete=models.SET_NULL) pdf = models.TextField(blank=True, default="") diff --git a/dashboard/src/booking/quick_deployer.py b/dashboard/src/booking/quick_deployer.py index 640ded9..763c8a0 100644 --- a/dashboard/src/booking/quick_deployer.py +++ b/dashboard/src/booking/quick_deployer.py @@ -32,7 +32,8 @@ from resource_inventory.models import ( OPNFVConfig, Network, NetworkConnection, - NetworkRole + NetworkRole, + HostOPNFVConfig, ) from resource_inventory.resource_manager import ResourceManager from resource_inventory.pdf_templater import PDFTemplater @@ -185,18 +186,30 @@ def generate_hostconfig(generic_host, image, config_bundle): hconf = HostConfiguration() hconf.host = generic_host hconf.image = image - - opnfvrole = OPNFVRole.objects.get(name="Jumphost") - if not opnfvrole: - raise OPNFVRoleDNE("No jumphost role was found.") - - hconf.opnfvRole = opnfvrole hconf.bundle = config_bundle + hconf.is_head_node = True hconf.save() return hconf +def generate_hostopnfv(hostconfig, opnfvconfig): + config = HostOPNFVConfig() + role = None + try: + role = OPNFVRole.objects.get(name="Jumphost") + except Exception: + role = OPNFVRole.objects.create( + name="Jumphost", + description="Single server jumphost role" + ) + config.role = role + config.host_config = hostconfig + config.opnfv_config = opnfvconfig + config.save() + return config + + def generate_resource_bundle(generic_resource_bundle, config_bundle): # warning: requires cleanup try: resource_manager = ResourceManager.getInstance() @@ -273,18 +286,16 @@ def create_from_form(form, request): check_available_matching_host(lab, host_profile) # requires cleanup if failure after this point grbundle = generate_grb(request.user, lab, quick_booking_id) - gresource = generate_gresource(grbundle, hostname) - ghost = generate_ghost(gresource, host_profile) - cbundle = generate_config_bundle(request.user, quick_booking_id, grbundle) + hconf = generate_hostconfig(ghost, image, cbundle) # if no installer provided, just create blank host + opnfv_config = None if installer: - generate_opnfvconfig(scenario, installer, cbundle) - - generate_hostconfig(ghost, image, cbundle) + opnfv_config = generate_opnfvconfig(scenario, installer, cbundle) + generate_hostopnfv(hconf, opnfv_config) # construct generic interfaces for interface_profile in host_profile.interfaceprofile.all(): @@ -297,24 +308,27 @@ def create_from_form(form, request): resource_bundle = generate_resource_bundle(grbundle, cbundle) # generate booking - booking = Booking() - booking.purpose = purpose_field - booking.project = project_field - booking.lab = lab - booking.owner = request.user - booking.start = timezone.now() - booking.end = timezone.now() + timedelta(days=int(length)) - booking.resource = resource_bundle - booking.pdf = PDFTemplater.makePDF(booking.resource) - booking.config_bundle = cbundle - booking.save() + booking = Booking.objects.create( + purpose=purpose_field, + project=project_field, + lab=lab, + owner=request.user, + start=timezone.now(), + end=timezone.now() + timedelta(days=int(length)), + resource=resource_bundle, + config_bundle=cbundle, + opnfv_config=opnfv_config + ) + booking.pdf = PDFTemplater.makePDF(booking) + users_field = users_field[2:-2] if users_field: # may be empty after split, if no collaborators entered users_field = json.loads(users_field) for collaborator in users_field: user = User.objects.get(id=collaborator['id']) booking.collaborators.add(user) - booking.save() + + booking.save() # generate job JobFactory.makeCompleteJob(booking) diff --git a/dashboard/src/resource_inventory/idf_templater.py b/dashboard/src/resource_inventory/idf_templater.py index 7cd13bb..26307e3 100644 --- a/dashboard/src/resource_inventory/idf_templater.py +++ b/dashboard/src/resource_inventory/idf_templater.py @@ -12,10 +12,7 @@ from django.template.loader import render_to_string from account.models import PublicNetwork -from resource_inventory.models import ( - OPNFVConfig, - Vlan -) +from resource_inventory.models import Vlan class IDFTemplater: @@ -67,7 +64,7 @@ class IDFTemplater: def get_public_net(self, booking): public = {} - config = OPNFVConfig.objects.get(bundle=booking.config_bundle) + config = booking.opnfv_config public_role = config.networks.get(name="public") public_vlan = Vlan.objects.filter(network=public_role.network).first() public_network = PublicNetwork.objects.get(vlan=public_vlan.vlan_id, lab=booking.lab) @@ -91,7 +88,7 @@ class IDFTemplater: return net def get_single_net_config(self, booking, net_name): - config = OPNFVConfig.objects.get(bundle=booking.config_bundle) + config = booking.opnfv_config role = config.networks.get(name=net_name) vlan = Vlan.objects.filter(network=role.network).first() self.networks[net_name]['vlan'] = vlan.vlan_id @@ -127,7 +124,10 @@ class IDFTemplater: return bridges def get_fuel_nodes(self, booking): - hosts = booking.resource.hosts.exclude(config__opnfvRole__name="jumphost") + jumphost = booking.opnfv_config.host_opnfv_config.get( + role__name__iexact="jumphost" + ) + hosts = booking.resource.hosts.exclude(pk=jumphost.pk) nodes = [] for host in hosts: node = {} diff --git a/dashboard/src/resource_inventory/migrations/0010_auto_20190430_1405.py b/dashboard/src/resource_inventory/migrations/0010_auto_20190430_1405.py new file mode 100644 index 0000000..3823eaf --- /dev/null +++ b/dashboard/src/resource_inventory/migrations/0010_auto_20190430_1405.py @@ -0,0 +1,54 @@ +# Generated by Django 2.1 on 2019-04-30 14:05 + +from django.db import migrations, models +import django.db.models.deletion + + +class Migration(migrations.Migration): + + dependencies = [ + ('resource_inventory', '0009_auto_20190315_1757'), + ] + + operations = [ + migrations.CreateModel( + name='HostOPNFVConfig', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ], + ), + migrations.RemoveField( + model_name='hostconfiguration', + name='opnfvRole', + ), + migrations.AddField( + model_name='hostconfiguration', + name='is_head_node', + field=models.BooleanField(default=False), + ), + migrations.AddField( + model_name='opnfvconfig', + name='description', + field=models.CharField(blank=True, default='', max_length=600), + ), + migrations.AddField( + model_name='opnfvconfig', + name='name', + field=models.CharField(blank=True, default='', max_length=300), + ), + migrations.AddField( + model_name='hostopnfvconfig', + name='host_config', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='host_opnfv_config', to='resource_inventory.HostConfiguration'), + ), + migrations.AddField( + model_name='hostopnfvconfig', + name='opnfv_config', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='host_opnfv_config', to='resource_inventory.OPNFVConfig'), + ), + migrations.AddField( + model_name='hostopnfvconfig', + name='role', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='host_opnfv_configs', to='resource_inventory.OPNFVRole'), + ), + ] diff --git a/dashboard/src/resource_inventory/models.py b/dashboard/src/resource_inventory/models.py index d3f47d4..b9f2c44 100644 --- a/dashboard/src/resource_inventory/models.py +++ b/dashboard/src/resource_inventory/models.py @@ -191,7 +191,7 @@ class ResourceBundle(models.Model): return "instance of " + str(self.template) def get_host(self, role="Jumphost"): - return Host.objects.filter(bundle=self, config__opnfvRole__name=role).first() + return Host.objects.filter(bundle=self, config__is_head_node=True).first() # should only ever be one, but it is not an invariant in the models class GenericInterface(models.Model): @@ -252,6 +252,8 @@ class OPNFVConfig(models.Model): scenario = models.ForeignKey(Scenario, on_delete=models.CASCADE) bundle = models.ForeignKey(ConfigBundle, related_name="opnfv_config", on_delete=models.CASCADE) networks = models.ManyToManyField(NetworkRole) + name = models.CharField(max_length=300, blank=True, default="") + description = models.CharField(max_length=600, blank=True, default="") def __str__(self): return "OPNFV job with " + str(self.installer) + " and " + str(self.scenario) @@ -297,12 +299,18 @@ class HostConfiguration(models.Model): host = models.ForeignKey(GenericHost, related_name="configuration", on_delete=models.CASCADE) image = models.ForeignKey(Image, on_delete=models.PROTECT) bundle = models.ForeignKey(ConfigBundle, related_name="hostConfigurations", null=True, on_delete=models.CASCADE) - opnfvRole = models.ForeignKey(OPNFVRole, on_delete=models.SET(get_sentinal_opnfv_role)) + is_head_node = models.BooleanField(default=False) def __str__(self): return "config with " + str(self.host) + " and image " + str(self.image) +class HostOPNFVConfig(models.Model): + role = models.ForeignKey(OPNFVRole, related_name="host_opnfv_configs", on_delete=models.CASCADE) + host_config = models.ForeignKey(HostConfiguration, related_name="host_opnfv_config", on_delete=models.CASCADE) + opnfv_config = models.ForeignKey(OPNFVConfig, related_name="host_opnfv_config", on_delete=models.CASCADE) + + class RemoteInfo(models.Model): address = models.CharField(max_length=15) mac_address = models.CharField(max_length=17) @@ -353,3 +361,11 @@ class Interface(models.Model): def __str__(self): return self.mac_address + " on host " + str(self.host) + + +class OPNFV_SETTINGS(): + """ + This is a static configuration class + """ + # all the required network types in PDF/IDF spec + NETWORK_ROLES = ["public", "private", "admin", "mgmt"] diff --git a/dashboard/src/resource_inventory/pdf_templater.py b/dashboard/src/resource_inventory/pdf_templater.py index 2db2129..d08b303 100644 --- a/dashboard/src/resource_inventory/pdf_templater.py +++ b/dashboard/src/resource_inventory/pdf_templater.py @@ -19,15 +19,15 @@ class PDFTemplater: """ @classmethod - def makePDF(cls, resource): + def makePDF(cls, booking): """ fills the pod descriptor file template with info about the resource """ template = "dashboard/pdf.yaml" info = {} - info['details'] = cls.get_pdf_details(resource) - info['jumphost'] = cls.get_pdf_jumphost(resource) - info['nodes'] = cls.get_pdf_nodes(resource) + info['details'] = cls.get_pdf_details(booking.resource) + info['jumphost'] = cls.get_pdf_jumphost(booking) + info['nodes'] = cls.get_pdf_nodes(booking) return render_to_string(template, context=info) @@ -63,22 +63,40 @@ class PDFTemplater: return details @classmethod - def get_pdf_jumphost(cls, resource): + def get_jumphost(cls, booking): + jumphost = None + if booking.opnfv_config: + jumphost_opnfv_config = booking.opnfv_config.host_opnfv_config.get( + role__name__iexact="jumphost" + ) + jumphost = booking.resource.hosts.get(config=jumphost_opnfv_config.host_config) + else: # if there is no opnfv config, use headnode + jumphost = Host.objects.filter( + bundle=booking.resource, + config__is_head_node=True + ).first() + + return jumphost + + @classmethod + def get_pdf_jumphost(cls, booking): """ returns a dict of all the info for the "jumphost" section """ - jumphost = Host.objects.get(bundle=resource, config__opnfvRole__name__iexact="jumphost") + jumphost = cls.get_jumphost(booking) jumphost_info = cls.get_pdf_host(jumphost) jumphost_info['os'] = jumphost.config.image.os.name return jumphost_info @classmethod - def get_pdf_nodes(cls, resource): + def get_pdf_nodes(cls, booking): """ returns a list of all the "nodes" (every host except jumphost) """ pdf_nodes = [] - nodes = Host.objects.filter(bundle=resource).exclude(config__opnfvRole__name__iexact="jumphost") + nodes = set(Host.objects.filter(bundle=booking.resource)) + nodes.discard(cls.get_jumphost(booking)) + for node in nodes: pdf_nodes.append(cls.get_pdf_host(node)) diff --git a/dashboard/src/templates/base.html b/dashboard/src/templates/base.html index 02c67dc..f48a201 100644 --- a/dashboard/src/templates/base.html +++ b/dashboard/src/templates/base.html @@ -134,6 +134,7 @@ +
  • diff --git a/dashboard/src/templates/booking/booking_table.html b/dashboard/src/templates/booking/booking_table.html index e0c5f49..32a0146 100644 --- a/dashboard/src/templates/booking/booking_table.html +++ b/dashboard/src/templates/booking/booking_table.html @@ -30,7 +30,7 @@ {{ booking.end }} - {{ booking.resource.get_host.config.image.os.name }} + {{ booking.resource.get_head_node.config.image.os.name }} {% endfor %} diff --git a/dashboard/src/templates/config_bundle/steps/assign_host_roles.html b/dashboard/src/templates/config_bundle/steps/assign_host_roles.html new file mode 100644 index 0000000..3ba7665 --- /dev/null +++ b/dashboard/src/templates/config_bundle/steps/assign_host_roles.html @@ -0,0 +1,22 @@ +{% extends "config_bundle/steps/table_formset.html" %} + +{% load bootstrap3 %} + +{% block table %} + + + Host + Role + + + + {% for form in formset %} + + {% bootstrap_field form.host_name show_label=False %} + {% bootstrap_field form.role show_label=False %} + + {% endfor %} + + +{{formset.management_form}} +{% endblock table %} diff --git a/dashboard/src/templates/config_bundle/steps/assign_network_roles.html b/dashboard/src/templates/config_bundle/steps/assign_network_roles.html new file mode 100644 index 0000000..0e887d6 --- /dev/null +++ b/dashboard/src/templates/config_bundle/steps/assign_network_roles.html @@ -0,0 +1,22 @@ +{% extends "config_bundle/steps/table_formset.html" %} + +{% load bootstrap3 %} + +{% block table %} + + + Role + Network + + + + {% for form in formset %} + + {% bootstrap_field form.role show_label=False %} + {% bootstrap_field form.network show_label=False %} + + {% endfor %} + + +{{formset.management_form}} +{% endblock table %} diff --git a/dashboard/src/templates/config_bundle/steps/config_software.html b/dashboard/src/templates/config_bundle/steps/config_software.html index e1f9541..b181c7e 100644 --- a/dashboard/src/templates/config_bundle/steps/config_software.html +++ b/dashboard/src/templates/config_bundle/steps/config_software.html @@ -8,58 +8,12 @@
    {% csrf_token %}

    Give it a name:

    - {{ form.name }} + {% bootstrap_field form.name %}

    And a description:

    - {{ form.description }} - - + {% bootstrap_field form.description %}
    - {% endblock content %} diff --git a/dashboard/src/templates/config_bundle/steps/define_software.html b/dashboard/src/templates/config_bundle/steps/define_software.html index 8e7be91..ba1ff34 100644 --- a/dashboard/src/templates/config_bundle/steps/define_software.html +++ b/dashboard/src/templates/config_bundle/steps/define_software.html @@ -1,102 +1,55 @@ -{% extends "workflow/viewport-element.html" %} -{% load staticfiles %} +{% extends "config_bundle/steps/table_formset.html" %} {% load bootstrap3 %} +{% block table %} + + + Device + Image + HeadNode + + + +{% for form in formset %} + + {% bootstrap_field form.host_name show_label=False %} + {% bootstrap_field form.image show_label=False %} + + + {{ form.headnode }} + + +{% endfor %} +{{formset.management_form}} + +{% endblock table %} + +{% block tablejs %} + +{% endblock tablejs %} -{% block extrahead %} - - - - -{% endblock extrahead %} - -{% block content %} -{% if error %} -

    {{ error }}

    -{% else %} -
    - {% csrf_token %} - -
    -
    -
    - - - {% block table %} - - - - - - - - - {% for form in formset %} - - {% for field in form %} - - {% endfor %} - - {% endfor %} - {{formset.management_form}} - - {% endblock table %} - -
    DeviceRoleImage
    {{ field }}
    -
    - - - -
    - -
    -
    - - -{% endif %} -{% endblock content %} - -{% block extrajs %} - {{ block.super }} - - - - - - - - {% block tablejs %} - {% endblock tablejs %} -{% endblock extrajs %} - - -{% block onleave %} -var form = $("#softwaredefinitionform"); +var form = $("#table_formset"); var formData = form.serialize(); var req = new XMLHttpRequest(); req.open("POST", "/wf/workflow/", false); req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); req.onerror = function() { alert("problem with form submission"); } req.send(formData); -{% endblock %} +{% endblock onleave %} diff --git a/dashboard/src/templates/config_bundle/steps/pick_installer.html b/dashboard/src/templates/config_bundle/steps/pick_installer.html new file mode 100644 index 0000000..3b170d9 --- /dev/null +++ b/dashboard/src/templates/config_bundle/steps/pick_installer.html @@ -0,0 +1,32 @@ +{% extends "workflow/viewport-element.html" %} +{% load staticfiles %} + +{% load bootstrap3 %} + +{% block content %} + +{% if unavailable %} +

    Please choose a config bundle first

    +{% else %} + +
    + {% csrf_token %} +

    Choose your installer:

    + {% bootstrap_field form.installer %} +

    Choose your scenario:

    + {% bootstrap_field form.scenario %} +
    + +{% endif %} + +{% endblock content %} + +{% block onleave %} +var form = $("#installer_form"); +var formData = form.serialize(); +var req = new XMLHttpRequest(); +req.open("POST", "/wf/workflow/", false); +req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); +req.onerror = function() { alert("problem with form submission"); } +req.send(formData); +{% endblock %} diff --git a/dashboard/src/templates/config_bundle/steps/table_formset.html b/dashboard/src/templates/config_bundle/steps/table_formset.html new file mode 100644 index 0000000..ad2c5a3 --- /dev/null +++ b/dashboard/src/templates/config_bundle/steps/table_formset.html @@ -0,0 +1,63 @@ +{% extends "workflow/viewport-element.html" %} +{% load staticfiles %} + +{% load bootstrap3 %} + +{% block extrahead %} + + + + + +{% endblock extrahead %} + +{% block content %} +{% if error %} +

    {{ error }}

    +{% else %} +
    +
    + {% csrf_token %} + +
    +
    +
    + + + {% block table %} + {% endblock table %} + +
    +
    +
    +
    +
    +
    + +{% endif %} +{% endblock content %} + +{% block extrajs %} + {{ block.super }} + + + + + + + + {% block tablejs %} + {% endblock tablejs %} +{% endblock extrajs %} + + +{% block onleave %} +var form = $("#table_formset"); +var formData = form.serialize(); +var req = new XMLHttpRequest(); +req.open("POST", "/wf/workflow/", false); +req.setRequestHeader("Content-Type", "application/x-www-form-urlencoded"); +req.onerror = function() { alert("problem with form submission"); } +req.send(formData); +{% endblock %} diff --git a/dashboard/src/templates/resource/steps/meta_info.html b/dashboard/src/templates/resource/steps/meta_info.html index 7a1b56a..da98267 100644 --- a/dashboard/src/templates/resource/steps/meta_info.html +++ b/dashboard/src/templates/resource/steps/meta_info.html @@ -7,7 +7,7 @@