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
--- /dev/null
+# 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),
+ ),
+ ]
--- /dev/null
+# 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),
+ ),
+ ]
--- /dev/null
+# 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',
+ ),
+ ]
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'
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'),
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):
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)
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"
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")
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 = [
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'),
]
--- /dev/null
+# 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
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
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):
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"])
}
}
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
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)
)
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)
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")
--- /dev/null
+# 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),
+ ),
+ ]
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)
--- /dev/null
+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'}))
+
+
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
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"
}
isValidCIFile(ci_file) {
- // todo
+ // todo
return true;
}
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?")) {
+ // }
+ }
}
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;
}
}
}
- alert("didnt remove");
+ showError("didnt remove");
}
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;
}
}
}
- alert("didnt remove");
+ showError("didnt remove");
}
/** Rebuilds the list without the chosen template */
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);
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);
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) {
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;
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.")
}
}
}
}
function todo() {
- alert('todo');
+ showError('todo');
}
\ No newline at end of file
}
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');
+}
--- /dev/null
+{% 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 %}
+
+++ /dev/null
-{% 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 %}
{% 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 %}
</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">×</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">×</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 %}
<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">×</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 %}
+++ /dev/null
-TODO: Document how new workflows work
##############################################################################
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):
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)
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)