From ecadb07367d31c0929212618e120130f54af78da Mon Sep 17 00:00:00 2001 From: Justin Choquette Date: Tue, 8 Aug 2023 11:33:57 -0400 Subject: MVP Change-Id: Ib590302f49e7e66f8d04841fb6cb97baf623f51a Signed-off-by: Justin Choquette --- src/static/js/workflows/book-a-pod.js | 80 ++++++++++++++++++++++++-------- src/static/js/workflows/common-models.js | 7 ++- src/static/js/workflows/design-a-pod.js | 51 ++++++++++++++------ src/static/js/workflows/workflow.js | 21 +++++---- 4 files changed, 116 insertions(+), 43 deletions(-) (limited to 'src/static') 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 - GUI.displayTemplates(this.userTemplates); + const flavorsList = await LibLaaSAPI.getLabFlavors("UNH_IOL") + this.labFlavors = new Map(); // Map + 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 {

` + templateBlob.pod_desc +`

+

` + availability_text + `

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 - // images are added after + this.images = []; // List + 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 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 = `

Resource

@@ -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 {

` + templateBlob.pod_desc +`

+

Resources available:` + available_count +`