Merge "Fixing Network Models"
authorParker Berberian <pberberian@iol.unh.edu>
Tue, 16 Apr 2019 13:59:21 +0000 (13:59 +0000)
committerGerrit Code Review <gerrit@opnfv.org>
Tue, 16 Apr 2019 13:59:21 +0000 (13:59 +0000)
20 files changed:
dashboard/src/api/views.py
dashboard/src/booking/forms.py
dashboard/src/booking/quick_deployer.py
dashboard/src/booking/views.py
dashboard/src/dashboard/actions.py [new file with mode: 0644]
dashboard/src/dashboard/views.py
dashboard/src/resource_inventory/models.py
dashboard/src/resource_inventory/urls.py
dashboard/src/resource_inventory/views.py
dashboard/src/templates/booking/booking_table.html
dashboard/src/templates/booking/quick_deploy.html
dashboard/src/templates/booking/steps/booking_meta.html
dashboard/src/templates/config_bundle/steps/config_software.html
dashboard/src/templates/dashboard/lab_detail.html
dashboard/src/templates/dashboard/lab_list.html
dashboard/src/templates/dashboard/multiple_select_filter_widget.html
dashboard/src/templates/resource/hostprofile_detail.html [new file with mode: 0644]
dashboard/src/templates/resource/hosts.html
dashboard/src/templates/resource/steps/meta_info.html
dashboard/src/templates/workflow/viewport-base.html

index a56dcfe..2ae1ac5 100644 (file)
@@ -101,6 +101,8 @@ def specific_task(request, lab_name="", job_id="", task_id=""):
             task.status = request.POST.get('status')
         if 'message' in request.POST:
             task.message = request.POST.get('message')
+        if 'lab_token' in request.POST:
+            task.lab_token = request.POST.get('lab_token')
         task.save()
         NotificationHandler.task_updated(task)
         d = {}
index 7ba5af0..9349ac1 100644 (file)
@@ -8,7 +8,6 @@
 ##############################################################################
 import django.forms as forms
 from django.forms.widgets import NumberInput
-from django.db.models import Q
 
 from workflow.forms import (
     SearchableSelectMultipleWidget,
@@ -22,7 +21,6 @@ from resource_inventory.models import Image, Installer, Scenario
 class QuickBookingForm(forms.Form):
     purpose = forms.CharField(max_length=1000)
     project = forms.CharField(max_length=400)
-    image = forms.ModelChoiceField(queryset=Image.objects.all())
     hostname = forms.CharField(max_length=400)
 
     installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=False)
@@ -40,14 +38,14 @@ class QuickBookingForm(forms.Form):
         elif data and "users" in data:
             chosen_users = data.getlist("users")
 
-        if user:
-            self.image = forms.ModelChoiceField(queryset=Image.objects.filter(
-                Q(public=True) | Q(owner=user)), required=False)
-        else:
-            self.image = forms.ModelChoiceField(queryset=Image.objects.all(), required=False)
-
         super(QuickBookingForm, self).__init__(data=data, **kwargs)
 
+        self.fields["image"] = forms.ModelChoiceField(
+            queryset=Image.objects.difference(
+                Image.objects.filter(public=False).difference(Image.objects.filter(owner=user))
+            )
+        )
+
         self.fields['users'] = forms.CharField(
             widget=SearchableSelectMultipleWidget(
                 attrs=self.build_search_widget_attrs(chosen_users, default_user=default_user)
index f8dc9ff..cc593fa 100644 (file)
@@ -119,7 +119,7 @@ def check_available_matching_host(lab, hostprofile):
     available_host_types = ResourceManager.getInstance().getAvailableHostTypes(lab)
     if hostprofile not in available_host_types:
         # TODO: handle deleting generic resource in this instance along with grb
-        raise HostNotAvailable("Could not book selected host due to changed availability. Try again later")
+        raise HostNotAvailable('Requested host type is not available. Please try again later. Host availability can be viewed in the "Hosts" tab to the left.')
 
     hostset = Host.objects.filter(lab=lab, profile=hostprofile).filter(booked=False).filter(working=True)
     if not hostset.exists():
index 1e14b8e..8211a0c 100644 (file)
@@ -62,8 +62,7 @@ def quick_create(request):
             try:
                 create_from_form(form, request)
             except Exception as e:
-                messages.error(request, "Whoops, looks like an error occurred. "
-                                        "Let the admins know that you got the following message: " + str(e))
+                messages.error(request, "Whoops, an error occurred: " + str(e))
                 return render(request, 'workflow/exit_redirect.html', context)
 
             messages.success(request, "We've processed your request. "
diff --git a/dashboard/src/dashboard/actions.py b/dashboard/src/dashboard/actions.py
new file mode 100644 (file)
index 0000000..44b1fdd
--- /dev/null
@@ -0,0 +1,47 @@
+##############################################################################
+# Copyright (c) 2019 Parker Berberian, Sawyer Bergeron, and others.
+#
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Apache License, Version 2.0
+# which accompanies this distribution, and is available at
+# http://www.apache.org/licenses/LICENSE-2.0
+##############################################################################
+
+from resource_inventory.models import Host, Vlan
+from account.models import Lab
+from booking.models import Booking
+from datetime import timedelta
+from django.utils import timezone
+
+
+def free_leaked_hosts(free_old_bookings=False, old_booking_age=timedelta(days=1)):
+    bundles = [booking.resource for booking in Booking.objects.filter(end__gt=timezone.now())]
+    active_hosts = set()
+    for bundle in bundles:
+        active_hosts.update([host for host in bundle.hosts.all()])
+
+    marked_hosts = set(Host.objects.filter(booked=True))
+
+    for host in (marked_hosts - active_hosts):
+        host.booked = False
+        host.save()
+
+
+def free_leaked_public_vlans():
+    booked_host_interfaces = []
+
+    for lab in Lab.objects.all():
+
+        for host in Host.objects.filter(booked=True).filter(lab=lab):
+            for interface in host.interfaces.all():
+                booked_host_interfaces.append(interface)
+
+        in_use_vlans = Vlan.objects.filter(public=True).distinct('vlan_id').filter(interface__in=booked_host_interfaces)
+
+        manager = lab.vlan_manager
+
+        for vlan in Vlan.objects.all():
+            if vlan not in in_use_vlans:
+                if vlan.public:
+                    manager.release_public_vlan(vlan.vlan_id)
+                manager.release_vlans(vlan)
index c4a6685..aaad7ab 100644 (file)
@@ -46,7 +46,7 @@ def lab_detail_view(request, lab_name):
             'title': "Lab Overview",
             'lab': lab,
             'hostprofiles': lab.hostprofiles.all(),
-            'images': images
+            'images': images,
         }
     )
 
index 0b7b24c..d3f47d4 100644 (file)
@@ -190,6 +190,9 @@ class ResourceBundle(models.Model):
             return "Resource bundle " + str(self.id) + " with no template"
         return "instance of " + str(self.template)
 
+    def get_host(self, role="Jumphost"):
+        return Host.objects.filter(bundle=self, config__opnfvRole__name=role).first()
+
 
 class GenericInterface(models.Model):
     id = models.AutoField(primary_key=True)
index 4e159ba..a72871b 100644 (file)
@@ -25,10 +25,11 @@ Including another URLconf
     2. Add a URL to urlpatterns:  url(r'^blog/', include('blog.urls'))
 """
 from django.conf.urls import url
-from resource_inventory.views import HostView
+from resource_inventory.views import HostView, hostprofile_detail_view
 
 
 app_name = "resource"
 urlpatterns = [
-    url(r'^hosts$', HostView.as_view(), name='hosts')
+    url(r'^hosts$', HostView.as_view(), name='hosts'),
+    url(r'^profiles/(?P<hostprofile_id>.+)/$', hostprofile_detail_view, name='host_detail'),
 ]
index 2937bd7..8c3d899 100644 (file)
@@ -9,8 +9,10 @@
 
 
 from django.views.generic import TemplateView
+from django.shortcuts import get_object_or_404
+from django.shortcuts import render
 
-from resource_inventory.models import Host
+from resource_inventory.models import HostProfile, Host
 
 
 class HostView(TemplateView):
@@ -21,3 +23,16 @@ class HostView(TemplateView):
         hosts = Host.objects.filter(working=True)
         context.update({'hosts': hosts, 'title': "Hardware Resources"})
         return context
+
+
+def hostprofile_detail_view(request, hostprofile_id):
+    hostprofile = get_object_or_404(HostProfile, id=hostprofile_id)
+
+    return render(
+        request,
+        "resource/hostprofile_detail.html",
+        {
+            'title': "Host Type: " + str(hostprofile.name),
+            'hostprofile': hostprofile
+        }
+    )
index 5e82645..e0c5f49 100644 (file)
@@ -5,11 +5,10 @@
 <tr>
     <th>Owner</th>
     <th>Purpose</th>
+    <th>Project</th>
     <th>Start</th>
     <th>End</th>
     <th>Operating System</th>
-    <th>Installer</th>
-    <th>Scenario</th>
 </tr>
 </thead>
 <tbody>
             {{ booking.purpose }}
         </td>
         <td>
-            {{ booking.start }}
-        </td>
-        <td>
-            {{ booking.end }}
+            {{ booking.project }}
         </td>
         <td>
-            {{ booking.opsys }}
+            {{ booking.start }}
         </td>
         <td>
-            {{ booking.installer }}
+            {{ booking.end }}
         </td>
         <td>
-            {{ booking.scenario }}
+            {{ booking.resource.get_host.config.image.os.name }}
         </td>
     </tr>
 {% endfor %}
index 3837315..38294b2 100644 (file)
     .grid_element_2third {
         grid-column-start: span 8;
     }
+    #id_length {
+        -moz-appearance: none;
+        border: none;
+        box-shadow: none;
+    }
+    input[type=range]::-moz-range-track {
+        background: #cccccc;
+    }
 </style>
 {% bootstrap_form_errors form type='non_fields' %}
 <form id="quick_booking_form" action="/booking/quick/" method="POST" class="form">
@@ -33,7 +41,7 @@
 <div class="grid_container">
 <div class="grid_element host_select_pane grid_element_wide">
 <p>Please select a host type you wish to book. Only available types are shown.</p>
-{% bootstrap_field form.filter_field %}
+{% bootstrap_field form.filter_field show_label=False %}
 </div>
 <div class="grid_element booking_info_pane grid_element_1third">
     {% bootstrap_field form.purpose %}
         <label>Collaborators</label>
         {{ form.users }}
 </div>
-<div class="grid_element configuration_pane grid_element_1third">
-    {% bootstrap_field form.hostname %}
-    {% bootstrap_field form.image %}
-    {% bootstrap_field form.installer %}
-    {% bootstrap_field form.scenario %}
+<div class="grid_element_1third">
+    <div class="configuration_pane grid_element">
+        {% bootstrap_field form.hostname %}
+        {% bootstrap_field form.image %}
+    </div>
+    <div class="configuration_pane grid_element">
+        <strong>OPNFV: (Optional)</strong>
+        {% bootstrap_field form.installer %}
+        {% bootstrap_field form.scenario %}
+    </div>
 </div>
 </div>
 <script type="text/javascript">
 
         $('#id_image').children().hide();
 
+        for( var i = 0; i < drop.childNodes.length; i++ )
+        {
+            drop.childNodes[i].disabled = true; // closest we can get on safari to hiding it outright
+        }
+
+
         var empty_map = {}
 
         for ( var i=0; i < drop.childNodes.length; i++ )
                 if( image_object.host_profile == host_pk && image_object.lab == lab_pk )
                 {
                     drop.childNodes[i].style.display = "inherit";
+                    drop.childNodes[i].disabled = false;
                 }
             }
         }
     }
 
-    $('#id_image').children().hide();
+    imageHider();
     $('#id_installer').children().hide();
     $('#id_scenario').children().hide();
 
         }
 
         targ_id = "#" + target;
+
         $(targ_id).children().hide();
+
+        for (var i = 0; i < document.getElementById(target).childNodes.length; i++)
+        {
+            document.getElementById(target).childNodes[i].disabled = true;
+        }
         var drop = document.getElementById(master);
         var opts = target_filter[drop.options[drop.selectedIndex].value];
         if (!opts) {
         for (var i = 0; i < document.getElementById(target).childNodes.length; i++) {
             if (document.getElementById(target).childNodes[i].value in opts && !(document.getElementById(target).childNodes[i].value in emptyMap) ) {
                 document.getElementById(target).childNodes[i].style.display = "inherit";
+                document.getElementById(target).childNodes[i].disabled = false;
             }
         }
     }
index e4881ae..fe43f53 100644 (file)
         grid-template-columns: 45% 10% 45%;
         border: none;
     }
+
+    #id_length {
+        -moz-appearance: none;
+        border: none;
+        box-shadow: none;
+    }
+    input[type=range]::-moz-range-track {
+        background: #cccccc;
+    }
 </style>
 
 {% bootstrap_form_errors form type='non_fields' %}
index ca15c77..e1f9541 100644 (file)
 
     <p>And a description:</p>
     {{ form.description }}
-    <p>Install OPNFV?</p>
-    {{ form.opnfv }}
-    <p>Choose your:</p>
-    <table>
-        <thead>
-            <tr>
-                <th>Installer</th>
-                <th>Scenario</th>
-            </tr>
-        </thead>
-        <tbody>
-            <tr>
-                <td>{{form.installer}}</td>
-                <td>{{form.scenario}}</td>
-            </tr>
-        </tbody>
-    </table>
+    <div id="hidden" style="display:none;">
+        <p>Install OPNFV?</p>
+        {{ form.opnfv }}
+        <p>Choose your:</p>
+        <table>
+            <thead>
+                <tr>
+                    <th>Installer</th>
+                    <th>Scenario</th>
+                </tr>
+            </thead>
+            <tbody>
+                <tr>
+                    <td>{{form.installer}}</td>
+                    <td>{{form.scenario}}</td>
+                </tr>
+            </tbody>
+        </table>
+    </div>
 
 </form>
 
index 4c06245..7938e86 100644 (file)
@@ -62,7 +62,7 @@
                     <tr>
                         <td>{{profile.name}}</td>
                         <td>{{profile.description}}</td>
-                        <td>{{profile.labs}}</td>
+                        <td><a href="/resource/profiles/{{ profile.id }}" class="btn btn-primary">Profile</a></td>
                     </tr>
                     {% endfor %}
                 </table>
index a86f7f4..c459dd9 100644 (file)
@@ -1,87 +1,26 @@
 {% extends "base.html" %}
-{% load staticfiles %}
-
-{% block extrahead %}
-    {{block.super}}
-    <script src="https://cdn.rawgit.com/google/code-prettify/master/loader/run_prettify.js?lang=yaml"></script>
-{% endblock %}
-
 {% block content %}
-    <style>
-     .grid-item-container {
-         padding: 10px;
-     }
-
-         .grid-item {
-             cursor: pointer;
-             border:2px;
-             border-style:none;
-             border-color:black;
-             border-radius: 5px;
-             padding: 7px;
-             color: inherit;
-
-             box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75);
-             transition-property: box-shadow, background-color;
-             transition-duration: .2s;
-         }
-
-     .grid-item-text
-     {
-         color: inherit;
-         text-decoration: none;
-     }
-     .grid-item-text:hover
-     {
-         color: #121212;
-         text-decoration: none;
-     }
-
-     .grid-item:hover {
-         box-shadow: 0px 0px 14px 0px rgba(0,0,0,0.75);
-         transition-property: box-shadow;
-         transition-duration: .2s;
-
-     }
-
-     .selected_node {
-         box-shadow: 0px 0px 14px 0px rgba(0,0,0,0.75);
-         background-color: #CCECD7;
-         transition-property: background-color;
-         transition-duration: .2s;
-     }
-
-     .disabled_node {
-         cursor: not-allowed;
-         background-color: #EFEFEF;
-         box-shadow: 0px 0px 1px 0px rgba(0,0,0,0.75);
-         transition-property: box-shadow;
-         transition-duration: .2s;
-     }
-
-     .disabled_node:hover {
-         box-shadow: 0px 0px 1px 0px rgba(0,0,0,0.75);
-     }
-
-    </style>
-    <div class="container-fluid">
-    <div class="row">
-
-    <div class="listgrid">
-        {% for lab in labs %}
-        <div class="grid-item-container col-lg-2 col-mid-4 col-sm-6">
-
-            <a href="{{ lab.name }}" class="grid-item-text">
-
-            <div class="grid-item">
-            <h4 class="grid-item-header">{{ lab.name }}</h4>
-            <p class="grid-item-description">{{ lab.description }}</p>
+    <h2>Labs</h2>
+    <div class="card_container">
+    {% for lab in labs %}
+        <div class="detail_card">
+            <div>
+                <h3>{{lab.name}}</h3>
+                <ul class="list-group">
+                    <li class="list-group-item">name: {{lab.name}}</li>
+                    <li class="list-group-item">description: {{lab.description}}</li>
+                    <li class="list-group-item">location: {{lab.location}}</li>
+                    {% if lab.status == 0 %}
+                    <li class="list-group-item">status: Up</li>
+                    {% elif lab.status == 100 %}
+                    <li class="list-group-item">status: Down for Maintenance</li>
+                    {% elif lab.status == 200 %}
+                    <li class="list-group-item">status: Down</li>
+                    {% endif %}
+                </ul>
             </div>
-            </a>
+            <a class="btn btn-primary" href="/lab/{{lab.name}}/">Details</a>
         </div>
-        {% endfor %}
-    </div>
+    {% endfor %}
     </div>
-    </div>
-
-{% endblock content %}
+{% endblock %}
index 4e47ce0..628fd95 100644 (file)
@@ -6,12 +6,16 @@
 }
 .class_grid_wrapper {
     border: 0px;
-    border-left: 1px;
+    text-align: center;
     border-right: 1px;
     border-style: solid;
     border-color: grey;
-    text-align: center;
 }
+
+.class_grid_wrapper:last-child {
+    border-right: none;
+}
+
 .grid_wrapper {
     display: grid;
     grid-template-columns: 1fr 1fr;
     margin-top: 10px;
 }
 
+#dropdown_wrapper > div > h5 {
+    margin: 12px;
+    display: inline-block;
+    vertical-align: middle;
+}
+
+#dropdown_wrapper > div > button {
+    padding: 7px;
+    margin: 2px;
+    float: right;
+    width: 80px;
+}
+#dropdown_wrapper > div > input {
+    padding: 7px;
+    margin: 2px;
+    float: right;
+    width: 300px;
+    width: calc(100% - 240px);
+}
+
+#dropdown_wrapper > div {
+    border:2px;
+    border-style:none;
+    border-color:black;
+    border-radius: 5px;
+    margin:20px;
+    padding: 2px;
+    box-shadow: 0px 0px 7px 0px rgba(0,0,0,0.75);
+    transition-property: box-shadow, background-color;
+    transition-duration: .2s;
+    display: inline-block;
+    vertical-align: middle;
+}
+
+#dropdown_wrapper {
+    display: grid;
+    grid-template-columns: 3fr 5fr;
+}
+
 </style>
 <input name="filter_field" id="filter_field" type="hidden"/>
 <div id="grid_wrapper" class="grid_wrapper">
@@ -305,9 +348,14 @@ function add_item_prepopulate(node, prepopulate){
     div.id = "dropdown_" + dropdown_count;
     dropdown_count++;
     var label = document.createElement("H5");
-    label.style['display'] = 'inline';
     label.appendChild(document.createTextNode(node['name']));
     div.appendChild(label);
+    button = document.createElement("BUTTON");
+    button.type = "button";
+    button.appendChild(document.createTextNode("Remove"));
+    button.classList.add("btn-danger");
+    button.classList.add("btn");
+    div.appendChild(button);
     for(var i=0; i<node['forms'].length; i++){
         form = node['forms'][i];
         var input = document.createElement("INPUT");
@@ -330,14 +378,12 @@ function add_item_prepopulate(node, prepopulate){
     hiddenInput.name = "class";
     hiddenInput.value = node['id'];
     div.appendChild(hiddenInput);
-    button = document.createElement("BUTTON");
     button.onclick = function(){
         remove_dropdown(div.id);
     }
-    button.type = "button";
-    button.appendChild(document.createTextNode("Remove"));
-    div.appendChild(button);
     document.getElementById("dropdown_wrapper").appendChild(div);
+    var linebreak = document.createElement("BR");
+    document.getElementById("dropdown_wrapper").appendChild(linebreak);
     updateObjectResult(div);
     return div;
 }
diff --git a/dashboard/src/templates/resource/hostprofile_detail.html b/dashboard/src/templates/resource/hostprofile_detail.html
new file mode 100644 (file)
index 0000000..0776b9e
--- /dev/null
@@ -0,0 +1,116 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+
+{% block content %}
+<div class="row">
+    <div class="col-lg-6">
+        <div class="panel panel-default">
+            <div class="panel-heading clearfix">
+                <h4 style="display: inline;">Available at</h4>
+                <a data-toggle="collapse" data-target="#panel_overview" class="btn pull-right" style="line-height: 1;" >Expand</a>
+            </div>
+            <div class="panel-body" id="panel_overview">
+                <table class="table">
+                    <tr>
+                        <td>
+                            <ul>
+                            {% for lab in hostprofile.labs.all %}
+                                <li>{{lab.name}}</li>
+                            {% endfor %}
+                            </ul>
+                        </td>
+                    </tr>
+                </table>
+            </div>
+        </div>
+        <div class="panel panel-default">
+            <div class="panel-heading clearfix">
+                <h4 style="display: inline;">RAM</h4>
+                <a data-toggle="collapse" data-target="#panel_overview" class="btn pull-right" style="line-height: 1;" >Expand</a>
+            </div>
+            <div class="panel-body" id="panel_overview">
+                <table class="table">
+                    <tr>
+                        <td>{{hostprofile.ramprofile.first.amount}}G,
+                            {{hostprofile.ramprofile.first.channels}} channels</td>
+                    </tr>
+                </table>
+            </div>
+        </div>
+        <div class="panel panel-default">
+            <div class="panel-heading clearfix">
+                <h4 style="display: inline;">CPU</h4>
+                <a data-toggle="collapse" data-target="#panel_overview" class="btn pull-right" style="line-height: 1;" >Expand</a>
+            </div>
+            <div class="panel-body" id="panel_overview">
+                <table class="table">
+                    <tr>
+                        <td>Arch:</td>
+                        <td>{{hostprofile.cpuprofile.first.architecture}}</td>
+                    </tr>
+                    <tr>
+                        <td>Cores:</td>
+                        <td>{{hostprofile.cpuprofile.first.cores}}</td>
+                    </tr>
+                    <tr>
+                        <td>Sockets:</td>
+                        <td>{{hostprofile.cpuprofile.first.cpus}}</td>
+                    </tr>
+                </table>
+            </div>
+        </div>
+    </div>
+    <div class="col-lg-6">
+        <div class="panel panel-default">
+            <div class="panel-heading clearfix">
+                <h4 style="display: inline;">Interfaces</h4>
+                <a data-toggle="collapse" data-target="#panel_overview" class="btn pull-right" style="line-height: 1;" >Expand</a>
+            </div>
+            <div class="panel-body" id="panel_overview">
+                <table class="table">
+                            {% for intprof in hostprofile.interfaceprofile.all %}
+                            <tr>
+                                <td>
+                                <table class="table borderless">
+                                    <tr>
+                                        <td>Name:</td>
+                                        <td>{{intprof.name}}</td>
+                                    </tr>
+                                    <tr>
+                                        <td>Speed:</td>
+                                        <td>{{intprof.speed}}</td>
+                                    </tr>
+                                </table>
+                                </td>
+                            </tr>
+                            {% endfor %}
+                </table>
+            </div>
+        </div>
+    </div>
+    <div class="col-lg-6">
+        <div class="panel panel-default">
+            <div class="panel-heading clearfix">
+                <h4 style="display: inline;">Disk</h4>
+                <a data-toggle="collapse" data-target="#panel_overview" class="btn pull-right" style="line-height: 1;" >Expand</a>
+            </div>
+            <div class="panel-body" id="panel_overview">
+                <table class="table">
+                    <tr>
+                        <td>Size:</td>
+                        <td>{{hostprofile.storageprofile.first.size}} GiB</td>
+                    </tr>
+                    <tr>
+                        <td>Type:</td>
+                        <td>{{hostprofile.storageprofile.first.media_type}}</td>
+                    </tr>
+                    <tr>
+                        <td>Mount Point:</td>
+                        <td>{{hostprofile.storageprofile.first.name}}</td>
+                    </tr>
+                </table>
+            </div>
+        </div>
+    </div>
+</div>
+{% endblock content %}
index 4bf64e0..69b7231 100644 (file)
@@ -17,7 +17,7 @@
                 {{ host.name }}
             </td>
             <td>
-                {{ host.profile }}
+                <a href="profiles/{{ host.profile.id }}">{{ host.profile }}</a>
             </td>
             <td>
                 {{ host.booked }}
index b458842..7a1b56a 100644 (file)
@@ -5,6 +5,28 @@
 
 {% block content %}
 
+<style>
+#resource_meta_form {
+    margin: 80px;
+    display: grid;
+}
+
+#resource_meta_form td > * {
+    width: 100%;
+    margin-bottom: 20px;
+    margin-top: 20px;
+}
+
+#resource_meta_form > table > tbody > tr {
+    border-bottom: 1px solid #cccccc;
+}
+
+#resource_meta_form > table > tbody > tr:last-child {
+    border-bottom: none;
+}
+
+</style>
+
 <form id="resource_meta_form" method="post" action="/wf/workflow/">
     {% csrf_token %}
     <table>
index f78bc01..1329595 100644 (file)
         background: #0FD57D;
     }
 
- #viewport-iframe
- {
-     height: calc(100vh - 450);
- }
-
+    .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;
+    }
 </style>
 
 <button id="gof" onclick="go(step+1)" class="btn go_btn go_forward">Go Forward</button>
             document.getElementById("view_message").className = "step_message";
             document.getElementById("view_message").classList.add("message_" + stepstatus);
         }
-        function resize_iframe(){
-            var page_rect = document.getElementById("wrapper").getBoundingClientRect();
-            var title_rect = document.getElementById("iframe_header").getBoundingClientRect();
-            var iframe_height = page_rect.bottom - title_rect.bottom;
-            document.getElementById("viewport-iframe").height = iframe_height;
 
-        }
-     window.addEventListener('load', resize_iframe);
-     window.addEventListener('resize', resize_iframe);
     </script>
     <!-- /.col-lg-12 -->
 </div>
 </form>
 </div>
 
-<iframe src="/wf/workflow" style="position: absolute; left: 351px; right: 105px; width: calc(100% - 450px); border-style: none; border-width: 1px; border-color: #888888;" scrolling="yes" id="viewport-iframe" onload="resize_iframe();"></iframe>
+<div class="iframe_div">
+        <iframe src="/wf/workflow" class="iframe_elem" scrolling="yes" id="viewport-iframe"></iframe>
+</div>
 {% endblock content %}