diff options
author | Justin Choquette <jchoquette@iol.unh.edu> | 2023-08-08 11:33:57 -0400 |
---|---|---|
committer | Justin Choquette <jchoquette@iol.unh.edu> | 2023-08-18 11:59:01 -0400 |
commit | ecadb07367d31c0929212618e120130f54af78da (patch) | |
tree | f626ef347f6fa7cb7f9ee962539a5f769bc3d471 /src/static | |
parent | a6168306c08e8d5b207b9acc48869180d194ff01 (diff) |
MVP
Change-Id: Ib590302f49e7e66f8d04841fb6cb97baf623f51a
Signed-off-by: Justin Choquette <jchoquette@iol.unh.edu>
Diffstat (limited to 'src/static')
-rw-r--r-- | src/static/js/workflows/book-a-pod.js | 80 | ||||
-rw-r--r-- | src/static/js/workflows/common-models.js | 7 | ||||
-rw-r--r-- | src/static/js/workflows/design-a-pod.js | 51 | ||||
-rw-r--r-- | src/static/js/workflows/workflow.js | 21 |
4 files changed, 116 insertions, 43 deletions
diff --git a/src/static/js/workflows/book-a-pod.js b/src/static/js/workflows/book-a-pod.js index 3f83849..ddea556 100644 --- a/src/static/js/workflows/book-a-pod.js +++ b/src/static/js/workflows/book-a-pod.js @@ -6,13 +6,13 @@ const steps = { SELECT_TEMPLATE: 0, CLOUD_INIT: 1, BOOKING_DETAILS: 2, - ADD_COLLABS: 3, - BOOKING_SUMMARY: 4 + ADD_COLLABS: 2, + BOOKING_SUMMARY: 3 } class BookingWorkflow extends Workflow { constructor(savedBookingBlob) { - super(["select_template", "cloud_init", "booking_details" ,"add_collabs", "booking_summary"]) + super(["select_template", "cloud_init", "booking_details" , "booking_summary"]) // if (savedBookingBlob) { // this.resume_workflow() @@ -24,7 +24,12 @@ const steps = { async startWorkflow() { this.userTemplates = await LibLaaSAPI.getTemplatesForUser() // List<TemplateBlob> - GUI.displayTemplates(this.userTemplates); + const flavorsList = await LibLaaSAPI.getLabFlavors("UNH_IOL") + this.labFlavors = new Map(); // Map<UUID, FlavorBlob> + for (const fblob of flavorsList) { + this.labFlavors.set(fblob.flavor_id, fblob); + } + GUI.displayTemplates(this.userTemplates, this.labFlavors); GUI.modifyCollabWidget(); this.setEventListeners(); document.getElementById(this.sections[0]).scrollIntoView({behavior: 'smooth'}); @@ -240,28 +245,35 @@ const steps = { /** Async / await is more infectious than I thought, so all functions that rely on an API call will need to be async */ async onclickConfirm() { + // disable button + const button = document.getElementById("booking-confirm-button"); + $("html").css("cursor", "wait"); + button.setAttribute('disabled', 'true'); const complete = this.isCompleteBookingInfo(); if (!complete[0]) { - showError(complete[1]); - this.step = complete[2] - document.getElementById(this.sections[complete[2]]).scrollIntoView({behavior: 'smooth'}); + showError(complete[1], complete[2]); + $("html").css("cursor", "default"); + button.removeAttribute('disabled'); return } const response = await LibLaaSAPI.makeBooking(this.bookingBlob); + if (!response) { + showError("The selected resources for this booking are unavailable at this time. Please select a different resource or try again later.", -1) + } if (response.bookingId) { - showError("The booking has been successfully created.") - window.location.href = "../../"; + showError("The booking has been successfully created.", -1) + window.location.href = "../../booking/detail/" + response.bookingId + "/"; // todo + return; } else { - if (response.status == 406) { - showError("One or more collaborators is missing SSH keys or has not configured their IPA account.") + if (response.error == true) { + showError(response.message, -1) } else { - showError("The booking could not be created at this time.") + showError("The booking could not be created at this time.", -1) } } - // if (confirm("Are you sure you would like to create this booking?")) { - - // } + $("html").css("cursor", "default"); + button.removeAttribute('disabled'); } } @@ -288,16 +300,45 @@ class GUI { } /** Takes a list of templateBlobs and creates a selectable card for each of them */ - static displayTemplates(templates) { + static displayTemplates(templates, flavor_map) { const templates_list = document.getElementById("default_templates_list"); for (const t of templates) { - const newCard = this.makeTemplateCard(t); + const newCard = this.makeTemplateCard(t, this.calculateAvailability(t, flavor_map)); templates_list.appendChild(newCard); } } - static makeTemplateCard(templateBlob) { + static calculateAvailability(templateBlob, flavor_map) { + const local_map = new Map() + + // Map flavor uuid to amount in template + for (const host of templateBlob.host_list) { + const existing_count = local_map.get(host.flavor) + if (existing_count) { + local_map.set(host.flavor, existing_count + 1) + } else { + local_map.set(host.flavor, 1) + } + } + + let lowest_count = Number.POSITIVE_INFINITY; + for (const [key, val] of local_map) { + const curr_count = Math.floor(flavor_map.get(key).available_count / val) + if (curr_count < lowest_count) { + lowest_count = curr_count; + } + } + + return lowest_count; + } + + static makeTemplateCard(templateBlob, available_count) { + const isAvailable = available_count > 0; + let availability_text = isAvailable ? 'Resources Available' : 'Resources Unavailable'; + let color = isAvailable ? 'text-success' : 'text-danger'; + let disabled = !isAvailable ? 'disabled = "true"' : ''; + const col = document.createElement('div'); col.classList.add('col-3', 'my-1'); col.innerHTML= ` @@ -307,9 +348,10 @@ class GUI { </div> <div class="card-body"> <p class="grid-item-description">` + templateBlob.pod_desc +`</p> + <p class="grid-item-description ` + color + `">` + availability_text + `</p> </div> <div class="card-footer"> - <button type="button" class="btn btn-success grid-item-select-btn w-100 stretched-link" + <button type="button"` + disabled + ` class="btn btn-success grid-item-select-btn w-100 stretched-link" onclick="workflow.onclickSelectTemplate(this.parentNode.parentNode, '` + templateBlob.id +`')">Select</button> </div> </div> diff --git a/src/static/js/workflows/common-models.js b/src/static/js/workflows/common-models.js index 65fedb1..dc479c2 100644 --- a/src/static/js/workflows/common-models.js +++ b/src/static/js/workflows/common-models.js @@ -169,11 +169,16 @@ class FlavorBlob { this.flavor_id = incomingBlob.flavor_id; // UUID (String) this.name = incomingBlob.name; // String this.interfaces = []; // List<String> - // images are added after + this.images = []; // List<ImageBlob> + this.available_count = incomingBlob.available_count; if (incomingBlob.interfaces) { this.interfaces = incomingBlob.interfaces; } + + if (incomingBlob.images) { + this.images = incomingBlob.images; + } } } diff --git a/src/static/js/workflows/design-a-pod.js b/src/static/js/workflows/design-a-pod.js index 58f8b85..2083f7d 100644 --- a/src/static/js/workflows/design-a-pod.js +++ b/src/static/js/workflows/design-a-pod.js @@ -89,7 +89,6 @@ class DesignWorkflow extends Workflow { this.labImages = new Map(); // Map<UUID, ImageBlob> for (const fblob of flavorsList) { - fblob.images = await LibLaaSAPI.getImagesForFlavor(fblob.flavor_id); for (const iblob of fblob.images) { this.labImages.set(iblob.image_id, iblob) } @@ -110,18 +109,17 @@ class DesignWorkflow extends Workflow { this.step = steps.ADD_RESOURCES; if (this.templateBlob.lab_name == null) { - showError("Please select a lab before adding resources."); - this.goTo(steps.SELECT_LAB); + showError("Please select a lab before adding resources.", steps.SELECT_LAB); return; } if (this.templateBlob.host_list.length >= 8) { - showError("You may not add more than 8 hosts to a single pod.") + showError("You may not add more than 8 hosts to a single pod.", -1) return; } this.resourceBuilder = null; - GUI.refreshAddHostModal(this.userTemplates); + GUI.refreshAddHostModal(this.userTemplates, this.labFlavors); $("#resource_modal").modal('toggle'); } @@ -248,6 +246,7 @@ class DesignWorkflow extends Workflow { for (const [index, host] of this.resourceBuilder.user_configs.entries()) { const new_host = new HostConfigBlob(host); this.templateBlob.host_list.push(new_host); + this.labFlavors.get(host.flavor).available_count-- } // Add networks @@ -274,6 +273,7 @@ class DesignWorkflow extends Workflow { for (let existing_host of this.templateBlob.host_list) { if (hostname == existing_host.hostname) { this.removeHostFromTemplateBlob(existing_host); + this.labFlavors.get(existing_host.flavor).available_count++; GUI.refreshHostStep(this.templateBlob.host_list, this.labFlavors, this.labImages); GUI.refreshNetworkStep(this.templateBlob.networks); GUI.refreshConnectionStep(this.templateBlob.host_list); @@ -282,7 +282,7 @@ class DesignWorkflow extends Workflow { } } - showError("didnt remove"); + showError("didnt remove", -1); } @@ -297,8 +297,7 @@ class DesignWorkflow extends Workflow { this.step = steps.ADD_NETWORKS; if (this.templateBlob.lab_name == null) { - showError("Please select a lab before adding networks."); - this.goTo(steps.SELECT_LAB); + showError("Please select a lab before adding networks.", steps.SELECT_LAB); return; } @@ -561,8 +560,7 @@ class DesignWorkflow extends Workflow { this.step = steps.POD_SUMMARY; const simpleValidation = this.simpleStepValidation(); if (!simpleValidation[0]) { - showError(simpleValidation[1]) - this.goTo(simpleValidation[2]); + showError(simpleValidation[1], simpleValidation[2]) return; } @@ -632,7 +630,7 @@ class GUI { /** Resets the host modal inner html * Takes a list of templateBlobs */ - static refreshAddHostModal(template_list) { + 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"> @@ -663,13 +661,39 @@ class GUI { const template_cards = document.getElementById('template-cards'); for (let template of template_list) { - template_cards.appendChild(this.makeTemplateCard(template)); + template_cards.appendChild(this.makeTemplateCard(template, this.calculateAvailability(template, flavor_map))); } } + static calculateAvailability(templateBlob, flavor_map) { + const local_map = new Map() + + // Map flavor uuid to amount in template + for (const host of templateBlob.host_list) { + const existing_count = local_map.get(host.flavor) + if (existing_count) { + local_map.set(host.flavor, existing_count + 1) + } else { + local_map.set(host.flavor, 1) + } + } + + let lowest_count = Number.POSITIVE_INFINITY; + for (const [key, val] of local_map) { + const curr_count = Math.floor(flavor_map.get(key).available_count / val) + if (curr_count < lowest_count) { + lowest_count = curr_count; + } + } + + return lowest_count; + } + /** Makes a card to be displayed in the add resource modal for a given templateBlob */ - static makeTemplateCard(templateBlob) { + static makeTemplateCard(templateBlob, available_count) { + 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.innerHTML= ` @@ -679,6 +703,7 @@ class GUI { </div> <div class="card-body"> <p class="grid-item-description">` + templateBlob.pod_desc +`</p> + <p class="grid-item-description ` + color + `">Resources available:` + available_count +`</p> </div> <div class="card-footer"> <button type="button" class="btn btn-success grid-item-select-btn w-100 stretched-link" diff --git a/src/static/js/workflows/workflow.js b/src/static/js/workflows/workflow.js index f3f39e9..97bf8f4 100644 --- a/src/static/js/workflows/workflow.js +++ b/src/static/js/workflows/workflow.js @@ -29,7 +29,6 @@ class LibLaaSAPI { /** POSTs to dashboard, which then auths and logs the requests, makes the request to LibLaaS, and passes the result back to here. Treat this as a private function. Only use the async functions when outside of this class */ static makeRequest(method, endpoint, workflow_data) { - console.log("Making request: %s, %s, %s", method, endpoint, workflow_data.toString()) const token = document.getElementsByName('csrfmiddlewaretoken')[0].value return new Promise((resolve, reject) => {// -> HttpResponse $.ajax( @@ -130,7 +129,6 @@ class LibLaaSAPI { static async makeTemplate(templateBlob) { // -> UUID or null templateBlob.owner = user; - console.log(JSON.stringify(templateBlob)) return await this.handleResponse(this.makeRequest(HTTP.POST, endpoint.MAKE_TEMPLATE, templateBlob)); } @@ -230,12 +228,13 @@ class Workflow { } goTo(step_number) { - while (step_number > this.step) { - this.goNext(); - } - - while (step_number < this.step) { - this.goPrev(); + if (step_number < 0) return; + this.step = step_number + 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'); } } @@ -245,9 +244,11 @@ function apiError(info) { showError("Unable to fetch " + info +". Please try again later or contact support.") } -function showError(message) { +// global variable needed for a scrollintoview bug affecting chrome +let alert_destination = -1; +function showError(message, destination) { + alert_destination = destination; const text = document.getElementById('alert_modal_message'); - text.innerText = message; $("#alert_modal").modal('show'); } |