User subsystem 74/74074/1
authorJustin Choquette <jchoquette@iol.unh.edu>
Mon, 7 Aug 2023 18:10:19 +0000 (14:10 -0400)
committerJustin Choquette <jchoquette@iol.unh.edu>
Mon, 7 Aug 2023 18:16:04 +0000 (14:16 -0400)
Change-Id: Ibef4ede9b2d6a3ea465f79a9b5cbcc821afbccae
Signed-off-by: Justin Choquette <jchoquette@iol.unh.edu>
24 files changed:
src/account/forms.py
src/account/migrations/0011_userprofile_ipa_username.py [new file with mode: 0644]
src/account/migrations/0012_auto_20230725_1749.py [new file with mode: 0644]
src/account/migrations/0013_auto_20230727_1903.py [new file with mode: 0644]
src/account/models.py
src/account/urls.py
src/account/views.py
src/api/urls.py
src/api/utils.py [new file with mode: 0644]
src/api/views.py
src/booking/migrations/0012_auto_20230802_1810.py [new file with mode: 0644]
src/booking/models.py
src/dashboard/forms.py [new file with mode: 0644]
src/dashboard/views.py
src/static/js/workflows/book-a-pod.js
src/static/js/workflows/design-a-pod.js
src/static/js/workflows/workflow.js
src/templates/base/account/settings.html [new file with mode: 0644]
src/templates/base/account/user_list.html [deleted file]
src/templates/base/dashboard/landing.html
src/templates/base/workflow/book_a_pod.html
src/templates/base/workflow/design_a_pod.html
src/workflow/README [deleted file]
src/workflow/views.py

index 28cb27d..8bacc24 100644 (file)
@@ -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 (file)
index 0000000..25cf6fb
--- /dev/null
@@ -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 (file)
index 0000000..3f4d142
--- /dev/null
@@ -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 (file)
index 0000000..9e1d222
--- /dev/null
@@ -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',
+        ),
+    ]
index f1deca7..bb1cad5 100644 (file)
@@ -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'
index 23ce122..35ef43b 100644 (file)
@@ -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'),
index 2d280cd..a975a2e 100644 (file)
@@ -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")
index b009aeb..755b61f 100644 (file)
@@ -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 (file)
index 0000000..c32205e
--- /dev/null
@@ -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
index ea36a6d..98dd3dc 100644 (file)
@@ -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 (file)
index 0000000..36095a1
--- /dev/null
@@ -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),
+        ),
+    ]
index 09244d3..eb72eb2 100644 (file)
@@ -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 (file)
index 0000000..dd87ec6
--- /dev/null
@@ -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'}))
+
+
index 2942d59..f250a3c 100644 (file)
@@ -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"
index d573342..3f83849 100644 (file)
@@ -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?")) {
 
+        // }
+    }
   }
 
 
index 7632537..58f8b85 100644 (file)
@@ -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
index 745a706..f3f39e9 100644 (file)
@@ -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 (file)
index 0000000..d1939b7
--- /dev/null
@@ -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 (file)
index e564524..0000000
+++ /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 %}
index fea4deb..960ad39 100644 (file)
@@ -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>
     </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 %}
index 7053bfd..7448dc5 100644 (file)
@@ -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>
       {% 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 %}
index 5d48273..ab3f11b 100644 (file)
           <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>
       </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>
   </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 (file)
index 565d1c2..0000000
+++ /dev/null
@@ -1 +0,0 @@
-TODO: Document how new workflows work
index 08ed22b..c634b38 100644 (file)
@@ -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)