minor status changes 83/74083/2
authorJustin Choquette <jchoquette@iol.unh.edu>
Fri, 18 Aug 2023 19:50:38 +0000 (15:50 -0400)
committerJustin Choquette <jchoquette@iol.unh.edu>
Mon, 21 Aug 2023 18:35:34 +0000 (14:35 -0400)
Change-Id: Ia29c2879ddea67bdb6b30c4e871d8cb97be38d41
Signed-off-by: Justin Choquette <jchoquette@iol.unh.edu>
src/api/views.py
src/booking/views.py
src/static/js/workflows/book-a-pod.js
src/static/js/workflows/design-a-pod.js
src/static/js/workflows/workflow.js
src/templates/base/booking/booking_detail.html
src/templates/base/dashboard/lab_detail.html
src/templates/base/dashboard/landing.html
src/templates/base/workflow/book_a_pod.html
src/templates/base/workflow/design_a_pod.html

index dbe00e8..fab78ee 100644 (file)
@@ -575,7 +575,7 @@ def get_booking_status(bookingObject):
         return json.loads(response.content)
     except:
         print("failed to get status")
-        return []
+        return {}
     
 def liblaas_end_booking(aggregateId):
     liblaas_url = liblaas_base_url + "booking/" + str(aggregateId) + "/end"
index 25cac43..c99e2c2 100644 (file)
@@ -87,7 +87,7 @@ def booking_detail_view(request, booking_id):
     context = {
         'title': 'Booking Details',
         'booking': booking,
-        'statuses': statuses,
+        'status': statuses,
         'collab_string': ', '.join(map(str, booking.collaborators.all()))
     }
 
index ddea556..2396fdb 100644 (file)
@@ -64,7 +64,7 @@ const steps = {
     }
 
     onclickSelectTemplate(templateCard, templateId) {
-        this.step = steps.SELECT_TEMPLATE
+        this.goTo(steps.SELECT_TEMPLATE)
         const oldHighlight = document.querySelector("#default_templates_list .selected_node")
         if (oldHighlight) {
             GUI.unhighlightCard(oldHighlight)
@@ -90,12 +90,6 @@ const steps = {
             return[passed, message]
         }
 
-        if (!(project.match(/^[a-z0-9~@#$^*()_+=[\]{}|,.?': -]+$/i))) {
-            passed = false;
-            message = "Project field contains invalid characters"
-            return[passed, message]
-        }
-
         return [passed, message]
     }
 
@@ -109,12 +103,6 @@ const steps = {
             return[passed, message]
         }
 
-        if (!(purpose.match(/^[a-z0-9~@#$^*()_+=[\]{}|,.?': -]+$/i))) {
-            passed = false;
-            message = "Purpose field contains invalid characters"
-            return[passed, message]
-        }
-
         return [passed, message]
     }
 
@@ -129,7 +117,7 @@ const steps = {
     }
 
     onFocusInCIFile() {
-        this.step = steps.CLOUD_INIT
+        workflow.goTo(steps.CLOUD_INIT)
         const ci_textarea = document.getElementById('ci-textarea')
         GUI.unhighlightError(ci_textarea)
     }
@@ -148,7 +136,7 @@ const steps = {
     }
 
     onFocusInPurpose() {
-        this.step = steps.BOOKING_DETAILS
+        workflow.goTo(steps.BOOKING_DETAILS)
         const input = document.getElementById('input_purpose');
         GUI.hideDetailsError()
         GUI.unhighlightError(input)
@@ -168,14 +156,14 @@ const steps = {
     }
 
     onFocusInProject() {
-        this.step = steps.BOOKING_DETAILS
+        workflow.goTo(steps.BOOKING_DETAILS)
         const input = document.getElementById('input_project');
         GUI.hideDetailsError()
         GUI.unhighlightError(input)
     }
 
     onchangeDays() {
-        this.step = steps.BOOKING_DETAILS
+        workflow.goTo(steps.BOOKING_DETAILS)
         const counter = document.getElementById("booking_details_day_counter")
         const input = document.getElementById('input_length')
         workflow.bookingBlob.metadata.length = input.value
@@ -184,8 +172,7 @@ const steps = {
     }
 
     add_collaborator(username) {
-        this.step = steps.ADD_COLLABS;
-
+        workflow.goTo(steps.ADD_COLLABS)
         for (const c of this.bookingBlob.allowed_users) {
             if (c == username) {
                 return;
@@ -198,8 +185,7 @@ const steps = {
 
     remove_collaborator(username) {
         // Removes collab from collaborators list and updates summary
-        this.step = steps.ADD_COLLABS
-
+        this.goTo(steps.ADD_COLLABS)
         const temp = [];
 
         for (const c of this.bookingBlob.allowed_users) {
@@ -340,9 +326,9 @@ class GUI {
         let disabled = !isAvailable ? 'disabled = "true"' : '';
 
         const col = document.createElement('div');
-        col.classList.add('col-3', 'my-1');
+        col.classList.add('col-12', 'col-md-6', 'col-xl-3', 'my-3', 'd-flex', 'flex-grow-1');
         col.innerHTML=  `
-          <div class="card">
+          <div class="card flex-grow-1">
             <div class="card-header">
                 <p class="h5 font-weight-bold mt-2">` + templateBlob.pod_name + `</p>
             </div>
index 2083f7d..efec093 100644 (file)
@@ -68,7 +68,7 @@ class DesignWorkflow extends Workflow {
 
     /** Takes an HTML element */
     async onclickSelectLab(lab_card) {
-        this.step = steps.SELECT_LAB;
+        this.goTo(steps.SELECT_LAB)
 
         if (this.templateBlob.lab_name == null) { // Lab has not been selected yet
             this.templateBlob.lab_name = lab_card.id;
@@ -106,7 +106,7 @@ class DesignWorkflow extends Workflow {
       // Generate template cards
       // Show modal
 
-      this.step = steps.ADD_RESOURCES;
+      this.goTo(steps.ADD_RESOURCES)
 
       if (this.templateBlob.lab_name == null) {
           showError("Please select a lab before adding resources.", steps.SELECT_LAB);
@@ -269,7 +269,7 @@ class DesignWorkflow extends Workflow {
      * @param {String} hostname 
      */
     onclickDeleteHost(hostname) {
-      this.step = steps.ADD_RESOURCES;
+      this.goTo(steps.ADD_RESOURCES);
       for (let existing_host of this.templateBlob.host_list) {
         if (hostname == existing_host.hostname) {
           this.removeHostFromTemplateBlob(existing_host);
@@ -294,8 +294,7 @@ class DesignWorkflow extends Workflow {
       // Prerequisite step checks
       // GUI stuff
 
-      this.step = steps.ADD_NETWORKS;
-
+      this.goTo(steps.ADD_NETWORKS)
       if (this.templateBlob.lab_name == null) {
           showError("Please select a lab before adding networks.", steps.SELECT_LAB);
           return;
@@ -311,7 +310,7 @@ class DesignWorkflow extends Workflow {
 
     /** onclick handler for the adding_network_confirm button */
     onclickConfirmNetwork() {
-      this.step = steps.ADD_NETWORKS;
+      this.goTo(steps.ADD_NETWORKS)
 
       // Add the network
       // call the GUI to make the card (refresh the whole view to make it easier)
@@ -364,7 +363,7 @@ class DesignWorkflow extends Workflow {
      * Takes a network name as a parameter.
     */
     onclickDeleteNetwork(network_name) {
-      this.step = steps.ADD_NETWORKS;
+      this.goTo(steps.ADD_NETWORKS)
 
       for (let existing_network of this.templateBlob.networks) {
         if (network_name == existing_network.name) {
@@ -402,7 +401,7 @@ class DesignWorkflow extends Workflow {
     }
 
     onclickConfigureConnection(hostname) {
-      this.step = steps.CONFIGURE_CONNECTIONS;
+      this.goTo(steps.CONFIGURE_CONNECTIONS)
 
       const host = this.templateBlob.findHost(hostname);
       if (!host) {
@@ -450,7 +449,7 @@ class DesignWorkflow extends Workflow {
       });
 
       pod_name_input.addEventListener('focusin', (event)=> {
-        this.step = steps.POD_DETAILS;
+        this.goTo(steps.POD_DETAILS)
         GUI.unhighlightError(pod_name_input);
         GUI.hidePodDetailsError();
       });
@@ -460,7 +459,7 @@ class DesignWorkflow extends Workflow {
       });
 
       pod_desc_input.addEventListener('focusin', (event)=> {
-        this.step = steps.POD_DETAILS;
+        this.goTo(steps.POD_DETAILS)
         GUI.unhighlightError(pod_desc_input);
         GUI.hidePodDetailsError();
       });
@@ -515,9 +514,6 @@ class DesignWorkflow extends Workflow {
       else if (input.length > maxCharCount) {
         message = form_name + ' cannot exceed ' + maxCharCount + ' characters.';
         result = false;
-      } else if (!(input.match(/^[a-z0-9~@#$^*()_+=[\]{}|,.?': -!]+$/i))) {
-        message = form_name + ' contains invalid characters.';
-        result = false;
       }
 
       return [result, message]
@@ -557,7 +553,7 @@ class DesignWorkflow extends Workflow {
     }
 
     async onclickSubmitTemplate() {
-      this.step = steps.POD_SUMMARY;
+      this.goTo(steps.POD_SUMMARY)
       const simpleValidation = this.simpleStepValidation();
       if (!simpleValidation[0]) {
         showError(simpleValidation[1], simpleValidation[2])
@@ -632,8 +628,10 @@ class GUI {
     */
     static refreshAddHostModal(template_list, flavor_map) {
       document.getElementById('add_resource_modal_body').innerHTML = `
-      <h2>Resource</h2>
-      <div id="template-cards" class="row align-items-center justify-content-start">
+      <p>Select a resource, then configure the image, hostname and cloud-init (optional).</p>
+      <p>For multi-node resources, select a tab to modify each individual node.</p>
+      <h2>Resource<span class="text-danger">*</span></h2>
+      <div id="template-cards" class="row flex-grow-1">
       </div>
 
       <div id="template-config-section">
@@ -642,11 +640,11 @@ class GUI {
         </ul>
         <!-- tabs -->
         <div id="resource_config_section" hidden="true">
-          <h2>Image</h2>
+          <h2>Image<span class="text-danger">*</span></h2>
           <div id="image-cards" class="row justify-content-start align-items-center">
           </div>
           <div class="form-group">
-            <h2>Hostname</h2>
+            <h2>Hostname<span class="text-danger">*</span></h2>
             <input type="text" class="form-control" id="hostname-input" placeholder="Enter Hostname">
             <h2>Cloud Init</h2>
             <div class="d-flex justify-content-center align-items-center">
@@ -695,11 +693,11 @@ class GUI {
       let color = available_count > 0 ? 'text-success' : 'text-danger';
       // let disabled = available_count == 0 ? 'disabled = "true"' : '';
         const col = document.createElement('div');
-        col.classList.add('col-12', 'col-md-6', 'col-xl-3', 'my-3');
+        col.classList.add('col-12', 'col-md-6', 'col-xl-3', 'my-3', 'd-flex', 'flex-grow-1');
         col.innerHTML=  `
-          <div class="card" id="card-" ` + templateBlob.id + `>
+          <div class="card flex-grow-1" id="card-" ` + templateBlob.id + `>
             <div class="card-header">
-                <p class="h5 font-weight-bold mt-2">` + templateBlob.pod_name + `</p>
+                <p class="h5 font-weight-bold">` + templateBlob.pod_name + `</p>
             </div>
             <div class="card-body">
                 <p class="grid-item-description">` + templateBlob.pod_desc +`</p>
@@ -999,7 +997,7 @@ class GUI {
           outer_block.appendChild(inner_block)
           for (const c of bg.connections) {
             const connection_li = document.createElement('li');
-            connection_li.innerText = c.connects_to + `: ` + c.tagged;
+            connection_li.innerText = c.connects_to + `: ` + (c.tagged == true ? "tagged" : "untagged");
             inner_block.appendChild(connection_li);
           }
           bondgroup_list.appendChild(outer_block)
index 97bf8f4..97892a2 100644 (file)
@@ -196,45 +196,28 @@ class Workflow {
      * Enables the next button if the step is less than sections.length after executing
     */
     goPrev() {
-
-        if (workflow.step <= 0) {
-            return;
-        }
-
-        this.step--;
-
-        document.getElementById(this.sections[this.step]).scrollIntoView({behavior: 'smooth'});
-
-        if (this.step == 0) {
-            document.getElementById('prev').setAttribute('disabled', '');
-        } else if (this.step == this.sections.length - 2) {
-            document.getElementById('next').removeAttribute('disabled');
-        }
+        this.goTo(this.step -1);
     }
 
     goNext() {
-        if (this.step >= this.sections.length - 1 ) {
-            return;
-        }
-
-        this.step++;
-        document.getElementById(this.sections[this.step]).scrollIntoView({behavior: 'smooth'});
-
-        if (this.step == this.sections.length - 1) {
-            document.getElementById('next').setAttribute('disabled', '');
-        } else if (this.step == 1) {
-            document.getElementById('prev').removeAttribute('disabled');
-        }
+        this.goTo(this.step + 1);
     }
 
     goTo(step_number) {
         if (step_number < 0) return;
         this.step = step_number
+        document.getElementById('workflow-next').removeAttribute('hidden');
+        document.getElementById('workflow-prev').removeAttribute('hidden');
+
         document.getElementById(this.sections[this.step]).scrollIntoView({behavior: 'smooth'});
+
+        if (this.step == 0) {
+            document.getElementById('workflow-prev').setAttribute('hidden', '');
+
+        }
+
         if (this.step == this.sections.length - 1) {
-            document.getElementById('next').setAttribute('disabled', '');
-        } else if (this.step == 1) {
-            document.getElementById('prev').removeAttribute('disabled');
+            document.getElementById('workflow-next').setAttribute('hidden', '');
         }
     }
 
index 33b0486..bcf554b 100644 (file)
@@ -53,6 +53,14 @@ code {
                         <td>Lab Deployed At</td>
                         <td>{{ booking.lab }}</td>
                     </tr>
+                    <tr>
+                        <td>IPMI Username</td>
+                        <td><code id="ipmi-username">{{ status.config.ipmi_username }}</code></td>
+                    </tr>
+                    <tr>
+                        <td>IPMI Password</td>
+                        <td><code id="ipmi-password">{{ status.config.ipmi_password }}</code></td>
+                    </tr>
                 </table>
             </div>
         </div>
@@ -61,35 +69,80 @@ code {
         <div class="card mb-3">
             <div class="card-header d-flex">
                 <h4 class="d-inline">Deployment Progress</h4>
-                <p class="mx-3">Your resources are being prepared. If this is taking a really long time, please contact us <a href="mailto:{{contact_email}}">here!</a></p>
-                <!-- <button data-toggle="collapse" data-target="#panel_tasks" class="btn btn-outline-secondary ml-auto">Expand</button> -->
+                <p class="mx-3">Your resources are being prepared. If this is taking a really long time, please contact
+                    us <a href="mailto:{{contact_email}}">here!</a></p>
             </div>
             <div class="collapse show" id="panel_tasks">
                 <table class="table m-0">
                     <tr>
                         <th></th>
-                        <th>Resource</th>
+                        <th>Name</th>
+
+                        <th>Assigned Host</th>
                         <th>Status</th>
                     </tr>
-                    {% for host in statuses %}
+                    {% with status.instances as instances %}
+
+                    {% for id, inst in instances.items %}
                     <tr>
                         <td>
-                            {% if 'Success' in host.status  %}
-                            <div class="rounded-circle bg-success square-20"></div>
-                            {% elif 'Fail' in host.status %}
-                            <div class="rounded-circle bg-danger square-20"></div>
+                            <!-- icon -->
+                            {% if inst.logs|length > 0 %}
+                            {% with inst.logs|last as lastelem %}
+                            {% if 'Success' in lastelem.status  %}
+                            <div id="icon-{{id}}" class="rounded-circle bg-success square-20"></div>
+                            {% elif 'Fail' in lastelem.status %}
+                            <div id="icon-{{id}}" class="rounded-circle bg-danger square-20"></div>
                             {% else %}
-                            <div class="spinner-border text-primary square-20"></div>
+                            <div id="icon-{{id}}" class="spinner-border text-primary square-20"></div>
+                            {% endif %}
+                            {% endwith %}
                             {% endif %}
                         </td>
-                        <td>
-                            {{ host.hostname }}
+                        <td id="alias-{{id}}">
+                            <!-- Hostname -->
+                            {{inst.host_alias}}
+                        </td>
+                        <td id="host-{{id}}">
+                            <!-- Actual Host -->
+                            {{inst.assigned_host}}
                         </td>
-                        <td id="{{host.instance_id}}">
-                            {{ host.status }}
+                        <td>
+                            <!-- Logs -->
+                            {% if inst.logs|length > 0 %}
+                            {% with inst.logs|last as lastelem %}
+                            <span id="status-{{id}}">
+                            {{lastelem.status}}
+                            </span>
+                                <button class="btn-secondary btn float-right" data-target="#collapse-{{id}}"
+                                    data-toggle="collapse">Show Additional Logs</button>
+                            {% endwith %}
+
+                            {% endif %}
+                            <p>
+                            <div class="card collapse mt-3" id="collapse-{{id}}">
+                                <div class="card-header">Additional Logs</div>
+                                <div class="card-body">
+                                    <table id="logs-{{id}}">
+                                        {% for log in inst.logs %}
+                                        <tr>
+                                            <td>
+                                                {{log.status}}
+                                            </td>
+
+                                            <td>
+                                                {{log.time}}
+                                            </td>
+                                        </tr>
+                                        {% endfor %}
+                                    </table>
+                                </div>
+                            </div>
+                            </p>
                         </td>
                     </tr>
                     {% endfor %}
+                    {% endwith %}
                 </table>
             </div>
         </div>
@@ -116,30 +169,67 @@ code {
 </div>
 
 <script>
-setInterval(function(){
-   fetchBookingStatus();
-}, 5000);
+    setInterval(function () {
+        fetchBookingStatus();
+    }, 5000);
 
 
-async function fetchBookingStatus() {
+    async function fetchBookingStatus() {
         req = new XMLHttpRequest();
         var url = "status";
         req.open("GET", url, true);
-        req.onerror = function() { alert("oops"); }
-        req.onreadystatechange = function() {
-            if(req.readyState === 4) {
-            statuses = JSON.parse(req.responseText)
-            updateStatuses(statuses)
+        req.onerror = function () { console.log("failed to get status") }
+        req.onreadystatechange = function () {
+            if (req.readyState === 4) {
+                let status = JSON.parse(req.responseText)
+                updateStatuses(status)
             }
         }
         req.send();
-}
+    }
+
+    async function updateStatuses(status) {
+
+        const instances = status.instances;
+        
+        Object.keys(instances).forEach((aggId) => {
+            const instance = instances[aggId]
+            const status = instance.logs.pop()
 
-async function updateStatuses(statuses) {
-    for (const s of statuses) {
-        document.getElementById(s.instance_id).innerText = s.status
+            let icon_class = "spinner-border text-primary square-20"
+            if (status.status.includes('Success')) {
+                icon_class = "rounded-circle bg-success square-20"
+            } else if (status.status.includes('Fail')) {
+                icon_class = "rounded-circle bg-danger square-20"
+            }
+
+            // icon
+            document.getElementById("icon-" + aggId).className = icon_class;
+            // host alias
+            document.getElementById("alias-" + aggId).innerText = instance.host_alias;
+            // assigned host
+            document.getElementById("host-" + aggId).innerText = instance.assigned_host;
+            // status
+            document.getElementById("status-" + aggId).innerText = status.status;
+            // logs
+            const log_table = document.getElementById("logs-" + aggId);
+            log_table.innerHTML = "";
+            for (const log of instance.logs) {
+                const tr = document.createElement('tr');
+                const td_status = document.createElement('td')
+                td_status.innerText = log.status;
+                const td_time = document.createElement('td')
+                td_time.innerText = log.time;
+                tr.appendChild(td_status)
+                tr.appendChild(td_time)
+                log_table.appendChild(tr)
+            }
+        })
+
+        // IPMI
+        document.getElementById("ipmi-username").innerText = status.config.ipmi_username;
+        document.getElementById("ipmi-password").innerText = status.config.ipmi_password;
     }
-}
 </script>
 
-{% endblock content %}
+{% endblock content %}
\ No newline at end of file
index cd096f6..1c15496 100644 (file)
@@ -57,7 +57,9 @@
                 </div>
             </div>
         </div>
-        <div class="card my-3">
+
+        <!-- Needs to stay commented until we can filter profiles by project. Currently this is showing Anuket and LFEdge profiles. -->
+        <!-- <div class="card my-3">
             <div class="card-header d-flex">
                 <h4 class="d-inline-block">Host Profiles</h4>
             </div>
@@ -74,7 +76,7 @@
                     </table>
                 </div>
             </div>
-        </div>
+        </div> -->
 
         <div class="card my-3">
             <div class="card-header d-flex">
index 960ad39..7f97e4f 100644 (file)
         {% endif %}
         {% else %}
         {% block btnGrp %}
-        <p>To get started, book a server below:</p>
-        <a class="btn btn-primary btn-lg d-flex flex-column justify-content-center align-content-center border p-4 btnAnuket" href="{% url 'workflow:book_a_pod' %}" >
-            Book a Pod
-        </a>
+        <p>Select 'Design a Pod' to create a custom resource group.</p>
+        <p>Select 'Book a Pod' to reserve a custom pod or a single resource.</p>
         <a class="btn btn-primary btn-lg d-flex flex-column justify-content-center align-content-center border p-4 btnAnuket" href="{% url 'workflow:design_a_pod' %}" >
             Design a Pod
         </a>
+        <a class="btn btn-primary btn-lg d-flex flex-column justify-content-center align-content-center border p-4 btnAnuket" href="{% url 'workflow:book_a_pod' %}" >
+            Book a Pod
+        </a>
+
         {% endblock btnGrp %}
         {% endif %}
     </div>
index 8a0fb47..5c1a253 100644 (file)
@@ -10,7 +10,7 @@
 <body>
 <div class="workflow-container">
     <div id="prev" class="row w-100 m-0">
-        <button class="btn btn-workflow-nav stretched-link m-0 p-0 mt-3" onclick="workflow.goPrev()" id="workflow-prev">
+        <button class="btn btn-workflow-nav stretched-link m-0 p-0 mt-3" hidden="true" onclick="workflow.goPrev()" id="workflow-prev">
           <div class="arrow arrow-up"></div>
         </button>
       </div>
         <div class="scroll-area pt-5 mx-5" id="select_template">
             <h1 class="mt-4"><u>Book a Pod</u></h1>
             <h2 class="mt-4 mb-3">Select Host Or Template<span class="text-danger">*</span></h2>
-            <div class="card-deck align-items-center">
-      
-              <div class="col-12" id="template_list">
-      
-                <div class="my-5" id="select_template_tab_content">
-                    <ul id="default_templates_list" class="p-0 m-0 row">
-                    </ul>
-                </div>
-      
+            <p>Select the resource bundle that you would like to reserve. Then use the navigation arrows or scroll to advance the workflow. Configure your own resource <a href="{% url 'workflow:design_a_pod' %}">here</a>.</p> 
+              <div id="default_templates_list" class="row flex-grow-1">
               </div>
-            </div>
         </div>
 
         <div class="scroll-area pt-5 mx-5" id="cloud_init">
             <h2 class="mt-4 mb-3">Global Cloud Init Override</h2>
+            <p>Add a custom cloud init configuration to apply to all hosts in your booking (optional).</p>
             <div class="d-flex align-items-center">
               <textarea name="ci-textarea" id="ci-textarea" rows="15" class="w-50"></textarea>
             </div>
@@ -47,6 +40,7 @@
 
         <div class="scroll-area pt-5 mx-5" id="booking_details">
             <h2 class="mt-4 mb-3">Booking Details<span class="text-danger">*</span></h2>
+            <p>Enter the project and purpose for your booking, as well as the duration (up to 21 days).</p>
             <div class="form-group mb-0">
               <div class="row align-items-center my-4">
                 <div class="col-xl-6 col-md-8 col-11">
@@ -67,6 +61,7 @@
               </div>
             </div>
             <h2 class="mt-4 mb-3">Add Collaborators:</h2>
+            <p>Give SSH and booking overview access to other users. Collaborators must mark their profiles as public and have a linked IPA account.</p>
             <div class="row">
               <div class="col-xl-6 col-md-8 col-11 p-0 border border-dark">
                 {% bootstrap_field form.users label="Collaborators" %}
@@ -76,6 +71,7 @@
 
         <div class="scroll-area pt-5 mx-5" id="booking_summary">
             <h2 class="mt-4 mb-3">Booking Summary</h2>
+            <p>Review your booking information and click 'Book' to reserve your resources.</p>
             <div class="row align-items-center">
               <div class="card col-xl-6 col-md-8 col-11 border-0">
                 <ul class="list-group">
index 32bd332..c23e5a8 100644 (file)
@@ -14,7 +14,7 @@
 
   <div class="workflow-container">
     <div id="prev" class="row w-100 m-0">
-      <button class="btn btn-workflow-nav stretched-link m-0 p-0 mt-3" onclick="workflow.goPrev()" id="workflow-prev">
+      <button class="btn btn-workflow-nav stretched-link m-0 p-0 mt-3" hidden="true" onclick="workflow.goPrev()" id="workflow-prev">
         <div class="arrow arrow-up"></div>
       </button>
     </div>
@@ -30,7 +30,8 @@
       <!-- Select Lab -->
       <div class="scroll-area pt-5 mx-5" id="select_lab">
         <!-- Ideally the 'Design a Pod' header would be anchored to the top of the page below the arrow -->
-        <h1 class="mt-4"><u>Design a Pod</u></h1> 
+        <h1 class="mt-4"><u>Design a Pod</u></h1>
+        <p>To get started, select a lab. Then use the navigation arrows or scroll to advance through the workflow.</p>
         <h2 class="mt-4 mb-3">Select a Lab<span class="text-danger">*</span></h2>
         <div class="row card-deck" id="lab_cards">
         </div>
@@ -39,6 +40,7 @@
       <!-- Add Resources -->
       <div class="scroll-area pt-5 mx-5" id="add_resources">
         <h2 class="mt-4 mb-3">Add Resources<span class="text-danger">*</span></h2>
+        <p>Add up to 8 configurable resources to your pod, then use the navigation arrows to proceed to the next step.</p>
         <div class="row card-deck align-items-center" id="host_cards">
           <div class="col-xl-3 col-md-6 col-12" id="add_resource_plus_card">
             <div class="card align-items-center border-0">
@@ -52,6 +54,7 @@
       <!-- Add Networks --> 
     <div class="scroll-area pt-5 mx-5" id="add_networks">
       <h2 class="mt-4 mb-3">Add Networks<span class="text-danger">*</span></h2>
+      <p>Define networks to use in your pod. A network may be set as public or private.</p>
         <div class="row card-deck align-items-center" id="network_card_deck">
           <div class="col-xl-3 col-md-6 col-12" id="add_network_plus_card">
             <div class="card align-items-center border-0">
@@ -65,6 +68,7 @@
    <!-- Configure Connections-->
    <div class="scroll-area pt-5 mx-5" id="configure_connections">
       <h2 class="mt-4 mb-3">Configure Connections<span class="text-danger">*</span></h2>
+      <p>Configure the connections for each host in your pod to your defined networks.</p>
       <div class="row card-deck align-items-center" id="connection_cards">
       </div>
   </div>
@@ -72,6 +76,7 @@
   <!-- Pod Details-->
   <div class="scroll-area pt-5 mx-5" id="pod_details">
     <h2 class="mt-4 mb-3">Pod Details<span class="text-danger">*</span></h2>
+    <p>Add a pod name and description to refer to later.</p>
     <div class="form-group">
       <div class="row align-items-center my-4">
         <div class="col-xl-6 col-md-8 col-11">
 
   <!-- Pod Summary-->
   <div class="scroll-area pt-5 mx-5" id="pod_summary">
-    <h2 class="mt-4 mb-3">Pod Summary:</h2>
+    <h2 class="mt-4 mb-3">Pod Summary</h2>
+    <p>Confirm the specifications below, and select 'create' to save this pod. Otherwise, navigate upwards and modify it as needed.</p>
     <div class="row align-items-center">
       <div class="col-xl-6 col-md-8 col-11">
         <div class="card border-0">
           <button class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span></button>
         </div>
         <div class="modal-body" id="add_resource_modal_body">
-          <h2>Resource</h2>
-          <div id="template-cards" class="row align-items-center justify-content-start">
+          <p>Select a resource, then configure the image, hostname and cloud-init (optional).</p>
+          <p>For multi-node resources, select a tab to modify each individual node.</p>
+          <h2>Resource<span class="text-danger">*</span></h2>
+          <div id="template-cards" class="row flex-grow-1">
           </div>
 
           <div id="template-config-section">
             </ul>
             <!-- tabs -->
             <div id="resource_config_section">
-              <h2>Image</h2>
+              <h2>Image<span class="text-danger">*</span></h2>
               <div id="image-cards" class="row justify-content-start align-items-center">
               </div>
               <div class="form-group">
-                <h2>Hostname</h2>
+                <h2>Hostname<span class="text-danger">*</span></h2>
                 <input type="text" class="form-control" id="hostname-input" placeholder="Enter Hostname">
                 <h2>Cloud Init</h2>
                 <div class="d-flex justify-content-center align-items-center">
         <h5 class="modal-title">Configure Connections</h5>
         <button class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span></button>
       </div>
+      <ul>
+        <li>Select an interface and a frame type to add a connection to a network.</li>
+        <li>An interface may send tagged or untagged frames on a single network, but not both.</li>
+        <li>Each interface may only be untagged on one network.</li>
+        <li>Reselect a connection to remove it.</li>
+      </ul>
       <div class="modal-body text-center">
         <ul class="nav nav-tabs" role="tablist" id="configure-connections-tablist">
         </ul>