Quick Booking Menu Resource Availibility 57/70357/8
authorssmith <ssmith@iol.unh.edu>
Wed, 17 Jun 2020 15:44:30 +0000 (11:44 -0400)
committerssmith <ssmith@iol.unh.edu>
Tue, 23 Jun 2020 16:26:07 +0000 (12:26 -0400)
Signed-off-by: Sean Smith <ssmith@iol.unh.edu>
Change-Id: I79f411af8996698d2c553185eed0221792a348d0

src/account/models.py
src/resource_inventory/models.py
src/static/js/dashboard.js
src/workflow/forms.py

index 294e109..4aab306 100644 (file)
 
 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
 
index 557a4fc..4a6375d 100644 (file)
@@ -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
 
index 10c7d84..efc0542 100644 (file)
@@ -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 = '<br> 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){
index 4220dea..9b56f93 100644 (file)
@@ -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