aboutsummaryrefslogtreecommitdiffstats
path: root/src/static/js/workflows
diff options
context:
space:
mode:
authorJustin Choquette <jchoquette@iol.unh.edu>2023-08-08 11:33:57 -0400
committerJustin Choquette <jchoquette@iol.unh.edu>2023-08-18 11:59:01 -0400
commitecadb07367d31c0929212618e120130f54af78da (patch)
treef626ef347f6fa7cb7f9ee962539a5f769bc3d471 /src/static/js/workflows
parenta6168306c08e8d5b207b9acc48869180d194ff01 (diff)
MVP
Change-Id: Ib590302f49e7e66f8d04841fb6cb97baf623f51a Signed-off-by: Justin Choquette <jchoquette@iol.unh.edu>
Diffstat (limited to 'src/static/js/workflows')
-rw-r--r--src/static/js/workflows/book-a-pod.js80
-rw-r--r--src/static/js/workflows/common-models.js7
-rw-r--r--src/static/js/workflows/design-a-pod.js51
-rw-r--r--src/static/js/workflows/workflow.js21
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');
}