summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--src/account/models.py8
-rw-r--r--src/account/tasks.py5
-rw-r--r--src/account/views.py6
-rw-r--r--src/resource_inventory/models.py5
-rw-r--r--src/static/js/dashboard.js97
-rw-r--r--src/workflow/forms.py15
-rw-r--r--src/workflow/models.py2
7 files changed, 132 insertions, 6 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/account/tasks.py b/src/account/tasks.py
index fe51974..53fbaf5 100644
--- a/src/account/tasks.py
+++ b/src/account/tasks.py
@@ -26,7 +26,10 @@ def sync_jira_accounts():
except JIRAError:
# User can be anonymous (local django admin account)
continue
- user.email = user_dict['emailAddress']
+ try:
+ user.email = user_dict['emailAddress']
+ except:
+ pass
user.userprofile.url = user_dict['self']
user.userprofile.full_name = user_dict['displayName']
diff --git a/src/account/views.py b/src/account/views.py
index d1cc813..1cb2275 100644
--- a/src/account/views.py
+++ b/src/account/views.py
@@ -127,7 +127,11 @@ class JiraAuthenticatedView(RedirectView):
jira = JIRA(server=settings.JIRA_URL, oauth=oauth_dict)
username = jira.current_user()
- email = jira.user(username).emailAddress
+ email = ""
+ try:
+ email = jira.user(username).emailAddress
+ except:
+ email = ""
url = '/'
# Step 3. Lookup the user or create them if they don't exist.
try:
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 = '<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){
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
diff --git a/src/workflow/models.py b/src/workflow/models.py
index 4a5616e..f550a38 100644
--- a/src/workflow/models.py
+++ b/src/workflow/models.py
@@ -11,6 +11,7 @@
from django.template.loader import get_template
from django.http import HttpResponse
from django.utils import timezone
+from django.db import transaction
import yaml
import requests
@@ -559,6 +560,7 @@ class Repository():
self.el[self.RESULT] = bundle
return False
+ @transaction.atomic # TODO: Rewrite transactions with savepoints at user level for all workflows
def make_booking(self):
models = self.el[self.BOOKING_MODELS]
owner = self.el[self.SESSION_USER]