From 03f767a2833293b7a0412eba9dce438a4b074b87 Mon Sep 17 00:00:00 2001 From: ssmith Date: Wed, 17 Jun 2020 11:44:30 -0400 Subject: [PATCH] Quick Booking Menu Resource Availibility Signed-off-by: Sean Smith Change-Id: I79f411af8996698d2c553185eed0221792a348d0 --- src/account/models.py | 8 ++++ src/resource_inventory/models.py | 5 +++ src/static/js/dashboard.js | 97 +++++++++++++++++++++++++++++++++++++++- src/workflow/forms.py | 15 +++++-- 4 files changed, 121 insertions(+), 4 deletions(-) diff --git a/src/account/models.py b/src/account/models.py index 294e109..4aab306 100644 --- a/src/account/models.py +++ b/src/account/models.py @@ -10,9 +10,11 @@ from django.contrib.auth.models import User from django.db import models +from django.apps import apps import json import random +from collections import Counter class LabStatus(object): """ @@ -212,6 +214,12 @@ class Lab(models.Model): key += random.choice(alphabet) return key + def get_available_resources(self): + # Cannot import model normally due to ciruclar import + Server = apps.get_model('resource_inventory', 'Server') # TODO: Find way to import ResourceQuery + resources = [str(resource.profile) for resource in Server.objects.filter(lab=self, booked=False)] + return dict(Counter(resources)) + def __str__(self): return self.name diff --git a/src/resource_inventory/models.py b/src/resource_inventory/models.py index 557a4fc..4a6375d 100644 --- a/src/resource_inventory/models.py +++ b/src/resource_inventory/models.py @@ -14,6 +14,7 @@ from django.db import models from django.db.models import Q import re +from collections import Counter from account.models import Lab from dashboard.utils import AbstractModelQuery @@ -169,6 +170,10 @@ class ResourceTemplate(models.Model): configs = self.resourceConfigurations.all() return list(configs) + def get_required_resources(self): + profiles = Counter([str(config.profile) for config in self.getConfigs()]) + return dict(profiles) + def __str__(self): return self.name diff --git a/src/static/js/dashboard.js b/src/static/js/dashboard.js index 10c7d84..efc0542 100644 --- a/src/static/js/dashboard.js +++ b/src/static/js/dashboard.js @@ -209,6 +209,8 @@ class MultipleSelectFilterWidget { this.inputs = []; this.graph_neighbors = neighbors; this.filter_items = items; + this.currentLab = null; + this.available_resources = {}; this.result = {}; this.dropdown_count = 0; @@ -220,7 +222,7 @@ class MultipleSelectFilterWidget { this.make_selection(initial); } - make_selection( initial_data ){ + make_selection(initial_data){ if(!initial_data || jQuery.isEmptyObject(initial_data)) return; for(let item_class in initial_data) { @@ -257,9 +259,11 @@ class MultipleSelectFilterWidget { const toCheck = [root]; while(toCheck.length > 0){ const node = toCheck.pop(); + if(!node['marked']) { continue; //already visited, just continue } + node['marked'] = false; //mark as visited if(node['follow'] || node == root){ //add neighbors if we want to follow this node const neighbors = this.graph_neighbors[node.id]; @@ -305,6 +309,10 @@ class MultipleSelectFilterWidget { node['selected'] = true; elem.classList.remove('bg-white', 'not-allowed', 'bg-light'); elem.classList.add('selected_node'); + + if(node['class'] == 'resource') + this.reserveResource(node); + } clear(node) { @@ -323,11 +331,96 @@ class MultipleSelectFilterWidget { elem.classList.add('not-allowed', 'bg-light'); } + labCheck(node){ + // if lab is not already selected update available resources + if(!node['selected']) { + this.currentLab = node; + this.available_resources = JSON.parse(node['available_resources']); + this.updateAvailibility(); + } else { + // a lab is already selected, clear already selected resources + if(confirm('Unselecting a lab will reset all selected resources, are you sure?')) + location.reload(); + } + } + + updateAvailibility() { + const lab_resources = this.graph_neighbors[this.currentLab.id]; + + // need to loop through and update all quantities + for(let i in lab_resources) { + const resource_node = this.filter_items[lab_resources[i]]; + const required_resources = JSON.parse(resource_node['required_resources']); + let elem = document.getElementById(resource_node.id).getElementsByClassName("grid-item-description")[0]; + let leastAvailable = 100; + let currCount; + let quantityDescription; + let quantityNode; + + // console.log(this.available_resources); + for(let resource in required_resources) { + currCount = Math.floor(this.available_resources[resource] / required_resources[resource]); + if(currCount < leastAvailable) + leastAvailable = currCount; + + if(!currCount || currCount < 0) { + leastAvailable = 0 + break; + } + } + + if (elem.children[0]){ + elem.removeChild(elem.children[0]); + } + + quantityDescription = '
Quantity Currently Available: ' + leastAvailable; + quantityNode = document.createElement('P'); + if (leastAvailable > 0) { + quantityDescription = quantityDescription.fontcolor('green'); + } else { + quantityDescription = quantityDescription.fontcolor('red'); + } + + quantityNode.innerHTML = quantityDescription; + elem.appendChild(quantityNode) + } + } + + reserveResource(node){ + const required_resources = JSON.parse(node['required_resources']); + + for(let resource in required_resources){ + this.available_resources[resource] -= required_resources[resource]; + } + + this.updateAvailibility(); + } + + releaseResource(node){ + const required_resources = JSON.parse(node['required_resources']); + + for(let resource in required_resources){ + this.available_resources[resource] += required_resources[resource]; + } + + this.updateAvailibility(); + } + processClick(id){ const node = this.filter_items[id]; if(!node['selectable']) return; + // If they are selecting a lab, update accordingly + if (node['class'] == 'lab') + this.labCheck(node); + + // Can only select a resource if a lab is selected + if (!this.currentLab) { + alert('You must select a lab before selecting a resource'); + return; + } + if(node['multiple']){ return this.processClickMultiple(node); } else { @@ -341,6 +434,7 @@ class MultipleSelectFilterWidget { this.select(node); } else { this.clear(node); + this.releaseResource(node); // can't do this in clear since clear removes border } this.process(node); this.updateResult(node); @@ -423,6 +517,7 @@ class MultipleSelectFilterWidget { const parent = div.parentNode; div.parentNode.removeChild(div); this.result[node.class][node.id]['count']--; + this.releaseResource(node); // This can't be done on clear b/c clear removes border //checks if we have removed last item in class if(this.result[node.class][node.id]['count'] == 0){ diff --git a/src/workflow/forms.py b/src/workflow/forms.py index 4220dea..9b56f93 100644 --- a/src/workflow/forms.py +++ b/src/workflow/forms.py @@ -1,5 +1,6 @@ ############################################################################## # Copyright (c) 2018 Sawyer Bergeron, Parker Berberian, and others. +# Copyright (c) 2020 Sawyer Bergeron, Sean Smith, and others. # # All rights reserved. This program and the accompanying materials # are made available under the terms of the Apache License, Version 2.0 @@ -22,7 +23,7 @@ from account.models import UserProfile from resource_inventory.models import ( OPNFVRole, Installer, - Scenario, + Scenario ) from resource_inventory.resource_manager import ResourceManager from booking.lib import get_user_items, get_user_field_opts @@ -314,8 +315,10 @@ class FormUtils: 'selectable': true, 'follow': multiple_hosts, 'multiple': false, - 'class': 'lab' + 'class': 'lab', + 'available_resources': json.dumps(lab.get_available_resources()) } + items[lab_node['id']] = lab_node neighbors[lab_node['id']] = [] labs[lab_node['id']] = lab_node @@ -331,14 +334,19 @@ class FormUtils: 'selectable': true, 'follow': false, 'multiple': multiple_hosts, - 'class': 'resource' + 'class': 'resource', + 'required_resources': json.dumps(template.get_required_resources()) } + if multiple_hosts: resource_node['values'] = [] # place to store multiple values + items[resource_node['id']] = resource_node neighbors[lab_node['id']].append(resource_node['id']) + if resource_node['id'] not in neighbors: neighbors[resource_node['id']] = [] + neighbors[resource_node['id']].append(lab_node['id']) resources[resource_node['id']] = resource_node @@ -349,6 +357,7 @@ class FormUtils: 'neighbors': neighbors, 'filter_items': items } + return context -- 2.16.6