diff options
-rw-r--r-- | src/api/views.py | 2 | ||||
-rw-r--r-- | src/booking/views.py | 2 | ||||
-rw-r--r-- | src/static/js/workflows/book-a-pod.js | 32 | ||||
-rw-r--r-- | src/static/js/workflows/design-a-pod.js | 42 | ||||
-rw-r--r-- | src/static/js/workflows/workflow.js | 41 | ||||
-rw-r--r-- | src/templates/base/booking/booking_detail.html | 146 | ||||
-rw-r--r-- | src/templates/base/dashboard/lab_detail.html | 6 | ||||
-rw-r--r-- | src/templates/base/dashboard/landing.html | 10 | ||||
-rw-r--r-- | src/templates/base/workflow/book_a_pod.html | 18 | ||||
-rw-r--r-- | src/templates/base/workflow/design_a_pod.html | 28 |
10 files changed, 199 insertions, 128 deletions
diff --git a/src/api/views.py b/src/api/views.py index dbe00e8..fab78ee 100644 --- a/src/api/views.py +++ b/src/api/views.py @@ -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" diff --git a/src/booking/views.py b/src/booking/views.py index 25cac43..c99e2c2 100644 --- a/src/booking/views.py +++ b/src/booking/views.py @@ -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())) } diff --git a/src/static/js/workflows/book-a-pod.js b/src/static/js/workflows/book-a-pod.js index ddea556..2396fdb 100644 --- a/src/static/js/workflows/book-a-pod.js +++ b/src/static/js/workflows/book-a-pod.js @@ -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> diff --git a/src/static/js/workflows/design-a-pod.js b/src/static/js/workflows/design-a-pod.js index 2083f7d..efec093 100644 --- a/src/static/js/workflows/design-a-pod.js +++ b/src/static/js/workflows/design-a-pod.js @@ -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) diff --git a/src/static/js/workflows/workflow.js b/src/static/js/workflows/workflow.js index 97bf8f4..97892a2 100644 --- a/src/static/js/workflows/workflow.js +++ b/src/static/js/workflows/workflow.js @@ -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', ''); } } diff --git a/src/templates/base/booking/booking_detail.html b/src/templates/base/booking/booking_detail.html index 33b0486..bcf554b 100644 --- a/src/templates/base/booking/booking_detail.html +++ b/src/templates/base/booking/booking_detail.html @@ -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 diff --git a/src/templates/base/dashboard/lab_detail.html b/src/templates/base/dashboard/lab_detail.html index cd096f6..1c15496 100644 --- a/src/templates/base/dashboard/lab_detail.html +++ b/src/templates/base/dashboard/lab_detail.html @@ -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"> diff --git a/src/templates/base/dashboard/landing.html b/src/templates/base/dashboard/landing.html index 960ad39..7f97e4f 100644 --- a/src/templates/base/dashboard/landing.html +++ b/src/templates/base/dashboard/landing.html @@ -49,13 +49,15 @@ {% 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> diff --git a/src/templates/base/workflow/book_a_pod.html b/src/templates/base/workflow/book_a_pod.html index 8a0fb47..5c1a253 100644 --- a/src/templates/base/workflow/book_a_pod.html +++ b/src/templates/base/workflow/book_a_pod.html @@ -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> @@ -25,21 +25,14 @@ <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"> diff --git a/src/templates/base/workflow/design_a_pod.html b/src/templates/base/workflow/design_a_pod.html index 32bd332..c23e5a8 100644 --- a/src/templates/base/workflow/design_a_pod.html +++ b/src/templates/base/workflow/design_a_pod.html @@ -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"> @@ -102,7 +107,8 @@ <!-- 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"> @@ -143,8 +149,10 @@ <button class="close" data-dismiss="modal"><span aria-hidden="true">×</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"> @@ -153,11 +161,11 @@ </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"> @@ -184,6 +192,12 @@ <h5 class="modal-title">Configure Connections</h5> <button class="close" data-dismiss="modal"><span aria-hidden="true">×</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> |