From e4488315862998d198d69cd8e4649428276e4f82 Mon Sep 17 00:00:00 2001 From: Sawyer Bergeron Date: Thu, 8 Apr 2021 14:19:54 -0400 Subject: [PATCH] Merge master for RC Squashed commit of the following: commit 4f12ae24d0de22f526d6dd59dbc437bdaa1874f1 Author: Sean Smith Date: Wed Apr 7 15:16:29 2021 -0400 Check if hostname null in resource filter Signed-off-by: Sean Smith Change-Id: Ie28c83109faf529f0a3bd4c61b1f96e064c1577c commit e9ed42e7ace16f36532a23d360300bb7f6b0387e Author: Sean Smith Date: Wed Apr 7 13:15:08 2021 -0400 Fix typo for image disable Change-Id: I74314aba4d1ee6f89b450666fd76deabdd99c11d Signed-off-by: Sean Smith commit 593ea6dc98ab12c1e70d65b16103f92f8b17abbc Merge: 241ff30 2271036 Author: Sawyer Bergeron Date: Wed Apr 7 15:20:07 2021 +0000 Merge "Restrict image on multi-node booking" commit 2271036232bce7ce4f2136ee6e031e9e77d9c1c2 Author: Sean Smith Date: Tue Apr 6 16:18:36 2021 -0400 Restrict image on multi-node booking Change-Id: I280dba83bceed74195a77b28f3016421c462cc5a Signed-off-by: Sean Smith commit 241ff304980c8bf542fdd1a2a47f73accf912ee1 Merge: f145622 d007fa6 Author: Sawyer Bergeron Date: Tue Apr 6 17:38:49 2021 +0000 Merge "Remove exposure of users on dashboard" commit f1456220fcc098cb0f5e9fc60124680ff8aba6af Merge: 6366b17 3abc7dc Author: Sawyer Bergeron Date: Tue Mar 30 18:01:05 2021 +0000 Merge "Hostname not required" commit 6366b1776bc51c29d4ba4c256f51d16acc52d871 Merge: a35c01b 0573f51 Author: Sawyer Bergeron Date: Tue Mar 30 17:54:21 2021 +0000 Merge "Update actions to free hosts and vlans" commit d007fa661e2faf77e70cb75c8d520941e907ebf7 Author: Sean Smith Date: Wed Mar 24 17:22:54 2021 -0400 Remove exposure of users on dashboard Signed-off-by: Sean Smith Change-Id: I83b93d9247a7eafb54e4a5761d1423a504d86400 commit 3abc7dcc9603cf978e5b7c2762e80998a4a6eef5 Author: Sean Smith Date: Mon Mar 29 15:00:52 2021 -0400 Hostname not required Change-Id: I7d639a17452e426e92671c558b111d6bcec34e8c Signed-off-by: Sean Smith commit a35c01bccec1137ccd8a0f07f832daecb4ee2da3 Author: Sean Smith Date: Mon Mar 29 14:14:48 2021 -0400 Resource display remains on error Signed-off-by: Sean Smith Change-Id: I0a59ef9da908599cf75e379b68b33e280a4deb16 commit 7addb00710a5865a9e6bc38f1d826aa0323ee67e Merge: f6472a3 f6753ae Author: Sawyer Bergeron Date: Mon Mar 29 18:05:56 2021 +0000 Merge "Fixes error on installer fail" commit f6472a3cca58ca428200265acf720174ed36ea7e Merge: 2e9a8c8 e3dec82 Author: Sawyer Bergeron Date: Fri Mar 26 23:26:33 2021 +0000 Merge "Remove duplicate migration" commit 2e9a8c854a93705d080dc54b8c6e3ba36eaba366 Author: Sean Smith Date: Thu Mar 25 15:16:50 2021 -0400 Additional fix for email Signed-off-by: Sean Smith Change-Id: I0599e29c7930386651f77bceccf05d069ebd4101 commit f6753ae2c5f54fb79375214590e565218b1bf0d7 Author: Sean Smith Date: Tue Sep 8 15:13:23 2020 -0400 Fixes error on installer fail Signed-off-by: Sean Smith Change-Id: I823f3c5dd2b7677b428b88e1af4bba78d7dc698c commit e3dec8238442865939bed379aa25219103f4d287 Author: Sean Smith Date: Wed Mar 24 16:09:15 2021 -0400 Remove duplicate migration Signed-off-by: Sean Smith Change-Id: I456b61f3ad25823dbcf2468b303c1e68ff41df7b commit e0a5e206a3a68bbae373bf8f9257b5aba14932c5 Merge: e231274 8559820 Author: Sawyer Bergeron Date: Wed Mar 24 15:56:51 2021 +0000 Merge "Filters non working servers in quick booking" commit e23127473b81bdbc1f4630036ddec3c46ee17dda Merge: b14e617 e817c32 Author: Sawyer Bergeron Date: Wed Mar 24 15:48:39 2021 +0000 Merge "Front end fixes for enforcing hostname" commit e817c3203fde0ca9bfb66a00919050cbdd1179e8 Author: Sean Smith Date: Wed Feb 24 12:18:45 2021 -0500 Front end fixes for enforcing hostname Signed-off-by: Sean Smith Change-Id: I0a077307a2e1bcfef5b31acdaf4ecb73be64fff9 commit 0573f51f18c1e55fe162dbd5a3f9acbd5394e065 Author: Sean Smith Date: Tue Feb 23 15:41:59 2021 -0500 Update actions to free hosts and vlans Signed-off-by: Sean Smith Change-Id: I94425ca5a48cccdf0b5382ef2bb16a989ee6b32d Signed-off-by: Sawyer Bergeron commit b14e617edb86628f31648847c0b96206764ef44c Merge: 13cdfee babedf3 Author: Sawyer Bergeron Date: Tue Mar 23 18:34:16 2021 +0000 Merge "Added LF Edge template overrides." commit babedf30038b453ea7d123c9bb744f4acb14e625 Author: Adam Hassick Date: Tue Mar 16 14:18:31 2021 -0400 Added LF Edge template overrides. Signed-off-by: Adam Hassick Change-Id: If119646eee67fb7e145c5e92e64fa4f28a947dbe commit 13cdfee5d9656c2195bb6238d33d66cb637c22e4 Author: Sawyer Bergeron Date: Wed Mar 3 14:09:35 2021 -0500 Add copyright notice to admin_utils file Signed-off-by: Sawyer Bergeron Change-Id: I4b8a53371c0da274347dcfb7ea969ab4ce403f66 commit 72585921008fc89b5a32f4cb116d5ea9fe1a8ead Merge: 8a902d3 de4cbcb Author: Sawyer Bergeron Date: Wed Mar 17 17:07:40 2021 +0000 Merge "Add documentation to admin_utils functions" commit de4cbcb7b770bd6ae14916715db361e987d1d3ec Author: Sawyer Bergeron Date: Wed Mar 3 14:06:07 2021 -0500 Add documentation to admin_utils functions Signed-off-by: Sawyer Bergeron Change-Id: I27204f49513b3d646776261ed869d9f4d5c6fd64 Signed-off-by: Sawyer Bergeron commit 85598208ad7b65de9e4c60b509331a9b6ce46e52 Author: Sean Smith Date: Mon Mar 8 17:15:42 2021 -0500 Filters non working servers in quick booking Signed-off-by: Sean Smith Change-Id: I47776a3e239a333d544d9e6e86f702b1d299baeb commit 8a902d357fa6eca07384d25fac32a6e0521a63e5 Author: Sean Smith Date: Mon Mar 8 16:21:56 2021 -0500 Release VLAN on booking end Signed-off-by: Sean Smith Change-Id: Ib17fd871501a2af46bb78bbdfc68112a66e763c0 commit a320df5bf04978fc1e73a2e49d1a4bed5b82b849 Merge: 402e6e1 4caae41 Author: Sawyer Bergeron Date: Wed Mar 3 19:07:19 2021 +0000 Merge "Add documentation request functions and use hints to admin utils" commit 4caae41287310cb1309c9b30e2871297d3ed21ef Author: Sawyer Bergeron Date: Wed Mar 3 12:10:02 2021 -0500 Add documentation request functions and use hints to admin utils Signed-off-by: Sawyer Bergeron Change-Id: I3211194fea5dc7d0c3569db6c1d42fe2f4aa53e1 Signed-off-by: Sawyer Bergeron commit 402e6e1111964f28235f6f7ec53ba51c76a8298f Author: Sean Smith Date: Mon Feb 22 13:05:46 2021 -0500 Update celery schedule to send emails Change-Id: I0fbf7080d440cea98d358a30ac5df88ce888a8a0 Signed-off-by: Sean Smith Update celery Change-Id: Icd20d67b561bacbccf1f9d75335f76ebdbef4f1d Signed-off-by: Sean Smith commit db4c2dc4c4f0d86e0a00e8409eed74a0bcffb20b Author: Sawyer Bergeron Date: Mon Feb 8 11:42:41 2021 -0500 Add migration to set rconfig hostname to a valid linux hostname Signed-off-by: Sawyer Bergeron Change-Id: Ib737dc25de37dd9d1c29a4e15f95da0fb68ddfc8 Signed-off-by: Sawyer Bergeron commit 9c200b5a15a336e3a3fc8d7ddac53053bc3b6ffe Author: Sawyer Bergeron Date: Tue Jan 19 12:38:32 2021 -0500 Add booking extension function to admin utils Signed-off-by: Sawyer Bergeron Change-Id: I4efab99e7cb82f69d01de8f74f30047263b4b0c2 Signed-off-by: Sawyer Bergeron commit c77b9169b09b7b53572b45a4f3679e602b8ca6a2 Author: Sawyer Bergeron Date: Mon Jan 11 15:02:03 2021 -0500 Add server create function to admin utils Signed-off-by: Sawyer Bergeron Change-Id: I5776213bfc2eda310049c47dcb1fa1a601bd1896 commit 76b884730a00fecff5ed39d8ff4360941f313fe0 Author: Sawyer Bergeron Date: Mon Jan 11 11:45:23 2021 -0500 Add scripts for creating default templates Signed-off-by: Sawyer Bergeron Change-Id: I522c617ff038531914f3a525b083d47c079489c1 Signed-off-by: Sawyer Bergeron commit d41310f9c3b62e8ab462fa94f2bf1b6b7d2b5e23 Merge: 107d8bb bf82270 Author: Sawyer Bergeron Date: Mon Jan 4 21:15:58 2021 +0000 Merge "Fix issue where users are redirected to Jira login in LFID mode in certain cases" commit bf822708949ad2e57b3966ad2bf485588fe4b29d Author: Adam Hassick Date: Mon Jan 4 20:47:35 2021 +0000 Fix issue where users are redirected to Jira login in LFID mode in certain cases Change-Id: If8a82d3a2628a7f55f902d321388be2419524c8b Signed-off-by: Adam Hassick Change-Id: Ieb9de968514b88a8c450967466d06c9860427d83 commit 107d8bb663083cd2e5fc09417ccd41560bc272c9 Author: Sawyer Bergeron Date: Fri Dec 18 12:16:57 2020 -0500 Reduce log spam: deletion on resource bundles should be deprecated Signed-off-by: Sawyer Bergeron Change-Id: I6bffc393f7a6f93c4b8cd8d4c8bacba0c23fb256 Signed-off-by: Sawyer Bergeron commit 17388941fc3cff40cf2c7e7290e6bad9a27ca578 Author: Sawyer Bergeron Date: Fri Dec 18 10:17:02 2020 -0500 Repair migration chain Signed-off-by: Sawyer Bergeron Change-Id: Iaedcaf76111694bc7a3d8c44bd98f29d508610c5 commit c089784cc3b3708cf89f79962d62de66b009fc05 Author: Sawyer Bergeron Date: Fri Dec 18 09:53:49 2020 -0500 Fix hostname default to be valid linux hostname Signed-off-by: Sawyer Bergeron Change-Id: I898c4e4a03261a1bf0f3f56dbc32104c9b5f3ee9 Signed-off-by: Sawyer Bergeron Change-Id: Ieb010345d5c44356db37f0d4df625686d143a81d Signed-off-by: Sawyer Bergeron --- src/account/forms.py | 5 +- .../migrations/0007_userprofile_pulic_user.py | 18 ++ src/account/migrations/0008_auto_20210324_2106.py | 18 ++ src/account/migrations/0009_auto_20210324_2107.py | 18 ++ src/account/models.py | 4 +- src/account/urls.py | 13 +- src/account/views.py | 7 +- src/booking/forms.py | 20 +- src/booking/lib.py | 2 +- src/booking/quick_deployer.py | 5 +- src/booking/views.py | 3 + src/dashboard/actions.py | 47 ---- src/dashboard/admin_utils.py | 290 +++++++++++++++++---- src/dashboard/tasks.py | 3 +- src/dashboard/views.py | 4 +- src/laas_dashboard/settings.py | 8 +- src/notifier/tasks.py | 2 +- ..._default_name.py => 0017_auto_20201218_1516.py} | 6 +- src/resource_inventory/models.py | 2 +- src/resource_inventory/resource_manager.py | 7 +- src/static/img/lfedge-logo.png | Bin 0 -> 6633 bytes src/static/js/dashboard.js | 77 ++++-- src/templates/akraino/base.html | 24 -- src/templates/akraino/dashboard/landing.html | 23 -- src/templates/base/base.html | 2 + src/templates/base/booking/quick_deploy.html | 33 +-- src/templates/lfedge/base.html | 45 ++++ .../{akraino => lfedge}/booking/booking_table.html | 0 .../{akraino => lfedge}/booking/quick_deploy.html | 2 +- src/templates/lfedge/dashboard/landing.html | 23 ++ src/templates/{akraino => lfedge}/layout.html | 2 +- 31 files changed, 503 insertions(+), 210 deletions(-) create mode 100644 src/account/migrations/0007_userprofile_pulic_user.py create mode 100644 src/account/migrations/0008_auto_20210324_2106.py create mode 100644 src/account/migrations/0009_auto_20210324_2107.py delete mode 100644 src/dashboard/actions.py rename src/resource_inventory/migrations/{0018_manual_change_rconfig_default_name.py => 0017_auto_20201218_1516.py} (71%) create mode 100644 src/static/img/lfedge-logo.png delete mode 100644 src/templates/akraino/base.html delete mode 100644 src/templates/akraino/dashboard/landing.html create mode 100644 src/templates/lfedge/base.html rename src/templates/{akraino => lfedge}/booking/booking_table.html (100%) rename src/templates/{akraino => lfedge}/booking/quick_deploy.html (91%) create mode 100644 src/templates/lfedge/dashboard/landing.html rename src/templates/{akraino => lfedge}/layout.html (71%) diff --git a/src/account/forms.py b/src/account/forms.py index dd1a0a9..28cb27d 100644 --- a/src/account/forms.py +++ b/src/account/forms.py @@ -18,11 +18,12 @@ from account.models import UserProfile class AccountSettingsForm(forms.ModelForm): class Meta: model = UserProfile - fields = ['company', 'email_addr', 'ssh_public_key', 'pgp_public_key', 'timezone'] + fields = ['company', 'email_addr', 'public_user', 'ssh_public_key', 'pgp_public_key', 'timezone'] labels = { 'email_addr': _('Email Address'), 'ssh_public_key': _('SSH Public Key'), - 'pgp_public_key': _('PGP 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') diff --git a/src/account/migrations/0007_userprofile_pulic_user.py b/src/account/migrations/0007_userprofile_pulic_user.py new file mode 100644 index 0000000..6a229e6 --- /dev/null +++ b/src/account/migrations/0007_userprofile_pulic_user.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2 on 2021-03-24 21:06 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0006_auto_20201109_1947'), + ] + + operations = [ + migrations.AddField( + model_name='userprofile', + name='pulic_user', + field=models.BooleanField(default=False), + ), + ] diff --git a/src/account/migrations/0008_auto_20210324_2106.py b/src/account/migrations/0008_auto_20210324_2106.py new file mode 100644 index 0000000..9ff513d --- /dev/null +++ b/src/account/migrations/0008_auto_20210324_2106.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2 on 2021-03-24 21:06 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0007_userprofile_pulic_user'), + ] + + operations = [ + migrations.RenameField( + model_name='userprofile', + old_name='pulic_user', + new_name='public_user', + ), + ] diff --git a/src/account/migrations/0009_auto_20210324_2107.py b/src/account/migrations/0009_auto_20210324_2107.py new file mode 100644 index 0000000..baa7382 --- /dev/null +++ b/src/account/migrations/0009_auto_20210324_2107.py @@ -0,0 +1,18 @@ +# Generated by Django 2.2 on 2021-03-24 21:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('account', '0008_auto_20210324_2106'), + ] + + operations = [ + migrations.AlterField( + model_name='userprofile', + name='public_user', + field=models.BooleanField(default=False), + ), + ] diff --git a/src/account/models.py b/src/account/models.py index 40de4d8..b71f0ac 100644 --- a/src/account/models.py +++ b/src/account/models.py @@ -54,6 +54,8 @@ class UserProfile(models.Model): full_name = models.CharField(max_length=100, null=True, blank=True, default='') booking_privledge = models.BooleanField(default=False) + public_user = models.BooleanField(default=False) + class Meta: db_table = 'user_profile' @@ -237,7 +239,7 @@ class Lab(models.Model): def get_available_resources(self): # Cannot import model normally due to ciruclar import Server = apps.get_model('resource_inventory', 'Server') # TODO: Find way to import ResourceQuery - resources = [str(resource.profile) for resource in Server.objects.filter(lab=self, booked=False)] + resources = [str(resource.profile) for resource in Server.objects.filter(lab=self, working=True, booked=False)] return dict(Counter(resources)) def __str__(self): diff --git a/src/account/urls.py b/src/account/urls.py index 0c01ee0..97d8c77 100644 --- a/src/account/urls.py +++ b/src/account/urls.py @@ -32,6 +32,7 @@ from account.views import ( AccountSettingsView, JiraAuthenticatedView, JiraLoginView, + OIDCLoginView, JiraLogoutView, UserListView, account_resource_view, @@ -45,11 +46,21 @@ from account.views import ( configuration_delete_view ) +from laas_dashboard import settings + + +def get_login_view(): + if (settings.AUTH_SETTING == 'LFID'): + return OIDCLoginView.as_view() + else: + return JiraLoginView.as_view() + + app_name = "account" urlpatterns = [ url(r'^settings/', AccountSettingsView.as_view(), name='settings'), url(r'^authenticated/$', JiraAuthenticatedView.as_view(), name='authenticated'), - url(r'^login/$', JiraLoginView.as_view(), name='login'), + url(r'^login/$', get_login_view(), name='login'), url(r'^logout/$', JiraLogoutView.as_view(), name='logout'), url(r'^users/$', UserListView.as_view(), name='users'), url(r'^my/resources/$', account_resource_view, name="my-resources"), diff --git a/src/account/views.py b/src/account/views.py index 08da918..b74126e 100644 --- a/src/account/views.py +++ b/src/account/views.py @@ -128,6 +128,11 @@ class JiraLoginView(RedirectView): return url +class OIDCLoginView(RedirectView): + def get_redirect_url(self, *args, **kwargs): + return reverse('oidc_authentication_init') + + class JiraLogoutView(LoginRequiredMixin, RedirectView): def get_redirect_url(self, *args, **kwargs): logout(self.request) @@ -204,7 +209,7 @@ class UserListView(TemplateView): template_name = "account/user_list.html" def get_context_data(self, **kwargs): - users = User.objects.all() + 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 diff --git a/src/booking/forms.py b/src/booking/forms.py index 2a8784f..cbc3407 100644 --- a/src/booking/forms.py +++ b/src/booking/forms.py @@ -21,7 +21,7 @@ from booking.lib import get_user_items, get_user_field_opts class QuickBookingForm(forms.Form): purpose = forms.CharField(max_length=1000) project = forms.CharField(max_length=400) - hostname = forms.CharField(max_length=400) + hostname = forms.CharField(required=False, max_length=400) installer = forms.ModelChoiceField(queryset=Installer.objects.all(), required=False) scenario = forms.ModelChoiceField(queryset=Scenario.objects.all(), required=False) @@ -35,12 +35,20 @@ class QuickBookingForm(forms.Form): super(QuickBookingForm, self).__init__(data=data, **kwargs) + image_help_text = 'Image can be set only for single-node bookings. For multi-node bookings set image through Design a POD.' self.fields["image"] = forms.ModelChoiceField( Image.objects.filter(public=True) | Image.objects.filter(owner=user), required=False ) + self.fields['image'].widget.attrs.update({ + 'class': 'has-popover', + 'data-content': image_help_text, + 'data-placement': 'bottom', + 'data-container': 'body' + }) + self.fields['users'] = SearchableSelectMultipleField( - queryset=UserProfile.objects.select_related('user').exclude(user=user), + queryset=UserProfile.objects.filter(public_user=True).select_related('user').exclude(user=user), items=get_user_items(exclude=user), required=False, **get_user_field_opts() @@ -59,6 +67,14 @@ class QuickBookingForm(forms.Form): self.fields['filter_field'] = MultipleSelectFilterField(widget=MultipleSelectFilterWidget(**lab_data)) + hostname_help_text = 'Hostname can be set only for single-node bookings. For multi-node bookings set hostname through Design a POD.' + self.fields['hostname'].widget.attrs.update({ + 'class': 'has-popover', + 'data-content': hostname_help_text, + 'data-placement': 'top', + 'data-container': 'body' + }) + def build_user_list(self): """ Build list of UserProfiles. diff --git a/src/booking/lib.py b/src/booking/lib.py index 8132c75..7a4c261 100644 --- a/src/booking/lib.py +++ b/src/booking/lib.py @@ -23,7 +23,7 @@ def get_user_field_opts(): def get_user_items(exclude=None): - qs = UserProfile.objects.select_related('user').exclude(user=exclude) + qs = UserProfile.objects.filter(public_user=True).select_related('user').exclude(user=exclude) items = {} for up in qs: item = { diff --git a/src/booking/quick_deployer.py b/src/booking/quick_deployer.py index 8b3af6c..0a3bfc6 100644 --- a/src/booking/quick_deployer.py +++ b/src/booking/quick_deployer.py @@ -111,7 +111,8 @@ def update_template(old_template, image, hostname, user): profile=old_config.profile, image=image_to_set, template=template, - is_head_node=old_config.is_head_node + is_head_node=old_config.is_head_node, + name=hostname if len(old_template.getConfigs()) == 1 else old_config.name ) for old_iface_config in old_config.interface_configs.all(): @@ -204,7 +205,7 @@ def create_from_form(form, request): purpose_field = form.cleaned_data['purpose'] project_field = form.cleaned_data['project'] users_field = form.cleaned_data['users'] - hostname = form.cleaned_data['hostname'] + hostname = 'opnfv_host' if not form.cleaned_data['hostname'] else form.cleaned_data['hostname'] length = form.cleaned_data['length'] image = form.cleaned_data['image'] diff --git a/src/booking/views.py b/src/booking/views.py index c41a7d6..66cb594 100644 --- a/src/booking/views.py +++ b/src/booking/views.py @@ -63,9 +63,12 @@ def quick_create(request): return redirect(reverse('booking:booking_detail', kwargs={'booking_id': booking.id})) except Exception as e: messages.error(request, "Whoops, an error occurred: " + str(e)) + context.update(drop_filter(request.user)) return render(request, 'booking/quick_deploy.html', context) else: messages.error(request, "Looks like the form didn't validate. Check that you entered everything correctly") + context['status'] = 'false' + context.update(drop_filter(request.user)) return render(request, 'booking/quick_deploy.html', context) diff --git a/src/dashboard/actions.py b/src/dashboard/actions.py deleted file mode 100644 index 44b1fdd..0000000 --- a/src/dashboard/actions.py +++ /dev/null @@ -1,47 +0,0 @@ -############################################################################## -# Copyright (c) 2019 Parker Berberian, Sawyer Bergeron, and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -from resource_inventory.models import Host, Vlan -from account.models import Lab -from booking.models import Booking -from datetime import timedelta -from django.utils import timezone - - -def free_leaked_hosts(free_old_bookings=False, old_booking_age=timedelta(days=1)): - bundles = [booking.resource for booking in Booking.objects.filter(end__gt=timezone.now())] - active_hosts = set() - for bundle in bundles: - active_hosts.update([host for host in bundle.hosts.all()]) - - marked_hosts = set(Host.objects.filter(booked=True)) - - for host in (marked_hosts - active_hosts): - host.booked = False - host.save() - - -def free_leaked_public_vlans(): - booked_host_interfaces = [] - - for lab in Lab.objects.all(): - - for host in Host.objects.filter(booked=True).filter(lab=lab): - for interface in host.interfaces.all(): - booked_host_interfaces.append(interface) - - in_use_vlans = Vlan.objects.filter(public=True).distinct('vlan_id').filter(interface__in=booked_host_interfaces) - - manager = lab.vlan_manager - - for vlan in Vlan.objects.all(): - if vlan not in in_use_vlans: - if vlan.public: - manager.release_public_vlan(vlan.vlan_id) - manager.release_vlans(vlan) diff --git a/src/dashboard/admin_utils.py b/src/dashboard/admin_utils.py index 6d990c9..186a64f 100644 --- a/src/dashboard/admin_utils.py +++ b/src/dashboard/admin_utils.py @@ -1,3 +1,12 @@ +############################################################################## +# Copyright (c) 2021 Sawyer Bergeron and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + from resource_inventory.models import ( ResourceTemplate, Image, @@ -17,10 +26,16 @@ from resource_inventory.models import ( ) import json +import sys +import inspect +import pydoc from django.contrib.auth.models import User -from account.models import Lab +from account.models import ( + Lab, + PublicNetwork +) from resource_inventory.resource_manager import ResourceManager from resource_inventory.pdf_templater import PDFTemplater @@ -39,12 +54,37 @@ from api.models import JobStatus def print_div(): - print("====================================================================") + """ + Utility function for printing dividers, does nothing directly useful as a utility + """ + print("=" * 68) def book_host(owner_username, host_labid, lab_username, hostname, image_id, template_name, length_days=21, collaborator_usernames=[], purpose="internal", project="LaaS"): """ creates a quick booking using the given host + + @owner_username is the simple username for the user who will own the resulting booking. + Do not set this to a lab username! + + @image_id is the django id of the image in question, NOT the labid of the image. + Query Image objects by their public status and compatible host types + + @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object + + @lab_username for iol is `unh_iol`, other labs will be documented here + + @hostname the hostname that the resulting host should have set + + @template_name the name of the (public, or user accessible) template to use for this booking + + @length_days how long the booking should be, no hard limit currently + + @collaborator_usernames a list of usernames for collaborators to the booking + + @purpose what this booking will be used for + + @project what project/group this booking is on behalf of or the owner represents """ lab = Lab.objects.get(lab_user__username=lab_username) host = Server.objects.filter(lab=lab).get(labid=host_labid) @@ -113,6 +153,16 @@ def book_host(owner_username, host_labid, lab_username, hostname, image_id, temp def mark_working(host_labid, lab_username, working=True): + """ + Mark a host working/not working so that it is either bookable or hidden in the dashboard. + + @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object + + @lab_username: param of the form `unh_iol` or similar + + @working: bool, whether by the end of execution the host should be considered working or not working + """ + lab = Lab.objects.get(lab_user__username=lab_username) server = Server.objects.filter(lab=lab).get(labid=host_labid) print("changing server working status from ", server.working, "to", working) @@ -121,6 +171,16 @@ def mark_working(host_labid, lab_username, working=True): def mark_booked(host_labid, lab_username, booked=True): + """ + Mark a host as booked/unbooked + + @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object + + @lab_username: param of the form `unh_iol` or similar + + @working: bool, whether by the end of execution the host should be considered booked or not booked + """ + lab = Lab.objects.get(lab_user__username=lab_username) server = Server.objects.filter(lab=lab).get(labid=host_labid) print("changing server booked status from ", server.booked, "to", booked) @@ -128,13 +188,26 @@ def mark_booked(host_labid, lab_username, booked=True): server.save() -# returns host filtered by lab and then unique id within lab def get_host(host_labid, lab_username): + """ + Returns host filtered by lab and then unique id within lab + + @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object + + @lab_username: param of the form `unh_iol` or similar + """ lab = Lab.objects.get(lab_user__username=lab_username) return Server.objects.filter(lab=lab).get(labid=host_labid) def get_info(host_labid, lab_username): + """ + Returns various information on the host queried by the given parameters + + @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object + + @lab_username: param of the form `unh_iol` or similar + """ info = {} host = get_host(host_labid, lab_username) info['host_labid'] = host_labid @@ -199,8 +272,16 @@ def detect_leaked_hosts(labid="unh_iol"): return filtered -def booking_for_host(host_labid: str, labid="unh_iol"): - server = Server.objects.get(lab__lab_user__username=labid, labid=host_labid) +def booking_for_host(host_labid: str, lab_username="unh_iol"): + """ + Returns the booking that this server is a part of, if any. + Fails with an exception if no such booking exists + + @host_labid is usually of the form `hpe3` or similar, is the labid of the Server (subtype of Resource) object + + @lab_username: param of the form `unh_iol` or similar + """ + server = Server.objects.get(lab__lab_user__username=lab_username, lab_username=host_labid) booking = server.bundle.booking_set.first() print_div() print(booking) @@ -211,7 +292,15 @@ def booking_for_host(host_labid: str, labid="unh_iol"): return booking -def force_release_booking(booking_id): +def force_release_booking(booking_id: int): + """ + Takes a booking id and forces the booking to end whether or not the tasks have + completed normally. + + Use with caution! Hosts may or may not be released depending on other underlying issues + + @booking_id: the id of the Booking object to be released + """ booking = Booking.objects.get(id=booking_id) job = booking.job tasks = job.get_tasklist() @@ -220,7 +309,28 @@ def force_release_booking(booking_id): task.save() +def free_leaked_public_vlans(safety_buffer_days=2): + for lab in Lab.objects.all(): + current_booking_set = Booking.objects.filter(end__gte=timezone.now() + timedelta(days=safety_buffer_days)) + + marked_nets = set() + + for booking in current_booking_set: + for network in get_network_metadata(booking.id): + marked_nets.add(network["vlan_id"]) + + for net in PublicNetwork.objects.filter(lab=lab).filter(in_use=True): + if net.vlan not in marked_nets: + lab.vlan_manager.release_public_vlan(net.vlan) + + def get_network_metadata(booking_id: int): + """ + Takes a booking id and prints all (known) networks that are owned by it. + Returns an object of the form {: {"vlan_id": int, "netname": str , "public": bool : { - capacity: int (GiB) - media_type: str ("SSD" or "HDD") - interface: str ("sata", "sas", "ssd", "nvme", "scsi", or "iscsi") +def add_profile(data): + """ + Used for adding a host profile to the dashboard + + schema (of dict passed as "data" param): + { + "name": str + "description": str + "labs": [ + str (lab username) + ] + "disks": { + : { + capacity: int (GiB) + media_type: str ("SSD" or "HDD") + interface: str ("sata", "sas", "ssd", "nvme", "scsi", or "iscsi") + } } - } - interfaces: { - : { - "speed": int (mbit) - "nic_type": str ("onboard" or "pcie") - "order": int (compared to the other interfaces, indicates the "order" that the ports are laid out) + interfaces: { + : { + "speed": int (mbit) + "nic_type": str ("onboard" or "pcie") + "order": int (compared to the other interfaces, indicates the "order" that the ports are laid out) + } + } + cpus: { + cores: int (hardware threads count) + architecture: str (x86_64" or "aarch64") + cpus: int (number of sockets) + cflags: str + } + ram: { + amount: int (GiB) + channels: int } } - cpus: { - cores: int (hardware threads count) - architecture: str (x86_64" or "aarch64") - cpus: int (number of sockets) - cflags: str - } - ram: { - amount: int (GiB) - channels: int - } -} -""" - - -def add_profile(data): + """ base_profile = ResourceProfile.objects.create(name=data['name'], description=data['description']) base_profile.save() @@ -303,6 +417,11 @@ def add_profile(data): def make_default_template(resource_profile, image_id=None, template_name=None, connected_interface_names=None, interfaces_tagged=False, connected_interface_tagged=False, owner_username="root", lab_username="unh_iol", public=True, temporary=False, description=""): + """ + Do not call this function without reading the related source code, it may have unintended effects. + + Used for creating a default template from some host profile + """ if not resource_profile: raise Exception("No viable continuation from none resource_profile") @@ -349,16 +468,27 @@ def make_default_template(resource_profile, image_id=None, template_name=None, c connection.save() -""" -Note: interfaces should be dict from interface name (eg ens1f0) to dict of schema: - { - mac_address: , - bus_addr: , //this field is optional, "" is default - } -""" +def add_server(profile, name, interfaces, lab_username="unh_iol", vendor="unknown", model="unknown"): + """ + Used to enroll a new host of some profile + @profile: the ResourceProfile in question (by reference to a model object) + + @name: the unique name of the server, currently indistinct from labid + + @interfaces: interfaces should be dict from interface name (eg ens1f0) to dict of schema: + { + mac_address: , + bus_addr: , //this field is optional, "" is default + } -def add_server(profile, uname, interfaces, lab_username="unh_iol", vendor="unknown", model="unknown"): + @lab_username: username of the lab to be added to + + @vendor: vendor name of the host, such as "HPE" or "Gigabyte" + + @model: specific model of the host, such as "DL380 Gen 9" + + """ server = Server.objects.create( bundle=None, profile=profile, @@ -366,9 +496,9 @@ def add_server(profile, uname, interfaces, lab_username="unh_iol", vendor="unkno working=True, vendor=vendor, model=model, - labid=uname, + labid=name, lab=Lab.objects.get(lab_user__username=lab_username), - name=uname, + name=name, booked=False) for iface_prof in InterfaceProfile.objects.filter(host=profile).all(): @@ -382,3 +512,63 @@ def add_server(profile, uname, interfaces, lab_username="unh_iol", vendor="unkno server.interfaces.add(iface) server.save() + + +def extend_booking(booking_id, days=0, hours=0, minutes=0, weeks=0): + """ + Extend a booking by n + + @booking_id: id of the booking + + @days/@hours/@minutes/@weeks: the cumulative amount of delta to add to the length of the booking + """ + + booking = Booking.objects.get(id=booking_id) + booking.end = booking.end + timedelta(days=days, hours=hours, minutes=minutes, weeks=weeks) + booking.save() + + +def docs(function=None, fulltext=False): + """ + Print documentation for a given function in admin_utils. + Call without arguments for more information + """ + + fn = None + + if isinstance(function, str): + try: + fn = globals()[function] + except KeyError: + print("Couldn't find a function by the given name") + return + elif callable(function): + fn = function + else: + print("docs(function: callable | str, fulltext: bool) was called with a 'function' that was neither callable nor a string name of a function") + print("usage: docs('some_function_in_admin_utils', fulltext=True)") + print("The 'fulltext' argument is used to choose if you want the complete source of the function printed. If this argument is false then you will only see the pydoc rendered documentation for the function") + return + + if not fn: + print("couldn't find a function by that name") + + if not fulltext: + print("Pydoc documents the function as such:") + print(pydoc.render_doc(fn)) + else: + print("The full source of the function is this:") + print(inspect.getsource(fn)) + + +def admin_functions(): + """ + List functions available to call within admin_utils + """ + + return [name for name, func in inspect.getmembers(sys.modules[__name__]) if (inspect.isfunction(func) and func.__module__ == __name__)] + + +print("Hint: call `docs()` or `admin_functions()` for help on using the admin utils") +print("docs() displays documentation on a given function") +print("admin_functions() lists all functions available to call within this module") diff --git a/src/dashboard/tasks.py b/src/dashboard/tasks.py index 8554f6c..3f88449 100644 --- a/src/dashboard/tasks.py +++ b/src/dashboard/tasks.py @@ -83,8 +83,9 @@ def free_hosts(): job__complete=True, resource__isnull=False ) + for booking in bookings: - ResourceManager.getInstance().deleteResourceBundle(booking.resource) + ResourceManager.getInstance().releaseResourceBundle(booking.resource) @shared_task diff --git a/src/dashboard/views.py b/src/dashboard/views.py index 7c85250..ff26c64 100644 --- a/src/dashboard/views.py +++ b/src/dashboard/views.py @@ -22,7 +22,7 @@ from booking.models import Booking from resource_inventory.models import Image, ResourceProfile, ResourceQuery from workflow.workflow_manager import ManagerTracker -import os +from laas_dashboard import settings def lab_list_view(request): @@ -80,7 +80,7 @@ def landing_view(request): else: bookings = None - LFID = True if os.environ['AUTH_SETTING'] == 'LFID' else False + LFID = True if settings.AUTH_SETTING == 'LFID' else False return render( request, 'dashboard/landing.html', diff --git a/src/laas_dashboard/settings.py b/src/laas_dashboard/settings.py index a32b1c5..6b3ed09 100644 --- a/src/laas_dashboard/settings.py +++ b/src/laas_dashboard/settings.py @@ -53,7 +53,9 @@ MIDDLEWARE = [ 'account.middleware.TimezoneMiddleware', ] -if os.environ['AUTH_SETTING'] == 'LFID': +AUTH_SETTING = os.environ.get('AUTH_SETTING', 'JIRA') + +if AUTH_SETTING == 'LFID': AUTHENTICATION_BACKENDS = ['account.views.MyOIDCAB'] # OpenID Authentications @@ -238,6 +240,10 @@ CELERYBEAT_SCHEDULE = { 'query_vpn_users': { 'task': 'dashboard.tasks.query_vpn_users', 'schedule': timedelta(hours=1) + }, + 'dispatch_emails': { + 'task': 'notifier.tasks.dispatch_emails', + 'schedule': timedelta(minutes=10) } } diff --git a/src/notifier/tasks.py b/src/notifier/tasks.py index 389750a..64d7574 100644 --- a/src/notifier/tasks.py +++ b/src/notifier/tasks.py @@ -47,5 +47,5 @@ def dispatch_emails(): email.title, email.message, os.environ.get("DEFAULT_FROM_EMAIL", "opnfv@laas-dashboard"), - email.recipient, + [email.recipient], fail_silently=False) diff --git a/src/resource_inventory/migrations/0018_manual_change_rconfig_default_name.py b/src/resource_inventory/migrations/0017_auto_20201218_1516.py similarity index 71% rename from src/resource_inventory/migrations/0018_manual_change_rconfig_default_name.py rename to src/resource_inventory/migrations/0017_auto_20201218_1516.py index b3459bf..d4884de 100644 --- a/src/resource_inventory/migrations/0018_manual_change_rconfig_default_name.py +++ b/src/resource_inventory/migrations/0017_auto_20201218_1516.py @@ -1,14 +1,18 @@ +# Generated by Django 2.2 on 2020-12-18 15:16 + from django.db import migrations, models class Migration(migrations.Migration): + dependencies = [ ('resource_inventory', '0016_auto_20201109_1947'), ] + operations = [ migrations.AlterField( model_name='resourceconfiguration', name='name', - field=models.CharField(default='opnfv-host') + field=models.CharField(default='opnfv_host', max_length=3000), ), ] diff --git a/src/resource_inventory/models.py b/src/resource_inventory/models.py index e2f2fea..7fe479a 100644 --- a/src/resource_inventory/models.py +++ b/src/resource_inventory/models.py @@ -233,7 +233,7 @@ class ResourceConfiguration(models.Model): image = models.ForeignKey("Image", on_delete=models.PROTECT) template = models.ForeignKey(ResourceTemplate, related_name="resourceConfigurations", null=True, on_delete=models.CASCADE) is_head_node = models.BooleanField(default=False) - name = models.CharField(max_length=3000, default="") + name = models.CharField(max_length=3000, default="opnfv_host") def __str__(self): return str(self.name) diff --git a/src/resource_inventory/resource_manager.py b/src/resource_inventory/resource_manager.py index 140cc09..9406977 100644 --- a/src/resource_inventory/resource_manager.py +++ b/src/resource_inventory/resource_manager.py @@ -64,9 +64,10 @@ class ResourceManager: # public interface def deleteResourceBundle(self, resourceBundle): - for resource in resourceBundle.get_resources(): - resource.release() - resourceBundle.delete() + raise NotImplementedError("Resource Bundle Deletion Not Implemented") + + def releaseResourceBundle(self, resourceBundle): + resourceBundle.release() def get_vlans(self, resourceTemplate): networks = {} diff --git a/src/static/img/lfedge-logo.png b/src/static/img/lfedge-logo.png new file mode 100644 index 0000000000000000000000000000000000000000..689f09a4244a66897c362309cc143c5519e18aaa GIT binary patch literal 6633 zcmV zaB^>EX>4U6ba`-PAZ2)IW&i+q+O3#da^$)ZME|i0F9EMY0n5QN!aMNteF~%`OEYuh z+F^A_O|eN3sLaYtwA=ss=W+kwCk0=ijp5>pSP=>jr%X;=XU^oz8ch`VJI62fjbao@~eOb@d%6#pir@|99=Z zujl_*&ifnZJM267&;PShj3tbhqKi9)bxiZhE%vHB;h#TzbUof*Uo(& z{wQ87zptI|?-l8LDV+bfq5P=Pdp^jI^XU_{_#L&6!}$FJzx)TDztrxWRnOVa>Uu6z zsG-_6a$x(8CG)zRxh3Vvaj1 z?=em=uyZwOY;h)oAloaPsHvspQU#9*YKhbFb1vaNx83K>P9XXl^R2)f<@C=t&VCKD0}_yVy*30`B=5U67XpU%u<%5i`|5Q!_e12&0H z&{(5An|n0eYoo;HG|~bPl1fsbX{iHN&H{flS2)xqNp>PqM5RiTUQ)?LN-0`Qqq-)| zDq2*vYSUg*%~fitT5D~!x7ZRACapAet+v*B=b@dUJ9l@k=zYYIMjkTC&{0PleL|m^ zr_3^S*4buXaivB3ud>vwy4vbHY^b#JmR+{)y4&stuAOx9kyDPIdfMrqsJ*CuM=kt7 z?ype`FKUKZzSrp&YMfU67$TUQh+zg|F$WM=LjXcY!_3!^b2Q`(Gv6XjAraJq7&t2! zVj!4zh;qUwc3;T-C2mgge}!B86LQX=`+p$k47wM&Uvc{lYO7yINf#jNLaV7y2Vvu) z9xG_esY?$Z&8*v>RW#3`sMDTnWJ%M{d0JiJXz*+422`{y>_vU>wc3t#^B89;hnXUu zQ;|Bg^R&{k_ZjSOZL(uMp=e!mvy8EO*t&Xk1DBT7cJA@a>HAJi#Or9?<~sXns{5); zD|5&>N-a*K9-wsMKC_5D!nL?s8E5ZB0g~NKp{uQx!r-r*6aJ?@drWOLpHJIc1;*jHjA4sP5r@#o$rE6#_$NOZAH4xF#ZRu*$k~VGym^J~X@M!p}xvWUa5ksi=MFY$^ z$CxowR3;#^h2~~Ri~c!al`p_~rI}a-o&@P30$q2EVJ$FU77L1$*%&bQD)+%m%hk13 z?@}?t72#2cB}^z0NG8zX&sfPmgmn*fol-3Q2}ItDyH zdqNm#+Zq8;8+R^!XW)%~Om{-sHFaGyF;4=S66jG+Pc5d17WFwOW@tWdrP{h02%MZ( zF7zDAZ?~vJ2)Qau!a>~>g4rji&&;UfrJ4!KrRuO~5YV2P3()b{O?4{dJSDg^W=azX zil8g@NLm8{qTsA~@fnE^73jLHn5LA(zz(HaMB;W&3q+8SDmu}i03Jl{rVHHq(Y(g2 zgb*@{l?cToAvIa_6RV=`A)&tw+6w=ln%qJEOrdF^yp{;TH7*RJp2%7$kYt!}q%SCf zAky5ZW$U5Bn5IC?TA>7iy?{}pX|jhYr0s!lr!snxmKEVvqBHdme#RaV#~}5n?9`7R zC@XSORw`~l5C+A(Qh_ffoh)3_c~B2fz!4<_5~_gliJI^dP+M1WaBn2gX-t++BoICv&t zPKv?lv{DAT`g((ZZP&IF3W+|!swMB3f~;GF4Wdu|Er&90G>Ja)u9q4B(* z*dFlK2+zOfgH@Rh(%84_Ay>Ue0!qxOW&>iQKx5wXezPq-Vc0Tdx-!^g80nY-Jr%bY zBxEA(W&l%$AcSY62cwp0O^nhb=dBAk*@|jpMt6-9?y)?qYKUQg!-kBjBAKh8yaI`K zp<9c$El(kD0T51y950|=#jz?(_U}6UpAFvq9fu!HB_5B-l_PabWDmw;J84@jC`BQ$ zZJKZ4%$R=0=nWcfpW$LeflIxTi@0G97Kemo7n^&6Fv-{#uti6Q9+5q1$fbGH-_{)4 zSIUD078D@Xm1FNHs(pW8+4bfFxDFMAd9hexgS=^=Egf|XHBxW#$Eb)Ya)2@(|3oZ7 zlG1G@YCP*bXKI2jZ$g~=$QqC9vlY6=y@EvR_38hV4U~S{@Fmccl#S?zh^~QR!lzUsqWh$jmGWo$8n55#4 ze(p}_*%L=>iRoA%M7`oPwPKfaNc#gv+7Zq~vljdgX;|5{@$oboQ*(Yg|2}7FGy>93 zHwah{yg}f`0K*n@+=_9HcCsV$jE`THG}fiH~0riFOj%aHEKa1s!NL4+v;(iX^S5!;Fp z^_lD)4+)CWCM#Fk)(_Y=PKM%A*1dP8t8m3dku`uX9mbJ4raXqDPwg#vg zJEeq_@I0CWq5^M`7$Gl#oPm_J0xp|w9F3SROlbiCvLpz5lns`$55a=2#w~ib%_vz2ICyBpcMx{+f@7D2jL}wne0IMr zLBgf74c?*}jXacx39vj`nS;%r>;~UGHf7&&mE&j~;w}5^lsbyc>V<6t?p4|Ro&oLR zsh8Iu&d$-}?T@poPQ=Z-xnU6fK8X61W2^|4aEHbFY64}ti?3rqG~Fsl&r&mWB#Z3A zj0(e#V_*BQei9T35>xh%%CLPuH{2JC+}o1(ofYnm)YNO($+~WPJ3}OJZ^PO}#r8x@ zpxZ~!xoaDz*d{>i>s|SHR|-~FTi@t$Zr49}Z})73EV zD-vMc?(Tv)#?ouJ_HF$EG4GZIW#L-3Y0qXkw@Hb5yBWx4XWwhH&x5$`+^^L8`#tRN z&n?fDaouYbrLiEVJP(H=qY5QbZ3UqeGNEnqov9vnX6eJ6)Fbvb!K`X7i2LcJ_nXkl`h_!7JBekI}np^)%j7Dn~A@vD)VKK^6*}g@3t! z1Nq@{>iEh%`Tzg`24YJ`L;(K){{a7>y{D4^000SaNLh0L04^f{04^f|c%?sf00007 zbV*G`2jv406B#f*U5P^g01YuoL_t(|+U=Zsa8z}c$3MT@x0Bcj5jzl`yC`Z#R}x5M z8Ckc65X%`yX4YYsVj+%;tge*0af{U&t6U$TRSy1P&FD%%r6?=n&L~#KnnA~Phjn1X zYXe9C9m2yvcuYb}XcF@1?({wT$33l0=uWzuz`&AIbt{kl-QV+j-sjhd$bJzyEh6m~ z;b{?(pNdE>eaLYfL}a3fZ0=`1r$l6xh~!5i5mKRT+qNMhwusCYk;Xw@SCfc5DdMnvusk*7sut%&T^bB&JmUn26di2PVYz9k}AB4X<6 z>+yIzXZ5>y@nS^85|P`qpYy@tDG_OqKe+6Fobh_Su_YUL$~f;8tyS6xax? zvn*@tvSrK8%$qk)N=ix)IeQ3W3_Eu0pt!i$==^U3W&_^=#_Aj#eE_ZjzO7FH*be*# z__&~;pt-HB&GGyF#1Tf8u4S0cF_gw0MF7Xv4L4jmWX|*RWZoU@H7DEG7T^~^XBuOC z1soF*QAUZ7FvWlG!n_3Ndqtn93ppMCb(D40h8 zbAW%;J~|bwb-+HLRY837 zVE87RA-Y&3Km+hA;8dFLM*@KW*IaW=?El+=Pl1{kNg2y%QSFhf!1_LLH&6&X0eFC4 zmX?;D&dkgt7<6%T>#eu)+;h)GrS84JKLeu`#J>do6KGP28i|p#naTh&fceTe3w4}- zA2w{*u}zyc$+T(H5=q%FRH}ESA*IFcM|}6DsTyO|i1!kc{(ow6fZo>D)^MsXo<4n= zS+i#G%rnnWUS1y06brYuwmN-HhK!61va+&zJRVQGF{T;V1JnZl0!#%K0gc9(-!(Qi zhDVJWg)xRLTee_~xeB-o_$LMP2HlAWC$_IkaY zz`F|03gB;mdBB&z+r`Djoqe=SEYmtbZ+CZhZ*g&P${TDa==2cqiE@9sD)?K#ooW|d zj?(mkwI4BJMEVnx#6wM=KAi&x4xD=!#$b$L`SRt?qD70^j4{8}kCx~;l>?uT969nR zAltWZ$LsZ4YCe<$*8w|$p9BBnI8N}f#~!1yvhwTsy!F;wUyn6w)-0*6uI}-Ay`Skj ze&7z^?}4qhZP%5Tm&-uwtgo*hG(hhMma7Aij+C~69|F@ZLqJD>KLTwjti4;+uX9Oq zrwAg#!w)}<&*$U5`|j&9#(b(~$76aTKQx{FRzWYk-08l3`)oDph0Bk+)p1(~gi~aKoM_&0SLVyOjaa~vlvv45FU1UL+AQ?EN&DJbD^n7X>UOMZc6Wo3lJVaKwp{pzR| zD9}c#t!-(P;udwzJ_Sy6b#=)bZ@iK8I4`~Q5=)mZmCVe{X5cLab|Vl@HII-Apf@Er zpUCdryRSsY&C>AGm?Qz+33Rlyw8*GYqb?86)Nwd*;soQzj}Iv0mIC8|5yqGX+_?E) z19=L-JwWLF_uuc=hL1+_`f}Vh=FJFmmKb3JVJpX~*ku zGm$+hMww1}Jf1_gs@LIt0G-X19Y6NylWf~$_R6R>EJ-mMBEVkYC&rlFgXL%+K72UZ zcejEk6Szv}X)9y*0%w58@ZrPb_VeP4FLLj__Zr1aGIIjZ(89lPJUiuv>Bqm1xlU{Vr* zJ`GeGV|FH3Q&9KFNfFT9^phzZqQNK8@YgG8m>gZe5lPjk?1#pT858SAMT?B0+2Lr$ z&Jd^QDwcdcU(`(d4sIgN>c`p6!mT~o4eW{o+90qN_>UA#4si1Zr-lHXnByZ!woA4| zK-AveB_eIQ`W>BUAEzCFd6TSMkjay~s2?PSfGdlu-0Qvd( zL7*Bp)|?3Z`M}?5pNT;40LOvPL?q=}j$kmz5TMVc5N@qQeG;IKXxmUK!qc+SuhbnZ zKu?@Fk(h`^hMG0eFlDD|5ev6WG&eUlH;|K)BWKQ>Ijf(SUw)aDD^~`MF~1(b@3;jw zc{45v#+UByZpqHhPAhCTMCYo#U#r?nri8H@=x%FkyQ~&DY0{*q6LynI=?=AT9R>4o zW!Rg5YmG5SUwiE}x&QwA&+6yRH{YbUw^u4EDnju9XpG^88*VVT8AsoRUf1OcXk%Gc zQgKSHV@McsN}%>SfRlUo?hU>0!V3eM0>_UZr?$5C@(JO1Jd7Sa8r*R0GzIoCpjGRw zdvME9=K!PB+1oZ}&YYfw3m2Z1ToDmguU^gS)vM!3Tc{QUe23S`_GgQ+B0Q*4asF~)qEo12?*4RRYgAWEQP%_4_YSk(f6Su@@f{Si)7+@mqErYHk`Zx`IVp-N9+^aqFbd#>bP1+2$ zgdUzkzoK6HFs|T4kuioFZ@iHeD^@Ub=1hz+*&1T|kz!yo@P&v(HgDd{?%lg7DJkg! zK2`}`ihFD2fMr=VCr+FQR#a55V#SKMwH*Zo1yom8+j6WOl;f6 zwrwoS!Wd(#-TXb=D^=HOsO=X(b3;P|08^(`SRtR zufF=K1OfpTFJ8>XjT>3Jb}dCkMPy`TV2tsqPLTbQ&0 z*ycD+Z?yJZM3_E(I<>X6-Nu-YRlq%}(t0@Vox)E%9#2zVUS3aCRaHc_Vf6mu)ye?n zD&6-3RjR%H17OJpvNAFv>R zwUtNX-u%8%wP-JHZSw25H4vRuRaJyS-&D))*s+79OP8`_$&%=8l^)z%--T+D{t!3Y zw@)3tXbsQ=pa{3VI$yQ-&(-Pt0sSXSJnHcO11Z#^$C#I0Hs7Q=)1}z=`=(9fwM`~$ z<_w~QmpF5c{Vs|A*25sqLDa!2O0)kXz)x_i-(ve60X)LFNgxyBNL#wzPBk$$0v{>J zx_9l`B?}iWJZHe?pMRd&vuDfX$&*_Y)Cbk>o}o_ObRE~}qoEx&Mc-9vzC$H4d(234kDG0c0(70nVSU9nU!KyX8*e87WC-^*Od>Faa0|+>8bm-J)q{+Jssr~D za3aStnqc63Z&f&sgG$3D9n*83`yA{eu^R>mU-sgD;maSj?E#gl2Z2B^7<3+Z-~rxw z=beOh`;;kDsHmurS6+FgUGE|8!M&a~RhjcI)ZemntbNKT+f)mjbR4JGvMfj6+XH+a ngMsLm?;IxiD{qMa{nh^gWT}vVXSon500000NkvXXu0mjfP7TWI literal 0 HcmV?d00001 diff --git a/src/static/js/dashboard.js b/src/static/js/dashboard.js index efc0542..85a337b 100644 --- a/src/static/js/dashboard.js +++ b/src/static/js/dashboard.js @@ -225,21 +225,39 @@ class MultipleSelectFilterWidget { make_selection(initial_data){ if(!initial_data || jQuery.isEmptyObject(initial_data)) return; - for(let item_class in initial_data) { - const selected_items = initial_data[item_class]; - for( let node_id in selected_items ){ - const node = this.filter_items[node_id]; - const selection_data = selected_items[node_id] - if( selection_data.selected ) { - this.select(node); - this.markAndSweep(node); - this.updateResult(node); - } - if(node['multiple']){ - this.make_multiple_selection(node, selection_data); - } + + // Need to sort through labs first + let initial_lab = initial_data['lab']; + let initial_resources = initial_data['resource']; + + for( let node_id in initial_lab) { // This should only be length one + const node = this.filter_items[node_id]; + const selection_data = initial_lab[node_id]; + if( selection_data.selected ) { + this.select(node); + this.markAndSweep(node); + this.updateResult(node); + } + if(node['multiple']){ + this.make_multiple_selection(node, selection_data); + } + this.currentLab = node; + this.available_resources = JSON.parse(node['available_resources']); + } + + for( let node_id in initial_resources){ + const node = this.filter_items[node_id]; + const selection_data = initial_resources[node_id]; + if( selection_data.selected ) { + this.select(node); + this.markAndSweep(node); + this.updateResult(node); + } + if(node['multiple']){ + this.make_multiple_selection(node, selection_data); } } + this.updateAvailibility(); } make_multiple_selection(node, selection_data){ @@ -338,10 +356,13 @@ class MultipleSelectFilterWidget { this.available_resources = JSON.parse(node['available_resources']); this.updateAvailibility(); } else { - // a lab is already selected, clear already selected resources - if(confirm('Unselecting a lab will reset all selected resources, are you sure?')) + // a lab is already selected, clear already selected resources + if(confirm('Unselecting a lab will reset all selected resources, are you sure?')) { location.reload(); + return false; + } } + return true; } updateAvailibility() { @@ -357,7 +378,6 @@ class MultipleSelectFilterWidget { let quantityDescription; let quantityNode; - // console.log(this.available_resources); for(let resource in required_resources) { currCount = Math.floor(this.available_resources[resource] / required_resources[resource]); if(currCount < leastAvailable) @@ -388,9 +408,19 @@ class MultipleSelectFilterWidget { reserveResource(node){ const required_resources = JSON.parse(node['required_resources']); + let hostname = document.getElementById('id_hostname'); + let image = document.getElementById('id_image'); + let cnt = 0 + for(let resource in required_resources){ this.available_resources[resource] -= required_resources[resource]; + cnt += required_resources[resource]; + } + + if (cnt > 1 && hostname && image) { + hostname.readOnly = true; + image.disabled = true; } this.updateAvailibility(); @@ -398,22 +428,33 @@ class MultipleSelectFilterWidget { releaseResource(node){ const required_resources = JSON.parse(node['required_resources']); + let hostname = document.getElementById('id_hostname'); + let image = document.getElementById('id_image'); for(let resource in required_resources){ this.available_resources[resource] += required_resources[resource]; } + if (hostname && image) { + hostname.readOnly = false; + image.disabled = false; + } + this.updateAvailibility(); } processClick(id){ + let lab_check; const node = this.filter_items[id]; if(!node['selectable']) return; // If they are selecting a lab, update accordingly - if (node['class'] == 'lab') - this.labCheck(node); + if (node['class'] == 'lab') { + lab_check = this.labCheck(node); + if (!lab_check) + return; + } // Can only select a resource if a lab is selected if (!this.currentLab) { diff --git a/src/templates/akraino/base.html b/src/templates/akraino/base.html deleted file mode 100644 index 1368476..0000000 --- a/src/templates/akraino/base.html +++ /dev/null @@ -1,24 +0,0 @@ -{% extends "base/base.html" %} -{% load staticfiles %} -{% block bgColor %} - -