aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJustin Choquette <jchoquette@iol.unh.edu>2023-08-07 14:10:19 -0400
committerJustin Choquette <jchoquette@iol.unh.edu>2023-08-07 14:16:04 -0400
commita6168306c08e8d5b207b9acc48869180d194ff01 (patch)
tree51ffcafac4ae0b5fd4da363d9bf839e8ad3fc286
parenta09db9f287a02873c0226759f8ea444bb304cd59 (diff)
User subsystem
Change-Id: Ibef4ede9b2d6a3ea465f79a9b5cbcc821afbccae Signed-off-by: Justin Choquette <jchoquette@iol.unh.edu>
-rw-r--r--src/account/forms.py12
-rw-r--r--src/account/migrations/0011_userprofile_ipa_username.py18
-rw-r--r--src/account/migrations/0012_auto_20230725_1749.py18
-rw-r--r--src/account/migrations/0013_auto_20230727_1903.py29
-rw-r--r--src/account/models.py6
-rw-r--r--src/account/urls.py6
-rw-r--r--src/account/views.py75
-rw-r--r--src/api/urls.py10
-rw-r--r--src/api/utils.py122
-rw-r--r--src/api/views.py118
-rw-r--r--src/booking/migrations/0012_auto_20230802_1810.py18
-rw-r--r--src/booking/models.py2
-rw-r--r--src/dashboard/forms.py46
-rw-r--r--src/dashboard/views.py53
-rw-r--r--src/static/js/workflows/book-a-pod.js35
-rw-r--r--src/static/js/workflows/design-a-pod.js59
-rw-r--r--src/static/js/workflows/workflow.js9
-rw-r--r--src/templates/base/account/settings.html88
-rw-r--r--src/templates/base/account/user_list.html55
-rw-r--r--src/templates/base/dashboard/landing.html45
-rw-r--r--src/templates/base/workflow/book_a_pod.html57
-rw-r--r--src/templates/base/workflow/design_a_pod.html23
-rw-r--r--src/workflow/README1
-rw-r--r--src/workflow/views.py11
24 files changed, 721 insertions, 195 deletions
diff --git a/src/account/forms.py b/src/account/forms.py
index 28cb27d..8bacc24 100644
--- a/src/account/forms.py
+++ b/src/account/forms.py
@@ -15,15 +15,11 @@ from django.utils.translation import gettext_lazy as _
from account.models import UserProfile
-class AccountSettingsForm(forms.ModelForm):
+class AccountPreferencesForm(forms.ModelForm):
class Meta:
model = UserProfile
- fields = ['company', 'email_addr', 'public_user', 'ssh_public_key', 'pgp_public_key', 'timezone']
+ fields = ['timezone', 'public_user']
labels = {
- 'email_addr': _('Email Address'),
- 'ssh_public_key': _('SSH Public Key'),
- 'pgp_public_key': _('PGP Public Key'),
- 'public_user': _('Public User')
}
-
- timezone = forms.ChoiceField(choices=[(x, x) for x in pytz.common_timezones], initial='UTC')
+ timezone = forms.ChoiceField(widget=forms.Select(attrs={'style': 'width: 200px;', 'class': 'form-control'}) ,choices=[(x, x) for x in pytz.common_timezones], initial='UTC')
+ public_user = forms.BooleanField(required=False, widget=forms.CheckboxInput(attrs={})) \ No newline at end of file
diff --git a/src/account/migrations/0011_userprofile_ipa_username.py b/src/account/migrations/0011_userprofile_ipa_username.py
new file mode 100644
index 0000000..25cf6fb
--- /dev/null
+++ b/src/account/migrations/0011_userprofile_ipa_username.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2 on 2023-07-24 20:40
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('account', '0010_auto_20230608_1913'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='userprofile',
+ name='ipa_username',
+ field=models.CharField(max_length=100, null=True),
+ ),
+ ]
diff --git a/src/account/migrations/0012_auto_20230725_1749.py b/src/account/migrations/0012_auto_20230725_1749.py
new file mode 100644
index 0000000..3f4d142
--- /dev/null
+++ b/src/account/migrations/0012_auto_20230725_1749.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2 on 2023-07-25 17:49
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('account', '0011_userprofile_ipa_username'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='userprofile',
+ name='ipa_username',
+ field=models.CharField(blank=True, max_length=100, null=True),
+ ),
+ ]
diff --git a/src/account/migrations/0013_auto_20230727_1903.py b/src/account/migrations/0013_auto_20230727_1903.py
new file mode 100644
index 0000000..9e1d222
--- /dev/null
+++ b/src/account/migrations/0013_auto_20230727_1903.py
@@ -0,0 +1,29 @@
+# Generated by Django 2.2 on 2023-07-27 19:03
+
+from django.db import migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('account', '0012_auto_20230725_1749'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='userprofile',
+ name='company',
+ ),
+ migrations.RemoveField(
+ model_name='userprofile',
+ name='jira_url',
+ ),
+ migrations.RemoveField(
+ model_name='userprofile',
+ name='pgp_public_key',
+ ),
+ migrations.RemoveField(
+ model_name='userprofile',
+ name='ssh_public_key',
+ ),
+ ]
diff --git a/src/account/models.py b/src/account/models.py
index f1deca7..bb1cad5 100644
--- a/src/account/models.py
+++ b/src/account/models.py
@@ -39,20 +39,16 @@ class UserProfile(models.Model):
user = models.OneToOneField(User, on_delete=models.CASCADE)
timezone = models.CharField(max_length=100, blank=False, default='UTC')
- ssh_public_key = models.FileField(upload_to=upload_to, null=True, blank=True)
- pgp_public_key = models.FileField(upload_to=upload_to, null=True, blank=True)
email_addr = models.CharField(max_length=300, blank=False, default='email@mail.com')
- company = models.CharField(max_length=200, blank=False)
oauth_token = models.CharField(max_length=1024, blank=False)
oauth_secret = models.CharField(max_length=1024, blank=False)
- jira_url = models.CharField(max_length=100, null=True, blank=True, default='')
-
full_name = models.CharField(max_length=100, null=True, blank=True, default='')
booking_privledge = models.BooleanField(default=False)
public_user = models.BooleanField(default=False)
+ ipa_username = models.CharField(max_length=100, null=True, blank=True)
class Meta:
db_table = 'user_profile'
diff --git a/src/account/urls.py b/src/account/urls.py
index 23ce122..35ef43b 100644
--- a/src/account/urls.py
+++ b/src/account/urls.py
@@ -29,24 +29,22 @@ from django.conf.urls import url
from django.urls import path
from account.views import (
- AccountSettingsView,
OIDCLoginView,
LogoutView,
- UserListView,
account_resource_view,
account_booking_view,
account_detail_view,
template_delete_view,
booking_cancel_view,
+ account_settings_view
)
app_name = 'account'
urlpatterns = [
- url(r'^settings/', AccountSettingsView.as_view(), name='settings'),
+ url(r'^settings/', account_settings_view, name='settings'),
url(r'^login/$', OIDCLoginView.as_view(), name='login'),
url(r'^logout/$', LogoutView.as_view(), name='logout'),
- url(r'^users/$', UserListView.as_view(), name='users'),
url(r'^my/resources/$', account_resource_view, name='my-resources'),
path('my/resources/delete/<int:resource_id>', template_delete_view),
url(r'^my/bookings/$', account_booking_view, name='my-bookings'),
diff --git a/src/account/views.py b/src/account/views.py
index 2d280cd..a975a2e 100644
--- a/src/account/views.py
+++ b/src/account/views.py
@@ -20,38 +20,55 @@ from django.contrib.auth.mixins import LoginRequiredMixin
from django.contrib.auth.models import User
from django.urls import reverse
from django.http import HttpResponse
-from django.shortcuts import get_object_or_404
+from django.shortcuts import get_object_or_404, redirect, render
from django.utils.decorators import method_decorator
from django.views.generic import RedirectView, TemplateView, UpdateView
from django.shortcuts import render
+from api.utils import ipa_set_ssh, ipa_query_user, ipa_set_company
+from dashboard.forms import SetCompanyForm, SetSSHForm
from rest_framework.authtoken.models import Token
from mozilla_django_oidc.auth import OIDCAuthenticationBackend
-from account.forms import AccountSettingsForm
+from account.forms import AccountPreferencesForm
from account.models import UserProfile
from booking.models import Booking
from api.views import delete_template, liblaas_templates
-@method_decorator(login_required, name='dispatch')
-class AccountSettingsView(UpdateView):
- model = UserProfile
- form_class = AccountSettingsForm
- template_name_suffix = '_update_form'
-
- def get_success_url(self):
- messages.add_message(self.request, messages.INFO,
- 'Settings saved')
- return '/'
+from workflow.views import login
+
+def account_settings_view(request):
+ if request.method == "GET":
+ if not request.user.is_authenticated:
+ return login(request)
+ profile = UserProfile.objects.get(user=request.user)
+ if (not profile or profile.ipa_username == "" or profile.ipa_username == None):
+ return redirect("dashboard:index")
+ ipa_user = ipa_query_user(profile.ipa_username)
+ template = "account/settings.html"
+ context = {
+ "preference_form": AccountPreferencesForm(instance=profile),
+ "company_form": SetCompanyForm(initial={'company': ipa_user['ou']}),
+ "existing_keys": ipa_user['ipasshpubkey'] if 'ipasshpubkey' in ipa_user else []
+ }
+ return render(request, template, context)
+
+ if request.method == 'POST':
+ data = request.POST
- def get_object(self, queryset=None):
- return self.request.user.userprofile
+ print("data is", data)
+ # User profile
+ profile = UserProfile.objects.get(user=request.user)
+ profile.public_user = "public_user" in data
+ profile.timezone = data["timezone"]
+ profile.save()
- def get_context_data(self, **kwargs):
- token, created = Token.objects.get_or_create(user=self.request.user)
- context = super(AccountSettingsView, self).get_context_data(**kwargs)
- context.update({'title': "Settings", 'token': token})
- return context
+ # IPA
+ ipa_set_company(profile, data['company'])
+ ipa_set_ssh(profile, data['ssh_key_list'].split(","))
+ return redirect("account:settings")
+
+ return HttpResponse(status=405)
class MyOIDCAB(OIDCAuthenticationBackend):
def filter_users_by_claims(self, claims):
@@ -106,17 +123,6 @@ class LogoutView(LoginRequiredMixin, RedirectView):
return '/'
-@method_decorator(login_required, name='dispatch')
-class UserListView(TemplateView):
- template_name = "account/user_list.html"
-
- def get_context_data(self, **kwargs):
- users = UserProfile.objects.filter(public_user=True).select_related('user')
- context = super(UserListView, self).get_context_data(**kwargs)
- context.update({'title': "Dashboard Users", 'users': users})
- return context
-
-
def account_detail_view(request):
template = "account/details.html"
return render(request, template)
@@ -134,10 +140,12 @@ def account_resource_view(request):
template = "account/resource_list.html"
if request.method == "GET":
-
+ profile = UserProfile.objects.get(user=request.user)
+ if (not profile or profile.ipa_username == "" or profile.ipa_username == None):
+ return redirect("dashboard:index")
r = liblaas_templates(request)
usable_templates = r.json()
- user_templates = [ t for t in usable_templates if t["owner"] == str(request.user)]
+ user_templates = [ t for t in usable_templates if t["owner"] == profile.ipa_username]
context = {
"templates": user_templates,
"title": "My Resources"
@@ -153,6 +161,9 @@ def account_resource_view(request):
def account_booking_view(request):
if not request.user.is_authenticated:
return render(request, "dashboard/login.html", {'title': 'Authentication Required'})
+ profile = UserProfile.objects.get(user=request.user)
+ if (not profile or profile.ipa_username == "" or profile.ipa_username == None):
+ return redirect("dashboard:index")
template = "account/booking_list.html"
bookings = list(Booking.objects.filter(owner=request.user, end__gt=timezone.now()).order_by("-start"))
my_old_bookings = Booking.objects.filter(owner=request.user, end__lt=timezone.now()).order_by("-start")
diff --git a/src/api/urls.py b/src/api/urls.py
index b009aeb..755b61f 100644
--- a/src/api/urls.py
+++ b/src/api/urls.py
@@ -42,6 +42,11 @@ from api.views import (
list_labs,
all_users,
booking_details,
+ ipa_create_account,
+ ipa_confirm_account,
+ ipa_set_company_from_workflow,
+ ipa_add_ssh_from_workflow,
+ ipa_conflict_account
)
urlpatterns = [
@@ -60,5 +65,10 @@ urlpatterns = [
path('users', all_users),
path('labs', list_labs),
+ path('ipa/create', ipa_create_account),
+ path('ipa/confirm', ipa_confirm_account),
+ path('ipa/conflict', ipa_conflict_account),
+ path('ipa/workflow-company', ipa_set_company_from_workflow),
+ path('ipa/workflow-ssh', ipa_add_ssh_from_workflow),
url(r'^token$', GenerateTokenView.as_view(), name='generate_token'),
]
diff --git a/src/api/utils.py b/src/api/utils.py
new file mode 100644
index 0000000..c32205e
--- /dev/null
+++ b/src/api/utils.py
@@ -0,0 +1,122 @@
+# These functions are called from views and perform the actual request to LibLaaS
+
+import json
+from django.http.response import JsonResponse, HttpResponse
+import requests
+import os
+
+from dashboard.forms import *
+liblaas_base_url = os.environ.get("LIBLAAS_BASE_URL")
+
+# IPA Stuff
+def ipa_query_user(ipa_username):
+ url = liblaas_base_url + "user/" + ipa_username
+ print("Getting ipa user for", ipa_username, url)
+ try:
+ response = requests.get(url)
+ data = response.json()
+ print("ipa user is", data)
+ return data # Expects a dict
+ except Exception as e:
+ print(e)
+ return None
+
+# Queries for an IPA user using dashboard username
+# Returns a result
+def get_ipa_migration_form(user, profile):
+ # ipa_user = ipa_query_user(str(dashboard_user))
+ # if (ipa_user and ipa_user.mail is )
+ # pass
+ dashboard_username = str(user)
+ dashboard_email = profile.email_addr
+ first_name = user.first_name
+ last_name = user.last_name
+
+ ipa_user = ipa_query_user(dashboard_username)
+ print("Attempting auto migration with", dashboard_username, dashboard_email, ipa_user)
+ if (ipa_user):
+ if (dashboard_email == ipa_user["mail"]):
+ # User is found and email match
+ print("User is found and email match")
+ return {
+ "form": ReadOnlyIPAAccountForm(initial={'ipa_username': ipa_user['uid'],'first_name': ipa_user["givenname"], 'last_name': ipa_user["sn"], 'email': ipa_user["mail"], 'company': ipa_user["ou"]}),
+ "message": "We have located the following IPA account matching your username and email. Please confirm to link your account. You may change these details at any time.",
+ "action": "api/ipa/confirm",
+ "button": "Link"
+ }
+
+ else:
+ # User is found and emails don't match
+ print("User is found and emails don't match")
+ return {
+ "form": ConflictIPAAcountForm(initial={'first_name': first_name, 'last_name': last_name, 'email': dashboard_email}),
+ "message": "Our records indicate that you do not currently have an account in our IPA system, or your emails do not match. Please enter the following details to enroll your account.",
+ "action": "/",
+ "button": "Submit"
+ }
+ else:
+ # User is not found
+ print("User is not found")
+ return {
+ "form": NewIPAAccountForm(initial={'first_name': first_name, 'last_name': last_name, 'email': dashboard_email}),
+ "message": "Our records indicate that you do not currently have an account in our IPA system, or your usernames do not match. Please enter the following details to enroll your account.",
+ "action": "api/ipa/create",
+ "button": "Submit"
+ }
+
+# Take a list of strings, sends it to liblaas, replacing the IPA keys with the new keys
+def ipa_set_ssh(user_profile, ssh_key_list):
+ url = liblaas_base_url + "user/" + user_profile.ipa_username + "/ssh"
+ print(ssh_key_list)
+ print("Setting SSH keys with URL", url)
+ try:
+ requests.post(url, data=json.dumps(ssh_key_list), headers={'Content-Type': 'application/json'})
+ return HttpResponse(status=200)
+ except Exception as e:
+ print(e)
+ return HttpResponse(status=500)
+
+def ipa_set_company(user_profile, company_name):
+ url = liblaas_base_url + "user/" + user_profile.ipa_username + "/company"
+ print("Setting company with URL", url)
+ try:
+ requests.post(url, data=json.dumps(company_name), headers={'Content-Type': 'application/json'})
+ return HttpResponse(status=200)
+ except Exception as e:
+ print(e)
+ return HttpResponse(status=500)
+
+def get_booking_prereqs_validator(user_profile):
+ ipa_user = None
+ if (user_profile.ipa_username != None and user_profile.ipa_username != ""):
+ ipa_user = ipa_query_user(user_profile.ipa_username)
+
+ if ipa_user == None:
+ print("No user")
+ return {
+ "form": None,
+ "exists": "false",
+ "action": "no user"
+ }
+
+ if (not "ou" in ipa_user) or (ipa_user["ou"] == ""):
+ print("Missing company")
+ return {
+ "form": SetCompanyForm(),
+ "exists": "true",
+ "action": "/api/ipa/workflow-company"
+ }
+
+ if (not "ipasshpubkey" in ipa_user) or (ipa_user["ipasshpubkey"] == []):
+ print("Missing SSH key")
+ return {
+ "form": SetSSHForm(),
+ "exists": "true",
+ "action": "/api/ipa/workflow-ssh"
+ }
+
+ return {
+ "form": None,
+ "exists": "false",
+ "action": ""
+ } \ No newline at end of file
diff --git a/src/api/views.py b/src/api/views.py
index ea36a6d..98dd3dc 100644
--- a/src/api/views.py
+++ b/src/api/views.py
@@ -23,7 +23,7 @@ from django.views import View
from django.http import HttpResponseNotFound
from django.http.response import JsonResponse, HttpResponse
import requests
-from rest_framework import viewsets
+from api.utils import ipa_query_user, ipa_set_ssh, ipa_set_company
from rest_framework.authtoken.models import Token
from django.views.decorators.csrf import csrf_exempt
from django.core.exceptions import ObjectDoesNotExist
@@ -51,7 +51,7 @@ Most functions let you GET or POST to the same endpoint, and
the correct thing will happen
"""
-
+liblaas_base_url = os.environ.get('LIBLAAS_BASE_URL')
@method_decorator(login_required, name='dispatch')
class GenerateTokenView(View):
def get(self, request, *args, **kwargs):
@@ -249,20 +249,31 @@ def make_booking(request):
data = json.loads(request.body)
print("incoming data is ", data)
- allowed_users = list(data["allowed_users"])
- allowed_users.append(str(request.user))
+ # todo - test this
+ ipa_users = list(UserProfile.objects.get(user=request.user).ipa_username) # add owner's ipa username to list of allowed users to be sent to liblaas
+
+ for user in list(data["allowed_users"]):
+ collab_profile = UserProfile.objects.get(user=User.objects.get(username=user))
+ if (collab_profile.ipa_username == "" or collab_profile.ipa_username == None):
+ return JsonResponse(
+ data={},
+ status=406, # Not good practice but a quick solution until blob validation is fully supported within django instead of the frontend
+ safe=False
+ )
+ else:
+ ipa_users.append(collab_profile.ipa_username)
bookingBlob = {
"template_id": data["template_id"],
- "allowed_users": allowed_users,
+ "allowed_users": ipa_users,
"global_cifile": data["global_cifile"],
"metadata": {
"booking_id": None, # fill in after creating django object
- "owner": str(request.user),
+ "owner": UserProfile.objects.get(user=request.user).ipa_username,
"lab": "UNH_IOL",
"purpose": data["metadata"]["purpose"],
"project": data["metadata"]["project"],
- "length": data["metadata"]["length"]
+ "length": int(data["metadata"]["length"])
}
}
@@ -279,9 +290,8 @@ def make_booking(request):
print("successfully created booking object with id ", booking.id)
# Now add collabs
- for c in bookingBlob["allowed_users"]:
- if c != bookingBlob["metadata"]["owner"]: # Don't add self as a collab
- booking.collaborators.add(User.objects.get(username=c))
+ for c in list(data["allowed_users"]):
+ booking.collaborators.add(User.objects.get(username=c))
print("successfully added collabs")
# Now create it in liblaas
@@ -479,7 +489,7 @@ def liblaas_request(request) -> JsonResponse:
liblaas_endpoint = post_data["endpoint"]
payload = post_data["workflow_data"]
# Fill in actual username
- liblaas_endpoint = liblaas_endpoint.replace("[username]", str(request.user))
+ liblaas_endpoint = liblaas_endpoint.replace("[username]", UserProfile.objects.get(user=request.user).ipa_username)
liblaas_endpoint = liblaas_base_url + liblaas_endpoint
print("processed endpoint is ", liblaas_endpoint)
@@ -513,7 +523,7 @@ def liblaas_request(request) -> JsonResponse:
)
def liblaas_templates(request):
- liblaas_url = os.environ.get("LIBLAAS_BASE_URL") + "template/list/" + str(request.user)
+ liblaas_url = os.environ.get("LIBLAAS_BASE_URL") + "template/list/" + UserProfile.objects.get(user=request.user).ipa_username
print("api call to " + liblaas_url)
return requests.get(liblaas_url)
@@ -553,4 +563,86 @@ def liblaas_end_booking(aggregateId):
return response
except:
print("failed to end booking")
- return HttpResponse(status=500) \ No newline at end of file
+ return HttpResponse(status=500)
+
+def ipa_create_account(request):
+ # Called when no username was found
+ # Only allow if user does not have a linked ipa account
+ profile = UserProfile.objects.get(user=request.user)
+ if (profile.ipa_username):
+ return HttpResponse(status=401)
+
+ post_data = request.POST
+ user = {
+ 'uid': str(request.user),
+ 'givenname': post_data['first_name'],
+ 'sn': post_data['last_name'],
+ 'cn': post_data['first_name'] + " " + post_data['last_name'],
+ 'mail': post_data['email'],
+ 'ou': post_data['company'],
+ 'random': True
+ }
+
+ try:
+ response = requests.post(liblaas_base_url + "user/create", data=json.dumps(user), headers={'Content-Type': 'application/json'})
+ profile.ipa_username = user['uid']
+ print("Saving ipa username", profile.ipa_username)
+ profile.save()
+ return redirect("dashboard:index")
+ except Exception as e:
+ print(e)
+ return redirect("dashboard:index")
+
+def ipa_confirm_account(request):
+ # Called when username was found and email matches
+ profile = UserProfile.objects.get(user=request.user)
+ if (profile.ipa_username):
+ return HttpResponse(status=401)
+
+ profile.ipa_username = str(request.user)
+ print("Saving ipa username", profile.ipa_username)
+ profile.save()
+ return redirect("dashboard:index")
+
+def ipa_conflict_account(request):
+ # Called when username was found but emails do not match
+ # Need to ask user to input alternate username
+ # To verify username is not taken, call query_username and accept if returns None
+ print("ipa conflict account")
+ profile = UserProfile.objects.get(user=request.user)
+ print("profile is", profile)
+ if (profile.ipa_username):
+ return HttpResponse(status=401)
+
+ post_data = request.POST
+ user = {
+ 'uid': post_data['ipa_username'],
+ 'givenname': post_data['first_name'],
+ 'sn': post_data['last_name'],
+ 'cn': post_data['first_name'] + " " + post_data['last_name'],
+ 'mail': post_data['email'],
+ 'ou': post_data['company'],
+ 'random': True,
+ }
+
+ try:
+ response = requests.post(liblaas_base_url + "user/create", data=json.dumps(user), headers={'Content-Type': 'application/json'})
+ profile.ipa_username = user['uid']
+ print("Saving ipa username", profile.ipa_username)
+ profile.save()
+ return redirect("dashboard:index")
+ except Exception as e:
+ print(e)
+ return redirect("dashboard:index")
+
+def ipa_set_company_from_workflow(request):
+ profile = UserProfile.objects.get(user=request.user)
+ ipa_set_company(profile, request.POST["company"])
+ return redirect("workflow:book_a_pod")
+
+def ipa_add_ssh_from_workflow(request):
+ profile = UserProfile.objects.get(user=request.user)
+ key_as_list = []
+ key_as_list.append(request.POST["ssh_public_key"])
+ ipa_set_ssh(profile, key_as_list)
+ return redirect("workflow:book_a_pod")
diff --git a/src/booking/migrations/0012_auto_20230802_1810.py b/src/booking/migrations/0012_auto_20230802_1810.py
new file mode 100644
index 0000000..36095a1
--- /dev/null
+++ b/src/booking/migrations/0012_auto_20230802_1810.py
@@ -0,0 +1,18 @@
+# Generated by Django 2.2 on 2023-08-02 18:10
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('booking', '0011_booking_aggregateid'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='booking',
+ name='aggregateId',
+ field=models.CharField(blank=True, max_length=36),
+ ),
+ ]
diff --git a/src/booking/models.py b/src/booking/models.py
index 09244d3..eb72eb2 100644
--- a/src/booking/models.py
+++ b/src/booking/models.py
@@ -34,7 +34,7 @@ class Booking(models.Model):
pdf = models.TextField(blank=True, default="")
idf = models.TextField(blank=True, default="")
# Associated LibLaaS aggregate
- aggregateId = models.CharField(blank=True, max_length=36, validators=[RegexValidator(regex='^[0-9a-fA-F]{8}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{4}\b-[0-9a-fA-F]{12}$', message='aggregate_id must be a valid UUID', code='nomatch')])
+ aggregateId = models.CharField(blank=True, max_length=36)
complete = models.BooleanField(default=False)
diff --git a/src/dashboard/forms.py b/src/dashboard/forms.py
new file mode 100644
index 0000000..dd87ec6
--- /dev/null
+++ b/src/dashboard/forms.py
@@ -0,0 +1,46 @@
+from django import forms
+
+class NewIPAAccountForm(forms.Form):
+
+ # First name
+ first_name = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'First Name', 'style': 'width: 300px;', 'class': 'form-control'}))
+ # Last name
+ last_name = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Last Name', 'style': 'width: 300px;', 'class': 'form-control'}))
+ # Company
+ company = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Company', 'style': 'width: 300px;', 'class': 'form-control'}))
+ # Email
+ email = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Email', 'style': 'width: 300px;', 'class': 'form-control', "readonly": True}))
+
+class ReadOnlyIPAAccountForm(forms.Form):
+ # IPA Username
+ ipa_username = forms.CharField(required=False, widget=forms.TextInput(attrs={'placeholder': 'IPA Username', 'style': 'width: 300px;', 'class': 'form-control', "readonly": True}))
+ # First name
+ first_name = forms.CharField(required=False, widget=forms.TextInput(attrs={'placeholder': 'First Name', 'style': 'width: 300px;', 'class': 'form-control', "readonly": True}))
+ # Last name
+ last_name = forms.CharField(required=False, widget=forms.TextInput(attrs={'placeholder': 'Last Name', 'style': 'width: 300px;', 'class': 'form-control', "readonly": True}))
+ # Company
+ company = forms.CharField(required=False, widget=forms.TextInput(attrs={'placeholder': 'Company', 'style': 'width: 300px;', 'class': 'form-control', "readonly": True}))
+ # Email
+ email = forms.CharField(required=False, widget=forms.TextInput(attrs={'placeholder': 'Email', 'style': 'width: 300px;', 'class': 'form-control', "readonly": True}))
+
+class ConflictIPAAcountForm(forms.Form):
+ # IPA Username
+ ipa_username = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'IPA Username', 'style': 'width: 300px;', 'class': 'form-control'}))
+ # First name
+ first_name = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'First Name', 'style': 'width: 300px;', 'class': 'form-control'}))
+ # Last name
+ last_name = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Last Name', 'style': 'width: 300px;', 'class': 'form-control'}))
+ # Company
+ company = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Company', 'style': 'width: 300px;', 'class': 'form-control'}))
+ # Email
+ email = forms.CharField(widget=forms.TextInput(attrs={'placeholder': 'Email', 'style': 'width: 300px;', 'class': 'form-control', "readonly": True}))
+
+class SetCompanyForm(forms.Form):
+ # Company
+ company = forms.CharField(required=True, widget=forms.TextInput(attrs={'placeholder': 'Company', 'style': 'width: 300px;', 'class': 'form-control'}))
+
+class SetSSHForm(forms.Form):
+ # SSH key
+ ssh_public_key = forms.CharField(required=True, widget=forms.Textarea(attrs={'placeholder': 'SSH Public Key', 'style': 'width: 300px;', 'class': 'form-control'}))
+
+
diff --git a/src/dashboard/views.py b/src/dashboard/views.py
index 2942d59..f250a3c 100644
--- a/src/dashboard/views.py
+++ b/src/dashboard/views.py
@@ -16,8 +16,11 @@ from django.db.models import Q
from datetime import datetime
import pytz
-from account.models import Lab
+from account.models import Lab, UserProfile
+from api.utils import get_ipa_migration_form, ipa_query_user
+from api.views import ipa_conflict_account
from booking.models import Booking
+from dashboard.forms import *
from laas_dashboard import settings
@@ -72,25 +75,55 @@ def host_profile_detail_view(request):
def landing_view(request):
user = request.user
+ ipa_migrator = {
+ "exists": "false" # Jinja moment
+ }
if not user.is_anonymous:
bookings = Booking.objects.filter(
Q(owner=user) | Q(collaborators=user),
end__gte=datetime.now(pytz.utc)
)
+ profile = UserProfile.objects.get(user=user)
+ if (not profile.ipa_username):
+ ipa_migrator = get_ipa_migration_form(user, profile)
+ ipa_migrator["exists"] = "true"
+
else:
bookings = None
+ print("IPA migrator is", ipa_migrator)
LFID = True if settings.AUTH_SETTING == 'LFID' else False
- return render(
- request,
- 'dashboard/landing.html',
- {
- 'title': "Welcome to the Lab as a Service Dashboard",
- 'bookings': bookings,
- 'LFID': LFID
- }
- )
+ if request.method == "GET":
+ return render(
+ request,
+ 'dashboard/landing.html',
+ {
+ 'title': "Welcome to the Lab as a Service Dashboard",
+ 'bookings': bookings,
+ 'LFID': LFID,
+ 'ipa_migrator': ipa_migrator,
+ }
+ )
+
+ # Using this for the special case in the ipa_migrator
+ if request.method == 'POST':
+ existing_profile = ipa_query_user(request.POST['ipa_username'])
+ print("exists already?", existing_profile != None)
+ if (existing_profile != None):
+ return render(
+ request,
+ 'dashboard/landing.html',
+ {
+ 'title': "Welcome to the Lab as a Service Dashboard",
+ 'bookings': bookings,
+ 'LFID': LFID,
+ 'ipa_migrator': ipa_migrator,
+ 'error': "Username is already taken"
+ }
+ )
+ else:
+ return ipa_conflict_account(request)
class LandingView(TemplateView):
template_name = "dashboard/landing.html"
diff --git a/src/static/js/workflows/book-a-pod.js b/src/static/js/workflows/book-a-pod.js
index d573342..3f83849 100644
--- a/src/static/js/workflows/book-a-pod.js
+++ b/src/static/js/workflows/book-a-pod.js
@@ -71,7 +71,7 @@ const steps = {
}
isValidCIFile(ci_file) {
- // todo
+ // todo
return true;
}
@@ -232,34 +232,37 @@ const steps = {
return[passed, message, section];
}
- onclickCancel() {
- if (confirm("Are you sure you wish to discard this booking?")) {
- location.reload();
- }
- }
+ // onclickCancel() {
+ // if (confirm("Are you sure you wish to discard this booking?")) {
+ // location.reload();
+ // }
+ // }
/** Async / await is more infectious than I thought, so all functions that rely on an API call will need to be async */
async onclickConfirm() {
const complete = this.isCompleteBookingInfo();
if (!complete[0]) {
- alert(complete[1]);
+ showError(complete[1]);
this.step = complete[2]
document.getElementById(this.sections[complete[2]]).scrollIntoView({behavior: 'smooth'});
return
}
- if (confirm("Are you sure you would like to create this booking?")) {
- const response = await LibLaaSAPI.makeBooking(this.bookingBlob);
- if (response.bookingId) {
- alert("The booking has been successfully created.")
- window.location.href = "../../";
+
+ const response = await LibLaaSAPI.makeBooking(this.bookingBlob);
+ if (response.bookingId) {
+ showError("The booking has been successfully created.")
+ window.location.href = "../../";
+ } else {
+ if (response.status == 406) {
+ showError("One or more collaborators is missing SSH keys or has not configured their IPA account.")
} else {
- alert("The booking could not be created at this time.")
+ showError("The booking could not be created at this time.")
}
}
- }
-
-
+ // if (confirm("Are you sure you would like to create this booking?")) {
+ // }
+ }
}
diff --git a/src/static/js/workflows/design-a-pod.js b/src/static/js/workflows/design-a-pod.js
index 7632537..58f8b85 100644
--- a/src/static/js/workflows/design-a-pod.js
+++ b/src/static/js/workflows/design-a-pod.js
@@ -110,13 +110,13 @@ class DesignWorkflow extends Workflow {
this.step = steps.ADD_RESOURCES;
if (this.templateBlob.lab_name == null) {
- alert("Please select a lab before adding resources.");
+ showError("Please select a lab before adding resources.");
this.goTo(steps.SELECT_LAB);
return;
}
if (this.templateBlob.host_list.length >= 8) {
- alert("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.")
return;
}
@@ -282,7 +282,7 @@ class DesignWorkflow extends Workflow {
}
}
- alert("didnt remove");
+ showError("didnt remove");
}
@@ -297,13 +297,13 @@ class DesignWorkflow extends Workflow {
this.step = steps.ADD_NETWORKS;
if (this.templateBlob.lab_name == null) {
- alert("Please select a lab before adding networks.");
+ showError("Please select a lab before adding networks.");
this.goTo(steps.SELECT_LAB);
return;
}
if (document.querySelector('#new_network_card') != null) {
- alert("Please finish adding the current network before adding a new one.");
+ showError("Please finish adding the current network before adding a new one.");
return;
}
@@ -377,7 +377,7 @@ class DesignWorkflow extends Workflow {
}
}
- alert("didnt remove");
+ showError("didnt remove");
}
/** Rebuilds the list without the chosen template */
@@ -407,7 +407,7 @@ class DesignWorkflow extends Workflow {
const host = this.templateBlob.findHost(hostname);
if (!host) {
- alert("host not found error");
+ showError("host not found error");
}
this.connectionTemp = new ConnectionTemp(host, this.templateBlob.networks, this.labFlavors.get(host.flavor).interfaces);
@@ -440,11 +440,11 @@ class DesignWorkflow extends Workflow {
setPodDetailEventListeners() {
const pod_name_input = document.getElementById("pod-name-input");
const pod_desc_input = document.getElementById("pod-desc-input");
- const pod_public_input = document.getElementById("pod-public-input");
+ // const pod_public_input = document.getElementById("pod-public-input");
pod_name_input.value = "";
pod_desc_input.value = "";
- pod_public_input.checked = false;
+ // pod_public_input.checked = false;
pod_name_input.addEventListener('focusout', (event)=> {
workflow.onFocusOutPodNameInput(pod_name_input);
@@ -466,10 +466,10 @@ class DesignWorkflow extends Workflow {
GUI.hidePodDetailsError();
});
- pod_public_input.addEventListener('focusout', (event)=> {
- this.step = steps.POD_DETAILS;
- workflow.onFocusOutPodPublicInput(pod_public_input);
- });
+ // pod_public_input.addEventListener('focusout', (event)=> {
+ // this.step = steps.POD_DETAILS;
+ // workflow.onFocusOutPodPublicInput(pod_public_input);
+ // });
}
onFocusOutPodNameInput(element) {
@@ -524,13 +524,13 @@ class DesignWorkflow extends Workflow {
return [result, message]
}
- async onclickDiscardTemplate() {
- this.step = steps.POD_SUMMARY;
- if(confirm('Are you sure you wish to delete this Pod?')) {
- await LibLaaSAPI.deleteTemplate(this.templateBlob);
- location.reload();
- }
- }
+ // async onclickDiscardTemplate() {
+ // this.step = steps.POD_SUMMARY;
+ // if(confirm('Are you sure you wish to delete this Pod?')) {
+ // await LibLaaSAPI.deleteTemplate(this.templateBlob);
+ // location.reload();
+ // }
+ // }
simpleStepValidation() {
let passed = true;
@@ -561,20 +561,21 @@ class DesignWorkflow extends Workflow {
this.step = steps.POD_SUMMARY;
const simpleValidation = this.simpleStepValidation();
if (!simpleValidation[0]) {
- alert(simpleValidation[1])
+ showError(simpleValidation[1])
this.goTo(simpleValidation[2]);
return;
}
// todo - make sure each host has at least one connection on any network.
- if (confirm("Are you sure you wish to create this pod?")) {
- let success = await LibLaaSAPI.makeTemplate(this.templateBlob);
- if (success) {
- window.location.href = "../../accounts/my/resources/";
- } else {
- alert("Could not create template.")
- }
+ // if (confirm("Are you sure you wish to create this pod?")) {
+
+ // }
+ let success = await LibLaaSAPI.makeTemplate(this.templateBlob);
+ if (success) {
+ window.location.href = "../../accounts/my/resources/";
+ } else {
+ showError("Could not create template.")
}
}
}
@@ -1182,5 +1183,5 @@ class ConnectionTemp {
}
function todo() {
- alert('todo');
+ showError('todo');
} \ No newline at end of file
diff --git a/src/static/js/workflows/workflow.js b/src/static/js/workflows/workflow.js
index 745a706..f3f39e9 100644
--- a/src/static/js/workflows/workflow.js
+++ b/src/static/js/workflows/workflow.js
@@ -242,5 +242,12 @@ class Workflow {
}
function apiError(info) {
- alert("Unable to fetch " + info +". Please try again later or contact support.")
+ showError("Unable to fetch " + info +". Please try again later or contact support.")
}
+
+function showError(message) {
+ const text = document.getElementById('alert_modal_message');
+
+ text.innerText = message;
+ $("#alert_modal").modal('show');
+}
diff --git a/src/templates/base/account/settings.html b/src/templates/base/account/settings.html
new file mode 100644
index 0000000..d1939b7
--- /dev/null
+++ b/src/templates/base/account/settings.html
@@ -0,0 +1,88 @@
+{% extends "base.html" %}
+{% load staticfiles %}
+{% load bootstrap4 %}
+{% block content %}
+<h1>Settings</h1>
+ <form action="/accounts/settings/" method="post">
+ {% csrf_token %}
+ <input id="hidden_key_list" type="hidden" name="ssh_key_list" value="">
+ <div class="form-group">
+ {{ company_form }}
+ {{ preference_form }}
+ <br>
+ <label>SSH Keys:</label>
+ <ul class="list-group" id="key_ul">
+ {% for key in existing_keys %}
+ <li class="card w-25 mb-1">
+ <div class="card-body" style="height: 150px; overflow-y: auto;">
+ {{key}}
+ </div>
+ <div class="card-footer d-flex flex-row-reverse">
+ <div class="btn btn-danger" onclick="remove_key('{{key}}', this.parentNode.parentNode)">Delete</div>
+ </div>
+ </li>
+ {% endfor %}
+ </ul>
+ <li class="card w-25 text-truncate mb-1">
+ <div class="card-body">
+ <textarea id="new_key_area" placeholder="New SSH Public Key" class="form-control" id="new_key_input"></textarea> </div>
+ <div class="card-footer d-flex flex-row-reverse">
+ <div class="btn btn-success" onclick="add_key(this.parentNode.parentNode.childNodes[1].childNodes[1].value)">Add</div>
+ </div>
+ </li>
+ <input class="btn btn-success mt-5" style="width: 100px" name="submitButton" type="submit" value="Save">
+ </div>
+ </form>
+
+<script>
+let key_list = []
+$(window).on('load', function() {
+ document.getElementById('new_key_area').value = "";
+ {% for key in existing_keys %}
+ key_list.push('{{key}}')
+ {% endfor %}
+ update_json_list()
+ console.log(key_list)
+});
+
+
+function remove_key(target_key, node) {
+ key_list = key_list.filter(key => key != target_key);
+ node.setAttribute("hidden", "true");
+ update_json_list()
+}
+
+function add_key(key_string) {
+ console.log(key_string)
+ if (key_list.indexOf(key_string) != -1) {
+ alert('This key has already been added');
+ return;
+ }
+ key_list.push(key_string)
+ create_key_card(key_string)
+ update_json_list()
+}
+
+function create_key_card(key_string) {
+ const elem = document.createElement('li');
+ elem.classList.add('card', 'w-25', 'mb-1');
+ elem.innerHTML = `
+ <div class="card-body" style="height: 150px; overflow-y: auto;">
+ ` + key_string + `
+ </div>
+ <div class="card-footer d-flex flex-row-reverse">
+ <div class="btn btn-danger" onclick="remove_key('` + key_string +`', this.parentNode.parentNode)">Delete</div>
+ </div>
+ `
+
+ document.getElementById('key_ul').appendChild(elem);
+ document.getElementById('new_key_area').value = "";
+
+}
+
+function update_json_list() {
+ document.getElementById("hidden_key_list").value = key_list.toString()
+}
+</script>
+{% endblock content %}
+
diff --git a/src/templates/base/account/user_list.html b/src/templates/base/account/user_list.html
deleted file mode 100644
index e564524..0000000
--- a/src/templates/base/account/user_list.html
+++ /dev/null
@@ -1,55 +0,0 @@
-{% extends "dashboard/table.html" %}
-{% load staticfiles %}
-
-{% block table %}
- <thead>
- <tr>
- <th>Username</th>
- <th>Full Name</th>
- <th>Email</th>
- <th>Company</th>
- <th>SSH Key</th>
- <th>GPG Key</th>
- </tr>
- </thead>
- <tbody>
- {% for user in users %}
- <tr>
- <td>
- {{ user.username }}
- </td>
- <td>
- {{ user.userprofile.full_name }}
- </td>
- <td>
- {{ user.userprofile.email_addr }}
- </td>
- <td>
- {{ user.userprofile.company }}
- </td>
- <td>
- {% if user.userprofile.ssh_public_key %}
- <a href={{ user.userprofile.ssh_public_key.url }}>SSH</a>
- {% endif %}
- </td>
- <td>
- {% if user.userprofile.pgp_public_key %}
- <a href={{ user.userprofile.pgp_public_key.url }}>GPG</a>
- {% endif %}
- </td>
- </tr>
- {% endfor %}
- </tbody>
-{% endblock table %}
-
-
-{% block tablejs %}
- <script type="text/javascript">
- $(document).ready(function () {
- $('#table').DataTable({
- scrollX: true,
- "order": [[0, "asc"]]
- });
- });
- </script>
-{% endblock tablejs %}
diff --git a/src/templates/base/dashboard/landing.html b/src/templates/base/dashboard/landing.html
index fea4deb..960ad39 100644
--- a/src/templates/base/dashboard/landing.html
+++ b/src/templates/base/dashboard/landing.html
@@ -1,16 +1,10 @@
{% extends "base.html" %}
{% load staticfiles %}
-
+{% load bootstrap4 %}
{% block content %}
<div class="text-center">
{% if not request.user.is_anonymous %}
- {% if not request.user.userprofile.ssh_public_key %}
- <div class="alert alert-danger alertAnuket" role="alert">
- <b>Warning: you need to upload an ssh key under <a href="/accounts/settings" class="inTextLink" >account settings</a> if you wish to
- log into the servers you book</b>
- </div>
- {% endif %}
{% else %}
{% endif %}
</div>
@@ -67,10 +61,37 @@
</div>
</div>
-<div class="hidden_form d-none" id="form_div">
- <form method="post" action="" class="form" id="wf_selection_form">
- {% csrf_token %}
- </form>
-</div>
+<!-- IPA Modal -->
+<div class="modal fade" id="ipa-modal" tabindex="-1">
+ <div class="modal-dialog modal-xl">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title">Welcome to LaaS 3.0</h5>
+ </div>
+ <div class="modal-body" id="add_resource_modal_body">
+ <p>We have made large scale improvements to the dashboard and our host provisioning service to improve your user experience.</p>
+ <p>{{ ipa_migrator.message }}</p>
+ <form action="{{ipa_migrator.action}}" method="post">
+ {% csrf_token %}
+ <p class="text-danger">{{error}}</p>
+ {{ ipa_migrator.form }}
+ <div class="form-group">
+ <input class="btn btn-success" name="submitButton" type="submit" value="{{ipa_migrator.button}}">
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+ </div>
+
+<script>
+
+$(window).on('load', function() {
+ if ({{ipa_migrator.exists}}) {
+ $('#ipa-modal').modal({backdrop: 'static', keyboard: false});
+ $('#ipa-modal').modal('show');
+ }
+ });
+</script>
{% endblock content %}
diff --git a/src/templates/base/workflow/book_a_pod.html b/src/templates/base/workflow/book_a_pod.html
index 7053bfd..7448dc5 100644
--- a/src/templates/base/workflow/book_a_pod.html
+++ b/src/templates/base/workflow/book_a_pod.html
@@ -98,7 +98,7 @@
</div>
</div>
<div class="row align-items-center mt-5">
- <button class="btn btn-danger cancel-book-button p-0 mr-2 col-xl-2 col-md-3 col-5" onclick="workflow.onclickCancel()">Cancel</button>
+ <!-- <button class="btn btn-danger cancel-book-button p-0 mr-2 col-xl-2 col-md-3 col-5" onclick="workflow.onclickCancel()">Cancel</button> -->
<button class="btn btn-success cancel-book-button p-0 ml-2 col-xl-2 col-md-3 col-5" onclick="workflow.onclickConfirm()">Book</button>
</div>
</div>
@@ -111,11 +111,60 @@
{% csrf_token %}
</form>
</div>
+
+<div class="modal fade" id="ipa-modal" tabindex="-1">
+ <div class="modal-dialog modal-xl">
+ <div class="modal-content">
+ <div class="modal-header">
+ <h5 class="modal-title">Welcome to LaaS 3.0</h5>
+ <button class="close" onclick="window.location.href = '../../'"><span aria-hidden="true">&times;</span></button>
+ </div>
+ <div class="modal-body" id="add_resource_modal_body">
+ <p>Please update your information before creating a booking.</p>
+ <form action="{{prereq_validator.action}}" method="post">
+ {% csrf_token %}
+ {{ prereq_validator.form }}
+ <div class="form-group">
+ <input class="btn btn-success" name="submitButton" type="submit" value="Save">
+ </div>
+ </form>
+ </div>
+ </div>
+ </div>
+</div>
+
+<!-- Alert Modal -->
+<div class="modal fade" id="alert_modal" tabindex="-1">
+ <div class="modal-dialog modal-sm">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span></button>
+ </div>
+ <div class="modal-body text-center">
+ <h5 id="alert_modal_message"></h5>
+ </div>
+ <div class="modal-footer d-flex justify-content-center">
+ <button class="btn btn-success" data-dismiss="modal" id="alert-modal-submit" onclick="">Confirm</button>
+ </div>
+ </div>
+ </div>
+</div>
</body>
<script>
- const user = "{{user}}"
- const workflow = new BookingWorkflow();
- workflow.startWorkflow();
+ let user;
+ let workflow;
+ $(window).on('load', function() {
+ if ({{prereq_validator.exists}}) {
+ $('#ipa-modal').modal({backdrop: 'static', keyboard: false});
+ $('#ipa-modal').modal('show');
+ } else {
+ user = "{{user}}"
+ workflow = new BookingWorkflow();
+ workflow.startWorkflow();
+ }
+ });
+
+
</script>
{% endblock %}
diff --git a/src/templates/base/workflow/design_a_pod.html b/src/templates/base/workflow/design_a_pod.html
index 5d48273..ab3f11b 100644
--- a/src/templates/base/workflow/design_a_pod.html
+++ b/src/templates/base/workflow/design_a_pod.html
@@ -83,14 +83,14 @@
<textarea id="pod-desc-input" class="form-control form-control-lg border border-dark pt-3 pl-3" rows="5" placeholder="Pod Description"></textarea>
</div>
</div>
- <div class="row align-items-center my-4">
+ <!-- <div class="row align-items-center my-4">
<div class="col-xl-6 col-md-8 col-11">
<div class="custom-control custom-switch">
<input type="checkbox" class="custom-control-input" id="pod-public-input">
<label class="custom-control-label" for="pod-public-input">Make pod template public?</label>
</div>
</div>
- </div>
+ </div> -->
<div class="row align-items-center my-4">
<div class="col-xl-6 col-md-8 col-11">
<span id="pod_details_error" class="text-danger"></span>
@@ -120,7 +120,7 @@
</div>
</div>
<div class="row align-items-center mt-5">
- <div class="col-xl-2 col-md-3 col-5"><button class="btn btn-danger cancel-book-button p-0 w-100" onclick="workflow.onclickDiscardTemplate()">Discard</button></div>
+ <!-- <div class="col-xl-2 col-md-3 col-5"><button class="btn btn-danger cancel-book-button p-0 w-100" onclick="workflow.onclickDiscardTemplate()">Discard</button></div> -->
<div class="col-xl-2 col-md-3 col-5"><button class="btn btn-success cancel-book-button p-0 w-100" onclick = "workflow.onclickSubmitTemplate()">Create</button></div>
</div>
</div>
@@ -197,6 +197,23 @@
</div>
</div>
+<!-- Alert Modal -->
+<div class="modal fade" id="alert_modal" tabindex="-1">
+ <div class="modal-dialog modal-sm">
+ <div class="modal-content">
+ <div class="modal-header">
+ <button class="close" data-dismiss="modal"><span aria-hidden="true">&times;</span></button>
+ </div>
+ <div class="modal-body text-center">
+ <h5 id="alert_modal_message"></h5>
+ </div>
+ <div class="modal-footer d-flex justify-content-center">
+ <button class="btn btn-success" data-dismiss="modal" id="alert-modal-submit" onclick="">Confirm</button>
+ </div>
+ </div>
+ </div>
+</div>
+
<div class="hidden_form d-none">
<form id="token">
{% csrf_token %}
diff --git a/src/workflow/README b/src/workflow/README
deleted file mode 100644
index 565d1c2..0000000
--- a/src/workflow/README
+++ /dev/null
@@ -1 +0,0 @@
-TODO: Document how new workflows work
diff --git a/src/workflow/views.py b/src/workflow/views.py
index 08ed22b..c634b38 100644
--- a/src/workflow/views.py
+++ b/src/workflow/views.py
@@ -8,12 +8,14 @@
##############################################################################
import json
-from django.shortcuts import render
+from django.shortcuts import render, redirect
from laas_dashboard.settings import TEMPLATE_OVERRIDE
from django.http import HttpResponse
from django.http.response import JsonResponse
from workflow.forms import BookingMetaForm
from api.views import liblaas_request, make_booking
+from api.utils import get_booking_prereqs_validator
+from account.models import UserProfile
def no_workflow(request):
@@ -27,6 +29,9 @@ def design_a_pod_view(request):
if request.method == "GET":
if not request.user.is_authenticated:
return login(request)
+ prereq_validator = get_booking_prereqs_validator(UserProfile.objects.get(user=request.user))
+ if (prereq_validator["action"] == "no user"):
+ return redirect("dashboard:index")
template = "workflow/design_a_pod.html"
context = {
"dashboard": str(TEMPLATE_OVERRIDE)
@@ -43,10 +48,14 @@ def book_a_pod_view(request):
if request.method == "GET":
if not request.user.is_authenticated:
return login(request)
+ prereq_validator = get_booking_prereqs_validator(UserProfile.objects.get(user=request.user))
+ if (prereq_validator["action"] == "no user"):
+ return redirect("dashboard:index")
template = "workflow/book_a_pod.html"
context = {
"dashboard": str(TEMPLATE_OVERRIDE),
"form": BookingMetaForm(initial={}, user_initial=[], owner=request.user),
+ "prereq_validator": prereq_validator
}
return render(request, template, context)