From 27f62dff6c8c1e2f5cbcdb49204e96022a6e5fb2 Mon Sep 17 00:00:00 2001 From: Real-Gecko Date: Wed, 27 May 2020 18:24:06 +0600 Subject: [PATCH] Added admin application - Manage users - Manage groups - Manage logs --- accounts/templates/accounts-list.html | 175 ------------------ accounts/templates/accounts.html | 144 -------------- accounts/templates/create_user_block.html | 38 ---- accounts/urls.py | 4 +- accounts/views.py | 113 ++--------- {instances/templatetags => admin}/__init__.py | 0 admin/apps.py | 5 + admin/decorators.py | 10 + admin/forms.py | 94 ++++++++++ admin/migrations/0001_initial.py | 30 +++ admin/migrations/__init__.py | 0 admin/models.py | 11 ++ .../admin/common/confirm_delete.html | 19 ++ admin/templates/admin/common/form.html | 28 +++ admin/templates/admin/common/list.html | 28 +++ admin/templates/admin/group_list.html | 63 +++++++ .../templates/admin/logs.html | 0 admin/templates/admin/user_form.html | 29 +++ admin/templates/admin/user_list.html | 79 ++++++++ admin/urls.py | 18 ++ admin/views.py | 171 +++++++++++++++++ computes/views.py | 99 +++++----- conf/requirements.txt | 2 + create/views.py | 65 ++++--- instances/templatetags/tags_active.py | 11 -- interfaces/views.py | 27 +-- logs/urls.py | 2 - logs/views.py | 35 +--- networks/views.py | 78 ++++---- nwfilters/views.py | 42 ++--- secrets/views.py | 33 ++-- static/css/webvirtcloud.css | 11 ++ static/js/filter-table.js | 12 ++ storages/views.py | 33 +--- templates/403.html | 15 ++ templates/navbar.html | 105 ++++++----- webvirtcloud/common_tags.py | 26 +++ webvirtcloud/settings.py.template | 20 +- 38 files changed, 933 insertions(+), 742 deletions(-) delete mode 100644 accounts/templates/accounts-list.html delete mode 100644 accounts/templates/accounts.html delete mode 100644 accounts/templates/create_user_block.html rename {instances/templatetags => admin}/__init__.py (100%) create mode 100644 admin/apps.py create mode 100644 admin/decorators.py create mode 100644 admin/forms.py create mode 100644 admin/migrations/0001_initial.py create mode 100644 admin/migrations/__init__.py create mode 100644 admin/models.py create mode 100644 admin/templates/admin/common/confirm_delete.html create mode 100644 admin/templates/admin/common/form.html create mode 100644 admin/templates/admin/common/list.html create mode 100644 admin/templates/admin/group_list.html rename logs/templates/showlogs.html => admin/templates/admin/logs.html (100%) create mode 100644 admin/templates/admin/user_form.html create mode 100644 admin/templates/admin/user_list.html create mode 100644 admin/urls.py create mode 100644 admin/views.py delete mode 100644 instances/templatetags/tags_active.py create mode 100644 static/js/filter-table.js create mode 100644 templates/403.html create mode 100644 webvirtcloud/common_tags.py diff --git a/accounts/templates/accounts-list.html b/accounts/templates/accounts-list.html deleted file mode 100644 index d6630cc..0000000 --- a/accounts/templates/accounts-list.html +++ /dev/null @@ -1,175 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} -{% load staticfiles %} -{% block title %}{% trans "Users" %}{% endblock %} -{% block content %} - -
-
- {% include 'create_user_block.html' %} - -

{% trans "Users" %}

-
-
- - - {% include 'errors_block.html' %} - -
- {% if not users %} -
-
- - {% trans "Warning:" %} {% trans "You don't have any User" %} -
-
- {% else %} -
- - - - - - - - - - - - {% for user in users %} - - - - - - - - {% endfor %} - -
{% trans "Username" %}{% trans "Status" %}{% trans "Staff" %}{% trans "Superuser" %}{% trans "Clone" %}
- {{ user.username }} - - - - - {% if user.is_active %} - {% trans "Active" %} - {% else %} - {% trans "Blocked" %} - {% endif %} - {% if user.is_staff %}{% endif %}{% if user.is_superuser %}{% endif %}{% if user.userattributes.can_clone_instances %}{% endif %}
- - {% for user in users %} - - - {% endfor %} -
- {% endif %} -
-{% endblock %} -{% block script %} - -{% endblock %} diff --git a/accounts/templates/accounts.html b/accounts/templates/accounts.html deleted file mode 100644 index 59077fc..0000000 --- a/accounts/templates/accounts.html +++ /dev/null @@ -1,144 +0,0 @@ -{% extends "base.html" %} -{% load i18n %} -{% block title %}{% trans "Users" %}{% endblock %} -{% block content %} - -
-
- {% include 'create_user_block.html' %} -

{% trans "Users" %}

-
-
- - - {% include 'errors_block.html' %} - -
- {% if not users %} -
-
- - {% trans "Warning:" %} {% trans "You don't have any User" %} -
-
- {% else %} - {% for user in users %} -
-
- -
-
-

{% trans "Status:" %}

-
-
- {% if user.is_active %} -

{% trans "Active" %}

- {% else %} -

{% trans "Blocked" %}

- {% endif %} -
-
-
-
- - - - {% endfor %} - {% endif %} -
-{% endblock %} diff --git a/accounts/templates/create_user_block.html b/accounts/templates/create_user_block.html deleted file mode 100644 index 55e5c2f..0000000 --- a/accounts/templates/create_user_block.html +++ /dev/null @@ -1,38 +0,0 @@ -{% load i18n %} -{% if request.user.is_superuser %} - - - - - - -{% endif %} diff --git a/accounts/urls.py b/accounts/urls.py index 6b49b70..21b7264 100644 --- a/accounts/urls.py +++ b/accounts/urls.py @@ -5,6 +5,6 @@ from . import views urlpatterns = [ path('login/', auth_views.LoginView.as_view(template_name='login.html'), name='login'), path('logout/', auth_views.LogoutView.as_view(template_name='logout.html'), name='logout'), - path('profile/', views.profile, name='profile'), path('', views.accounts, name='accounts'), - re_path(r'^profile/(?P[0-9]+)/$', views.account, name='account'), + path('profile/', views.profile, name='profile'), + path('profile//', views.account, name='account'), ] diff --git a/accounts/views.py b/accounts/views.py index 56f2c02..0664741 100644 --- a/accounts/views.py +++ b/accounts/views.py @@ -1,12 +1,14 @@ -from django.shortcuts import render -from django.http import HttpResponseRedirect -from django.urls import reverse -from django.utils.translation import ugettext_lazy as _ -from accounts.models import * -from instances.models import Instance -from accounts.forms import UserAddForm from django.conf import settings from django.core.validators import ValidationError +from django.http import HttpResponseRedirect +from django.shortcuts import render +from django.urls import reverse +from django.utils.translation import ugettext_lazy as _ + +from accounts.forms import UserAddForm +from accounts.models import * +from admin.decorators import superuser_only +from instances.models import Instance def profile(request): @@ -16,7 +18,7 @@ def profile(request): """ error_messages = [] - user = User.objects.get(id=request.user.id) + # user = User.objects.get(id=request.user.id) publickeys = UserSSHKey.objects.filter(user_id=request.user.id) show_profile_edit_password = settings.SHOW_PROFILE_EDIT_PASSWORD @@ -26,7 +28,7 @@ def profile(request): email = request.POST.get('email', '') user.first_name = username user.email = email - user.save() + request.user.save() return HttpResponseRedirect(request.get_full_path()) if 'oldpasswd' in request.POST: oldpasswd = request.POST.get('oldpasswd', '') @@ -36,11 +38,11 @@ def profile(request): error_messages.append("Passwords didn't enter") if password1 and password2 and password1 != password2: error_messages.append("Passwords don't match") - if not user.check_password(oldpasswd): + if not request.user.check_password(oldpasswd): error_messages.append("Old password is wrong!") if not error_messages: - user.set_password(password1) - user.save() + request.user.set_password(password1) + request.user.save() return HttpResponseRedirect(request.get_full_path()) if 'keyname' in request.POST: keyname = request.POST.get('keyname', '') @@ -67,85 +69,7 @@ def profile(request): return render(request, 'profile.html', locals()) -def accounts(request): - """ - :param request: - :return: - """ - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) - - error_messages = [] - users = User.objects.all().order_by('username') - allow_empty_password = settings.ALLOW_EMPTY_PASSWORD - - if request.method == 'POST': - if 'create' in request.POST: - form = UserAddForm(request.POST) - if form.is_valid(): - data = form.cleaned_data - else: - for msg_err in form.errors.values(): - error_messages.append(msg_err.as_text()) - if not error_messages: - new_user = User.objects.create_user(data['name'], None, data['password']) - new_user.save() - UserAttributes.configure_user(new_user) - return HttpResponseRedirect(request.get_full_path()) - if 'edit' in request.POST: - CHECKBOX_MAPPING = {'on': True, 'off': False, } - - user_id = request.POST.get('user_id', '') - user_pass = request.POST.get('user_pass', '') - user_edit = User.objects.get(id=user_id) - - if user_pass != '': user_edit.set_password(user_pass) - user_edit.is_staff = CHECKBOX_MAPPING.get(request.POST.get('user_is_staff', 'off')) - user_edit.is_superuser = CHECKBOX_MAPPING.get(request.POST.get('user_is_superuser', 'off')) - user_edit.save() - - UserAttributes.create_missing_userattributes(user_edit) - user_edit.userattributes.can_clone_instances = CHECKBOX_MAPPING.get(request.POST.get('userattributes_can_clone_instances', 'off')) - user_edit.userattributes.max_instances = request.POST.get('userattributes_max_instances', 0) - user_edit.userattributes.max_cpus = request.POST.get('userattributes_max_cpus', 0) - user_edit.userattributes.max_memory = request.POST.get('userattributes_max_memory', 0) - user_edit.userattributes.max_disk_size = request.POST.get('userattributes_max_disk_size', 0) - - try: - user_edit.userattributes.clean_fields() - except ValidationError as exc: - error_messages.append(exc) - else: - user_edit.userattributes.save() - return HttpResponseRedirect(request.get_full_path()) - if 'block' in request.POST: - user_id = request.POST.get('user_id', '') - user_block = User.objects.get(id=user_id) - user_block.is_active = False - user_block.save() - return HttpResponseRedirect(request.get_full_path()) - if 'unblock' in request.POST: - user_id = request.POST.get('user_id', '') - user_unblock = User.objects.get(id=user_id) - user_unblock.is_active = True - user_unblock.save() - return HttpResponseRedirect(request.get_full_path()) - if 'delete' in request.POST: - user_id = request.POST.get('user_id', '') - try: - del_user_inst = UserInstance.objects.filter(user_id=user_id) - del_user_inst.delete() - finally: - user_delete = User.objects.get(id=user_id) - user_delete.delete() - return HttpResponseRedirect(request.get_full_path()) - - accounts_template_file = 'accounts.html' - if settings.VIEW_ACCOUNTS_STYLE == "list": - accounts_template_file = 'accounts-list.html' - return render(request, accounts_template_file, locals()) - - +@superuser_only def account(request, user_id): """ :param request: @@ -153,9 +77,6 @@ def account(request, user_id): :return: """ - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) - error_messages = [] user = User.objects.get(id=user_id) user_insts = UserInstance.objects.filter(user_id=user_id) @@ -181,12 +102,12 @@ def account(request, user_id): return HttpResponseRedirect(request.get_full_path()) if 'add' in request.POST: inst_id = request.POST.get('inst_id', '') - + if settings.ALLOW_INSTANCE_MULTIPLE_OWNER: check_inst = UserInstance.objects.filter(instance_id=int(inst_id), user_id=int(user_id)) else: check_inst = UserInstance.objects.filter(instance_id=int(inst_id)) - + if check_inst: msg = _("Instance already added") error_messages.append(msg) diff --git a/instances/templatetags/__init__.py b/admin/__init__.py similarity index 100% rename from instances/templatetags/__init__.py rename to admin/__init__.py diff --git a/admin/apps.py b/admin/apps.py new file mode 100644 index 0000000..5bbf122 --- /dev/null +++ b/admin/apps.py @@ -0,0 +1,5 @@ +from django.apps import AppConfig + + +class AdminConfig(AppConfig): + name = 'admin' diff --git a/admin/decorators.py b/admin/decorators.py new file mode 100644 index 0000000..ebe901f --- /dev/null +++ b/admin/decorators.py @@ -0,0 +1,10 @@ +from django.core.exceptions import PermissionDenied + + +def superuser_only(function): + def _inner(request, *args, **kwargs): + if not request.user.is_superuser: + raise PermissionDenied + return function(request, *args, **kwargs) + + return _inner diff --git a/admin/forms.py b/admin/forms.py new file mode 100644 index 0000000..29c87b8 --- /dev/null +++ b/admin/forms.py @@ -0,0 +1,94 @@ +from django import forms +from django.contrib.auth.models import Group, User +from django.utils.translation import ugettext_lazy as _ + +from accounts.models import UserAttributes + +from .models import Permission + + +class GroupForm(forms.ModelForm): + permissions = forms.ModelMultipleChoiceField( + widget=forms.CheckboxSelectMultiple, + queryset=Permission.objects.filter(content_type__model='permissionset'), + required=False, + ) + + users = forms.ModelMultipleChoiceField( + widget=forms.CheckboxSelectMultiple, + queryset=User.objects.all(), + required=False, + ) + + def __init__(self, *args, **kwargs): + super(GroupForm, self).__init__(*args, **kwargs) + instance = getattr(self, 'instance', None) + if instance and instance.id: + self.fields['users'].initial = self.instance.user_set.all() + + def save_m2m(self): + self.instance.user_set.set(self.cleaned_data['users']) + + def save(self, *args, **kwargs): + instance = super(GroupForm, self).save() + self.save_m2m() + return instance + + class Meta: + model = Group + fields = '__all__' + + +class UserForm(forms.ModelForm): + user_permissions = forms.ModelMultipleChoiceField( + widget=forms.CheckboxSelectMultiple, + queryset=Permission.objects.filter(content_type__model='permissionset'), + label=_('Permissions'), + required=False, + ) + + groups = forms.ModelMultipleChoiceField( + widget=forms.CheckboxSelectMultiple, + queryset=Group.objects.all(), + label=_('Groups'), + required=False, + ) + + class Meta: + model = User + fields = [ + 'username', + 'groups', + 'first_name', + 'last_name', + 'email', + 'user_permissions', + 'is_staff', + 'is_active', + 'is_superuser', + ] + + +class UserCreateForm(UserForm): + password = forms.CharField(widget=forms.PasswordInput) + + class Meta: + model = User + fields = [ + 'username', + 'password', + 'groups', + 'first_name', + 'last_name', + 'email', + 'user_permissions', + 'is_staff', + 'is_active', + 'is_superuser', + ] + + +class UserAttributesForm(forms.ModelForm): + class Meta: + model = UserAttributes + exclude = ['user'] diff --git a/admin/migrations/0001_initial.py b/admin/migrations/0001_initial.py new file mode 100644 index 0000000..9a54081 --- /dev/null +++ b/admin/migrations/0001_initial.py @@ -0,0 +1,30 @@ +# Generated by Django 2.2.12 on 2020-05-27 07:01 + +import django.contrib.auth.models +from django.db import migrations + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ('auth', '0011_update_proxy_permissions'), + ] + + operations = [ + migrations.CreateModel( + name='Permission', + fields=[ + ], + options={ + 'proxy': True, + 'indexes': [], + 'constraints': [], + }, + bases=('auth.permission',), + managers=[ + ('objects', django.contrib.auth.models.PermissionManager()), + ], + ), + ] diff --git a/admin/migrations/__init__.py b/admin/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/admin/models.py b/admin/models.py new file mode 100644 index 0000000..73ddb5d --- /dev/null +++ b/admin/models.py @@ -0,0 +1,11 @@ +from django.contrib.auth.models import Permission as P + +class Permission(P): + """ + Proxy model to Django Permissions model allows us to override __str__ + """ + def __str__(self): + return f'{self.content_type.app_label}: {self.name}' + + class Meta: + proxy = True diff --git a/admin/templates/admin/common/confirm_delete.html b/admin/templates/admin/common/confirm_delete.html new file mode 100644 index 0000000..6ad93d4 --- /dev/null +++ b/admin/templates/admin/common/confirm_delete.html @@ -0,0 +1,19 @@ +{% extends "base.html" %} +{% load bootstrap3 %} +{% load font_awesome %} +{% load i18n %} + +{% block title %}{%trans "Delete" %}{% endblock %} + +{% block content %} +
+ {% csrf_token %} +
+ {%trans "Are you sure you want to delete" %} "{{ object }}"? +
+ {% icon 'times' %} {% trans "Cancel" %} + +
+{% endblock %} \ No newline at end of file diff --git a/admin/templates/admin/common/form.html b/admin/templates/admin/common/form.html new file mode 100644 index 0000000..4ff3fd3 --- /dev/null +++ b/admin/templates/admin/common/form.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} +{% load bootstrap3 %} +{% load font_awesome %} +{% load i18n %} + +{% block title %}{% trans "User" %}{% endblock %} + +{% block content %} +
+
+ +
+
+
+
+
+ {% csrf_token %} + {% bootstrap_form form layout='horizontal' %} +
+
+ {% icon 'times' %} {% trans "Cancel" %} + +
+
+
+{% endblock content %} \ No newline at end of file diff --git a/admin/templates/admin/common/list.html b/admin/templates/admin/common/list.html new file mode 100644 index 0000000..a7a8e50 --- /dev/null +++ b/admin/templates/admin/common/list.html @@ -0,0 +1,28 @@ +{% extends "base.html" %} +{% load font_awesome %} +{% load i18n %} + +{% block title %}{{ title }}{% endblock %} + +{% block content %} +{% if create_url %} +{% icon 'plus' %} {%trans "Create New" %} +{% endif %} + + {% for object in object_list %} + + + + {% endfor %} +
{{ object }} +
+ {% icon 'edit' %} {%trans "Edit"%} + {% if extra_urls %} + {% for url in extra_urls %} + {{ url.1 }} + {% endfor %} + {% endif %} + {% icon 'times' %} {%trans "Delete" %} +
+
+{% endblock %} \ No newline at end of file diff --git a/admin/templates/admin/group_list.html b/admin/templates/admin/group_list.html new file mode 100644 index 0000000..4ec6a19 --- /dev/null +++ b/admin/templates/admin/group_list.html @@ -0,0 +1,63 @@ +{% extends "base.html" %} +{% load i18n %} +{% load static %} +{% load font_awesome %} +{% block title %}{% trans "Users" %}{% endblock %} +{% block content %} +
+
+ + {% icon 'plus' %} + + +

{% trans "Groups" %}

+
+
+{% include 'errors_block.html' %} +
+ {% if not groups %} +
+
+ + {% icon 'exclamation-triangle '%} {% trans "Warning" %}: {% trans "You don't have any groups" %} +
+
+ {% else %} +
+ + + + + + + + + {% for group in groups %} + + + + + {% endfor %} + +
{% trans "Group Name" %}{% trans "Actions" %}
+ {{ group.name }} + + +
+
+ {% endif %} +
+{% endblock content %} + +{% block script %} + +{% endblock script %} diff --git a/logs/templates/showlogs.html b/admin/templates/admin/logs.html similarity index 100% rename from logs/templates/showlogs.html rename to admin/templates/admin/logs.html diff --git a/admin/templates/admin/user_form.html b/admin/templates/admin/user_form.html new file mode 100644 index 0000000..ff54117 --- /dev/null +++ b/admin/templates/admin/user_form.html @@ -0,0 +1,29 @@ +{% extends "base.html" %} +{% load bootstrap3 %} +{% load font_awesome %} +{% load i18n %} + +{% block title %}{% trans "User" %}{% endblock %} + +{% block content %} +
+
+ +
+
+
+
+
+ {% csrf_token %} + {% bootstrap_form user_form layout='horizontal' %} + {% bootstrap_form attributes_form layout='horizontal' %} +
+
+ {% icon 'times' %} {% trans "Cancel" %} + +
+
+
+{% endblock content %} \ No newline at end of file diff --git a/admin/templates/admin/user_list.html b/admin/templates/admin/user_list.html new file mode 100644 index 0000000..bba9a58 --- /dev/null +++ b/admin/templates/admin/user_list.html @@ -0,0 +1,79 @@ +{% extends "base.html" %} +{% load i18n %} +{% load static %} +{% load font_awesome %} +{% block title %}{% trans "Users" %}{% endblock %} +{% block content %} +
+
+ + {% icon 'plus' %} + + +

{% trans "Users" %}

+
+
+{% include 'errors_block.html' %} +
+ {% if not users %} +
+
+ + {% icon 'exclamation-triangle '%} {% trans "Warning" %}: {% trans "You don't have any users" %} +
+
+ {% else %} +
+ + + + + + + + + + + + + {% for user in users %} + + + + + + + + + {% endfor %} + +
{% trans "Username" %}{% trans "Status" %}{% trans "Staff" %}{% trans "Superuser" %}{% trans "Clone" %}{% trans "" %}
+ {{ user.username }} + + {% if user.is_active %} + {% trans "Active" %} + {% else %} + {% trans "Blocked" %} + {% endif %} + {% if user.is_staff %}{% icon 'check' %}{% endif %}{% if user.is_superuser %}{% icon 'check' %}{% endif %}{% if user.userattributes.can_clone_instances %}{% icon 'check' %}{% endif %} +
+ {% icon 'eye' %} + {% icon 'pencil' %} + {% if user.is_active %} + {% icon 'stop' %} + {% else %} + {% icon 'play' %} + {% endif %} + {% icon 'times' %} +
+
+
+ {% endif %} +
+{% endblock content %} + +{% block script %} + +{% endblock script %} diff --git a/admin/urls.py b/admin/urls.py new file mode 100644 index 0000000..5e934c0 --- /dev/null +++ b/admin/urls.py @@ -0,0 +1,18 @@ +from django.urls import path +from django.contrib.auth.views import PasswordChangeView, PasswordChangeDoneView + +from . import views + +urlpatterns = [ + path('groups/', views.group_list, name='group_list'), + path('groups/create/', views.group_create, name='group_create'), + path('groups//update/', views.group_update, name='group_update'), + path('groups//delete/', views.group_delete, name='group_delete'), + path('users/', views.user_list, name='user_list'), + path('users/create/', views.user_create, name='user_create'), + path('users//update/', views.user_update, name='user_update'), + path('users//delete/', views.user_delete, name='user_delete'), + path('users//block/', views.user_block, name='user_block'), + path('users//unblock/', views.user_unblock, name='user_unblock'), + path('logs/', views.logs, name='logs'), +] diff --git a/admin/views.py b/admin/views.py new file mode 100644 index 0000000..85a4370 --- /dev/null +++ b/admin/views.py @@ -0,0 +1,171 @@ +from django.conf import settings +from django.contrib.auth.decorators import user_passes_test +from django.contrib.auth.models import Group, User +from django.core.paginator import Paginator +from django.shortcuts import get_object_or_404, redirect, render +from django.utils.translation import ugettext_lazy as _ + +from accounts.models import UserAttributes +from logs.models import Logs + +from . import forms +from .decorators import superuser_only + + +@superuser_only +def group_list(request): + groups = Group.objects.all() + return render( + request, + 'admin/group_list.html', + { + 'groups': groups, + }, + ) + + +@superuser_only +def group_create(request): + form = forms.GroupForm(request.POST or None) + if form.is_valid(): + form.save() + return redirect('admin:group_list') + return render( + request, + 'admin/common/form.html', + { + 'form': form, + 'title': _('Create Group'), + }, + ) + + +@superuser_only +def group_update(request, pk): + group = get_object_or_404(Group, pk=pk) + form = forms.GroupForm(request.POST or None, instance=group) + if form.is_valid(): + form.save() + return redirect('admin:group_list') + + return render( + request, + 'admin/common/form.html', + { + 'form': form, + 'title': _('Update Group'), + }, + ) + + +@superuser_only +def group_delete(request, pk): + group = get_object_or_404(Group, pk=pk) + if request.method == 'POST': + group.delete() + return redirect('admin:group_list') + + return render( + request, + 'admin/common/confirm_delete.html', + {'object': group}, + ) + + +@superuser_only +def user_list(request): + users = User.objects.all() + return render( + request, + 'admin/user_list.html', + { + 'users': users, + 'title': _('Users'), + }, + ) + + +@superuser_only +def user_create(request): + user_form = forms.UserCreateForm(request.POST or None) + attributes_form = forms.UserAttributesForm(request.POST or None) + if user_form.is_valid() and attributes_form.is_valid(): + user = user_form.save() + password = user_form.cleaned_data['password'] + user.set_password(password) + user.save() + attributes = attributes_form.save(commit=False) + attributes.user = user + attributes.save() + return redirect('admin:user_list') + + return render( + request, + 'admin/user_form.html', + { + 'user_form': user_form, + 'attributes_form': attributes_form, + 'title': _('Create User') + }, + ) + + +@superuser_only +def user_update(request, pk): + user = get_object_or_404(User, pk=pk) + attributes = UserAttributes.objects.get(user=user) + user_form = forms.UserForm(request.POST or None, instance=user) + attributes_form = forms.UserAttributesForm(request.POST or None, instance=attributes) + if user_form.is_valid() and attributes_form.is_valid(): + user_form.save() + attributes_form.save() + return redirect('admin:user_list') + + return render( + request, + 'admin/user_form.html', + { + 'user_form': user_form, + 'attributes_form': attributes_form, + 'title': _('Update User') + }, + ) + + +@superuser_only +def user_delete(request, pk): + user = get_object_or_404(User, pk=pk) + if request.method == 'POST': + user.delete() + return redirect('admin:user_list') + + return render( + request, + 'admin/common/confirm_delete.html', + {'object': user}, + ) + + +@superuser_only +def user_block(request, pk): + user: User = get_object_or_404(User, pk=pk) + user.is_active = False + user.save() + return redirect('admin:user_list') + + +@superuser_only +def user_unblock(request, pk): + user: User = get_object_or_404(User, pk=pk) + user.is_active = True + user.save() + return redirect('admin:user_list') + + +@superuser_only +def logs(request): + l = Logs.objects.order_by('-date') + paginator = Paginator(l, settings.LOGS_PER_PAGE) + page = request.GET.get('page', 1) + logs = paginator.page(page) + return render(request, 'admin/logs.html', {'logs': logs}) diff --git a/computes/views.py b/computes/views.py index 77c9d05..d6b5c73 100644 --- a/computes/views.py +++ b/computes/views.py @@ -10,38 +10,37 @@ from computes.forms import ComputeAddTcpForm, ComputeAddSshForm, ComputeEditHost from vrtManager.hostdetails import wvmHostDetails from vrtManager.connection import CONN_SSH, CONN_TCP, CONN_TLS, CONN_SOCKET, connection_manager, wvmConnect from libvirt import libvirtError +from admin.decorators import superuser_only +@superuser_only def computes(request): """ :param request: :return: """ - - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) - def get_hosts_status(computes): """ Function return all hosts all vds on host """ compute_data = [] for compute in computes: - compute_data.append({'id': compute.id, - 'name': compute.name, - 'hostname': compute.hostname, - 'status': connection_manager.host_is_up(compute.type, compute.hostname), - 'type': compute.type, - 'login': compute.login, - 'password': compute.password, - 'details': compute.details - }) + compute_data.append({ + 'id': compute.id, + 'name': compute.name, + 'hostname': compute.hostname, + 'status': connection_manager.host_is_up(compute.type, compute.hostname), + 'type': compute.type, + 'login': compute.login, + 'password': compute.password, + 'details': compute.details + }) return compute_data error_messages = [] computes = Compute.objects.filter().order_by('name') computes_info = get_hosts_status(computes) - + if request.method == 'POST': if 'host_del' in request.POST: compute_id = request.POST.get('host_id', '') @@ -133,6 +132,7 @@ def computes(request): return render(request, 'computes.html', locals()) +@superuser_only def overview(request, compute_id): """ :param request: @@ -140,17 +140,16 @@ def overview(request, compute_id): :return: """ - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) - error_messages = [] compute = get_object_or_404(Compute, pk=compute_id) try: - conn = wvmHostDetails(compute.hostname, - compute.login, - compute.password, - compute.type) + conn = wvmHostDetails( + compute.hostname, + compute.login, + compute.password, + compute.type, + ) hostname, host_arch, host_memory, logical_cpu, model_cpu, uri_conn = conn.get_node_info() hypervisor = conn.get_hypervisors_domain_types() mem_usage = conn.get_memory_usage() @@ -172,10 +171,12 @@ def compute_graph(request, compute_id): """ compute = get_object_or_404(Compute, pk=compute_id) try: - conn = wvmHostDetails(compute.hostname, - compute.login, - compute.password, - compute.type) + conn = wvmHostDetails( + compute.hostname, + compute.login, + compute.password, + compute.type, + ) current_time = timezone.now().strftime("%H:%M:%S") cpu_usage = conn.get_cpu_usage() mem_usage = conn.get_memory_usage() @@ -184,9 +185,11 @@ def compute_graph(request, compute_id): cpu_usage = {'usage': 0} mem_usage = {'usage': 0} - data = json.dumps({'cpudata': cpu_usage['usage'], - 'memdata': mem_usage, - 'timeline': current_time}) + data = json.dumps({ + 'cpudata': cpu_usage['usage'], + 'memdata': mem_usage, + 'timeline': current_time, + }) response = HttpResponse() response['Content-Type'] = "text/javascript" response.write(data) @@ -197,10 +200,12 @@ def get_compute_disk_buses(request, compute_id, arch, machine, disk): data = dict() compute = get_object_or_404(Compute, pk=compute_id) try: - conn = wvmConnect(compute.hostname, - compute.login, - compute.password, - compute.type) + conn = wvmConnect( + compute.hostname, + compute.login, + compute.password, + compute.type, + ) disk_device_types = conn.get_disk_device_types(arch, machine) @@ -223,10 +228,12 @@ def get_compute_machine_types(request, compute_id, arch): data = dict() try: compute = get_object_or_404(Compute, pk=compute_id) - conn = wvmConnect(compute.hostname, - compute.login, - compute.password, - compute.type) + conn = wvmConnect( + compute.hostname, + compute.login, + compute.password, + compute.type, + ) data['machines'] = conn.get_machine_types(arch) except libvirtError: pass @@ -238,10 +245,12 @@ def get_compute_video_models(request, compute_id, arch, machine): data = dict() try: compute = get_object_or_404(Compute, pk=compute_id) - conn = wvmConnect(compute.hostname, - compute.login, - compute.password, - compute.type) + conn = wvmConnect( + compute.hostname, + compute.login, + compute.password, + compute.type, + ) data['videos'] = conn.get_video_models(arch, machine) except libvirtError: pass @@ -253,10 +262,12 @@ def get_dom_capabilities(request, compute_id, arch, machine): data = dict() try: compute = get_object_or_404(Compute, pk=compute_id) - conn = wvmConnect(compute.hostname, - compute.login, - compute.password, - compute.type) + conn = wvmConnect( + compute.hostname, + compute.login, + compute.password, + compute.type, + ) data['videos'] = conn.get_disk_device_types(arch, machine) data['bus'] = conn.get_disk_device_types(arch, machine) except libvirtError: diff --git a/conf/requirements.txt b/conf/requirements.txt index b831e22..af0e93a 100644 --- a/conf/requirements.txt +++ b/conf/requirements.txt @@ -1,4 +1,6 @@ Django==2.2.12 +django-bootstrap3==12.1.0 +django-fa==1.0.0 django-login-required-middleware==0.5.0 gunicorn==20.0.4 libvirt-python==6.1.0 diff --git a/create/views.py b/create/views.py index 384d6eb..0c84bea 100644 --- a/create/views.py +++ b/create/views.py @@ -23,13 +23,12 @@ from webvirtcloud.settings import INSTANCE_FIRMWARE_DEFAULT_TYPE from django.contrib import messages from logs.views import addlogmsg +from admin.decorators import superuser_only +@superuser_only def create_instance_select_type(request, compute_id): - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) - conn = None error_messages = list() storages = list() @@ -39,10 +38,7 @@ def create_instance_select_type(request, compute_id): compute = get_object_or_404(Compute, pk=compute_id) try: - conn = wvmCreate(compute.hostname, - compute.login, - compute.password, - compute.type) + conn = wvmCreate(compute.hostname, compute.login, compute.password, compute.type) instances = conn.get_instances() all_hypervisors = conn.get_hypervisors_machines() # Supported hypervisors by webvirtcloud: i686, x86_64(for now) @@ -74,6 +70,7 @@ def create_instance_select_type(request, compute_id): return render(request, 'create_instance_w1.html', locals()) +@superuser_only def create_instance(request, compute_id, arch, machine): """ :param request: @@ -82,8 +79,6 @@ def create_instance(request, compute_id, arch, machine): :param machine: :return: """ - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) conn = None error_messages = list() @@ -96,10 +91,7 @@ def create_instance(request, compute_id, arch, machine): flavors = Flavor.objects.filter().order_by('id') try: - conn = wvmCreate(compute.hostname, - compute.login, - compute.password, - compute.type) + conn = wvmCreate(compute.hostname, compute.login, compute.password, compute.type) default_firmware = INSTANCE_FIRMWARE_DEFAULT_TYPE default_cpu_mode = INSTANCE_CPU_DEFAULT_MODE @@ -150,10 +142,7 @@ def create_instance(request, compute_id, arch, machine): form = FlavorAddForm(request.POST) if form.is_valid(): data = form.cleaned_data - create_flavor = Flavor(label=data['label'], - vcpu=data['vcpu'], - memory=data['memory'], - disk=data['disk']) + create_flavor = Flavor(label=data['label'], vcpu=data['vcpu'], memory=data['memory'], disk=data['disk']) create_flavor.save() return HttpResponseRedirect(request.get_full_path()) if 'delete_flavor' in request.POST: @@ -184,7 +173,9 @@ def create_instance(request, compute_id, arch, machine): error_messages.append(error_msg) else: try: - path = conn.create_volume(data['storage'], data['name'], data['hdd_size'], + path = conn.create_volume(data['storage'], + data['name'], + data['hdd_size'], metadata=meta_prealloc) volume = dict() volume['path'] = path @@ -203,7 +194,10 @@ def create_instance(request, compute_id, arch, machine): error_msg = _("Image has already exist. Please check volumes or change instance name") error_messages.append(error_msg) else: - clone_path = conn.clone_from_template(data['name'], templ_path, data['storage'], metadata=meta_prealloc) + clone_path = conn.clone_from_template(data['name'], + templ_path, + data['storage'], + metadata=meta_prealloc) volume = dict() volume['path'] = clone_path volume['type'] = conn.get_volume_type(clone_path) @@ -238,23 +232,36 @@ def create_instance(request, compute_id, arch, machine): firmware["readonly"] = 'yes' firmware["type"] = 'pflash' if 'secboot' in firmware["loader"] and machine != 'q35': - messages.warning(request, "Changing machine type from '%s' to 'q35' " - "which is required for UEFI secure boot." % machine) + messages.warning( + request, "Changing machine type from '%s' to 'q35' " + "which is required for UEFI secure boot." % machine) machine = 'q35' firmware["secure"] = 'yes' if not error_messages: uuid = util.randomUUID() try: - conn.create_instance(name=data['name'], memory=data['memory'], vcpu=data['vcpu'], - vcpu_mode=data['vcpu_mode'], uuid=uuid, arch=arch, machine=machine, + conn.create_instance(name=data['name'], + memory=data['memory'], + vcpu=data['vcpu'], + vcpu_mode=data['vcpu_mode'], + uuid=uuid, + arch=arch, + machine=machine, firmware=firmware, - images=volume_list, cache_mode=data['cache_mode'], - io_mode=default_io, discard_mode=default_discard, detect_zeroes_mode=default_zeroes, - networks=data['networks'], virtio=data['virtio'], - listen_addr=data["listener_addr"], nwfilter=data["nwfilter"], - graphics=data["graphics"], video=data["video"], - console_pass=data["console_pass"], mac=data['mac'], + images=volume_list, + cache_mode=data['cache_mode'], + io_mode=default_io, + discard_mode=default_discard, + detect_zeroes_mode=default_zeroes, + networks=data['networks'], + virtio=data['virtio'], + listen_addr=data["listener_addr"], + nwfilter=data["nwfilter"], + graphics=data["graphics"], + video=data["video"], + console_pass=data["console_pass"], + mac=data['mac'], qemu_ga=data['qemu_ga']) create_instance = Instance(compute_id=compute_id, name=data['name'], uuid=uuid) create_instance.save() diff --git a/instances/templatetags/tags_active.py b/instances/templatetags/tags_active.py deleted file mode 100644 index 8d8ce62..0000000 --- a/instances/templatetags/tags_active.py +++ /dev/null @@ -1,11 +0,0 @@ -from django import template -import re - -register = template.Library() - - -@register.simple_tag -def class_active(request, pattern): - if re.search(pattern, request.path): - return 'class="active"' - return '' diff --git a/interfaces/views.py b/interfaces/views.py index cd9f43f..0e8a52c 100644 --- a/interfaces/views.py +++ b/interfaces/views.py @@ -5,8 +5,10 @@ from computes.models import Compute from interfaces.forms import AddInterface from vrtManager.interface import wvmInterface, wvmInterfaces from libvirt import libvirtError +from admin.decorators import superuser_only +@superuser_only def interfaces(request, compute_id): """ :param request: @@ -14,18 +16,12 @@ def interfaces(request, compute_id): :return: """ - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) - ifaces_all = [] error_messages = [] compute = get_object_or_404(Compute, pk=compute_id) try: - conn = wvmInterfaces(compute.hostname, - compute.login, - compute.password, - compute.type) + conn = wvmInterfaces(compute.hostname, compute.login, compute.password, compute.type) ifaces = conn.get_ifaces() try: netdevs = conn.get_net_devices() @@ -40,10 +36,9 @@ def interfaces(request, compute_id): form = AddInterface(request.POST) if form.is_valid(): data = form.cleaned_data - conn.create_iface(data['name'], data['itype'], data['start_mode'], data['netdev'], - data['ipv4_type'], data['ipv4_addr'], data['ipv4_gw'], - data['ipv6_type'], data['ipv6_addr'], data['ipv6_gw'], - data['stp'], data['delay']) + conn.create_iface(data['name'], data['itype'], data['start_mode'], data['netdev'], data['ipv4_type'], + data['ipv4_addr'], data['ipv4_gw'], data['ipv6_type'], data['ipv6_addr'], + data['ipv6_gw'], data['stp'], data['delay']) return HttpResponseRedirect(request.get_full_path()) else: for msg_err in form.errors.values(): @@ -55,6 +50,7 @@ def interfaces(request, compute_id): return render(request, 'interfaces.html', locals()) +@superuser_only def interface(request, compute_id, iface): """ :param request: @@ -63,19 +59,12 @@ def interface(request, compute_id, iface): :return: """ - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) - ifaces_all = [] error_messages = [] compute = get_object_or_404(Compute, pk=compute_id) try: - conn = wvmInterface(compute.hostname, - compute.login, - compute.password, - compute.type, - iface) + conn = wvmInterface(compute.hostname, compute.login, compute.password, compute.type, iface) start_mode = conn.get_start_mode() state = conn.is_active() mac = conn.get_mac() diff --git a/logs/urls.py b/logs/urls.py index fb8fb0b..a8a5ae8 100644 --- a/logs/urls.py +++ b/logs/urls.py @@ -2,7 +2,5 @@ from django.urls import path, re_path from . import views urlpatterns = [ - path('', views.showlogs, name='showlogs'), - re_path(r'^(?P[0-9]+)/$', views.showlogs, name='showlogspage'), re_path(r'^vm_logs/(?P[\w\-\.]+)/$', views.vm_logs, name='vm_logs'), ] diff --git a/logs/views.py b/logs/views.py index 45569b0..c04e284 100644 --- a/logs/views.py +++ b/logs/views.py @@ -1,10 +1,13 @@ -from django.shortcuts import render +import json + +from django.conf import settings from django.http import HttpResponse, HttpResponseRedirect +from django.shortcuts import render from django.urls import reverse + +from admin.decorators import superuser_only from instances.models import Instance from logs.models import Logs -from django.conf import settings -import json def addlogmsg(user, instance, message): @@ -18,35 +21,13 @@ def addlogmsg(user, instance, message): add_log_msg.save() -def showlogs(request, page=1): - """ - :param request: - :param page: - :return: - """ - - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) - - page = int(page) - limit_from = (page-1)*settings.LOGS_PER_PAGE - limit_to = page*settings.LOGS_PER_PAGE - logs = Logs.objects.all().order_by('-date')[limit_from:limit_to+1] - has_next_page = logs.count() > settings.LOGS_PER_PAGE - # TODO: remove last element from queryset, but do not affect database - - return render(request, 'showlogs.html', locals()) - - +@superuser_only def vm_logs(request, vname): """ :param request: :param vname: :return: """ - - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) vm = Instance.objects.get(name=vname) logs_ = Logs.objects.filter(instance=vm.name, date__gte=vm.created).order_by('-date') @@ -58,5 +39,5 @@ def vm_logs(request, vname): log['message'] = l.message log['date'] = l.date.strftime('%x %X') logs.append(log) - + return HttpResponse(json.dumps(logs)) diff --git a/networks/views.py b/networks/views.py index 39b648d..8905d9c 100644 --- a/networks/views.py +++ b/networks/views.py @@ -1,15 +1,17 @@ -from django.shortcuts import render, get_object_or_404 +from django.contrib import messages from django.http import HttpResponseRedirect -from django.utils.translation import ugettext_lazy as _ +from django.shortcuts import get_object_or_404, render from django.urls import reverse +from django.utils.translation import ugettext_lazy as _ +from libvirt import libvirtError + +from admin.decorators import superuser_only from computes.models import Compute from networks.forms import AddNetPool -from vrtManager.network import wvmNetwork, wvmNetworks -from vrtManager.network import network_size -from libvirt import libvirtError -from django.contrib import messages +from vrtManager.network import network_size, wvmNetwork, wvmNetworks +@superuser_only def networks(request, compute_id): """ :param request: @@ -17,17 +19,16 @@ def networks(request, compute_id): :return: """ - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) - error_messages = [] compute = get_object_or_404(Compute, pk=compute_id) try: - conn = wvmNetworks(compute.hostname, - compute.login, - compute.password, - compute.type) + conn = wvmNetworks( + compute.hostname, + compute.login, + compute.password, + compute.type, + ) networks = conn.get_networks_info() dhcp4 = netmask4 = gateway4 = '' dhcp6 = prefix6 = gateway6 = '' @@ -52,11 +53,21 @@ def networks(request, compute_id): if prefix6 != '64': error_messages.append(_('For libvirt, the IPv6 network prefix must be /64')) if not error_messages: - conn.create_network(data['name'], - data['forward'], - ipv4, gateway4, netmask4, dhcp4, - ipv6, gateway6, prefix6, dhcp6, - data['bridge_name'], data['openvswitch'], data['fixed']) + conn.create_network( + data['name'], + data['forward'], + ipv4, + gateway4, + netmask4, + dhcp4, + ipv6, + gateway6, + prefix6, + dhcp6, + data['bridge_name'], + data['openvswitch'], + data['fixed'], + ) return HttpResponseRedirect(reverse('network', args=[compute_id, data['name']])) else: for msg_err in form.errors.values(): @@ -68,6 +79,7 @@ def networks(request, compute_id): return render(request, 'networks.html', locals()) +@superuser_only def network(request, compute_id, pool): """ :param request: @@ -76,18 +88,17 @@ def network(request, compute_id, pool): :return: """ - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) - error_messages = [] compute = get_object_or_404(Compute, pk=compute_id) try: - conn = wvmNetwork(compute.hostname, - compute.login, - compute.password, - compute.type, - pool) + conn = wvmNetwork( + compute.hostname, + compute.login, + compute.password, + compute.type, + pool, + ) networks = conn.get_networks() state = conn.is_active() device = conn.get_bridge_device() @@ -187,8 +198,7 @@ def network(request, compute_id, pool): if edit_xml: conn.edit_network(edit_xml) if conn.is_active(): - messages.success(request, _("Network XML is changed. \\" - "Stop and start network to activate new config.")) + messages.success(request, _("Network XML is changed. \\" "Stop and start network to activate new config.")) else: messages.success(request, _("Network XML is changed.")) return HttpResponseRedirect(request.get_full_path()) @@ -201,8 +211,10 @@ def network(request, compute_id, pool): try: conn.set_qos(qos_dir, average, peak, burst) if conn.is_active(): - messages.success(request, _("{} Qos is set. Network XML is changed.").format(qos_dir.capitalize()) + - _("Stop and start network to activate new config")) + messages.success( + request, + _("{} Qos is set. Network XML is changed.").format(qos_dir.capitalize()) + + _("Stop and start network to activate new config")) else: messages.success(request, _("{} Qos is set").format(qos_dir.capitalize())) except libvirtError as lib_err: @@ -213,8 +225,10 @@ def network(request, compute_id, pool): conn.unset_qos(qos_dir) if conn.is_active(): - messages.success(request, _("{} Qos is deleted. Network XML is changed. ").format(qos_dir.capitalize()) + - _("Stop and start network to activate new config.")) + messages.success( + request, + _("{} Qos is deleted. Network XML is changed. ").format(qos_dir.capitalize()) + + _("Stop and start network to activate new config.")) else: messages.success(request, _("{} Qos is deleted").format(qos_dir.capitalize())) return HttpResponseRedirect(request.get_full_path()) diff --git a/nwfilters/views.py b/nwfilters/views.py index 897db7e..00b7e4f 100644 --- a/nwfilters/views.py +++ b/nwfilters/views.py @@ -2,17 +2,20 @@ from __future__ import unicode_literals from django.http import HttpResponseRedirect -from django.shortcuts import render, get_object_or_404 -from django.utils.translation import ugettext_lazy as _ +from django.shortcuts import get_object_or_404, render from django.urls import reverse -from computes.models import Compute -from vrtManager import util -from vrtManager.nwfilters import wvmNWFilters, wvmNWFilter -from vrtManager.instance import wvmInstances, wvmInstance +from django.utils.translation import ugettext_lazy as _ from libvirt import libvirtError + +from admin.decorators import superuser_only +from computes.models import Compute from logs.views import addlogmsg +from vrtManager import util +from vrtManager.instance import wvmInstance, wvmInstances +from vrtManager.nwfilters import wvmNWFilter, wvmNWFilters +@superuser_only def nwfilters(request, compute_id): """ :param request: @@ -20,18 +23,12 @@ def nwfilters(request, compute_id): :return: """ - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) - error_messages = [] nwfilters_all = [] compute = get_object_or_404(Compute, pk=compute_id) try: - conn = wvmNWFilters(compute.hostname, - compute.login, - compute.password, - compute.type) + conn = wvmNWFilters(compute.hostname, compute.login, compute.password, compute.type) if request.method == 'POST': if 'create_nwfilter' in request.POST: @@ -105,9 +102,11 @@ def nwfilters(request, compute_id): error_messages.append(err) addlogmsg(request.user.username, compute.hostname, err) - return render(request, 'nwfilters.html', {'error_messages': error_messages, - 'nwfilters': nwfilters_all, - 'compute': compute}) + return render(request, 'nwfilters.html', { + 'error_messages': error_messages, + 'nwfilters': nwfilters_all, + 'compute': compute + }) def nwfilter(request, compute_id, nwfltr): @@ -117,15 +116,8 @@ def nwfilter(request, compute_id, nwfltr): compute = get_object_or_404(Compute, pk=compute_id) try: - nwfilter = wvmNWFilter(compute.hostname, - compute.login, - compute.password, - compute.type, - nwfltr) - conn = wvmNWFilters(compute.hostname, - compute.login, - compute.password, - compute.type) + nwfilter = wvmNWFilter(compute.hostname, compute.login, compute.password, compute.type, nwfltr) + conn = wvmNWFilters(compute.hostname, compute.login, compute.password, compute.type) for nwf in conn.get_nwfilters(): nwfilters_all.append(conn.get_nwfilter_info(nwf)) diff --git a/secrets/views.py b/secrets/views.py index d40eda0..97c6ef6 100644 --- a/secrets/views.py +++ b/secrets/views.py @@ -1,12 +1,16 @@ -from django.shortcuts import render, get_object_or_404 -from django.http import HttpResponseRedirect -from django.urls import reverse -from computes.models import Compute from secrets.forms import AddSecret -from vrtManager.secrets import wvmSecrets + +from django.http import HttpResponseRedirect +from django.shortcuts import get_object_or_404, render +from django.urls import reverse from libvirt import libvirtError +from admin.decorators import superuser_only +from computes.models import Compute +from vrtManager.secrets import wvmSecrets + +@superuser_only def secrets(request, compute_id): """ :param request: @@ -14,17 +18,11 @@ def secrets(request, compute_id): :return: """ - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) - secrets_all = [] error_messages = [] compute = get_object_or_404(Compute, pk=compute_id) try: - conn = wvmSecrets(compute.hostname, - compute.login, - compute.password, - compute.type) + conn = wvmSecrets(compute.hostname, compute.login, compute.password, compute.type) secrets = conn.get_secrets() for uuid in secrets: @@ -33,11 +31,12 @@ def secrets(request, compute_id): secret_value = conn.get_secret_value(uuid) except libvirtError as lib_err: secret_value = None - secrets_all.append({'usage': secrt.usageID(), - 'uuid': secrt.UUIDString(), - 'usageType': secrt.usageType(), - 'value': secret_value - }) + secrets_all.append({ + 'usage': secrt.usageID(), + 'uuid': secrt.UUIDString(), + 'usageType': secrt.usageType(), + 'value': secret_value + }) if request.method == 'POST': if 'create' in request.POST: form = AddSecret(request.POST) diff --git a/static/css/webvirtcloud.css b/static/css/webvirtcloud.css index 0bdbba5..c20c15f 100644 --- a/static/css/webvirtcloud.css +++ b/static/css/webvirtcloud.css @@ -154,4 +154,15 @@ p { .bottom-bar-margin { margin-top: 65px; +} + +.thumbnail { + padding: 10px; +} + +/* make dropdowns show on hover */ +@media only screen and (min-width: 768px) { + .dropdown:hover .dropdown-menu { + display: block; + } } \ No newline at end of file diff --git a/static/js/filter-table.js b/static/js/filter-table.js new file mode 100644 index 0000000..bbf4843 --- /dev/null +++ b/static/js/filter-table.js @@ -0,0 +1,12 @@ +function filter_table() { + var rex = new RegExp($(this).val(), 'i'); + $('.searchable tr').hide(); + $('.searchable tr').filter(function () { + return rex.test($(this).text()); + }).show(); +} +$(document).ready(function () { + (function ($) { + $('#filter').keyup(filter_table) + }(jQuery)); +}); diff --git a/storages/views.py b/storages/views.py index 76749a4..9f2215c 100644 --- a/storages/views.py +++ b/storages/views.py @@ -8,8 +8,10 @@ from vrtManager.storage import wvmStorage, wvmStorages from libvirt import libvirtError from django.contrib import messages import json +from admin.decorators import superuser_only +@superuser_only def storages(request, compute_id): """ :param request: @@ -17,17 +19,11 @@ def storages(request, compute_id): :return: """ - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) - error_messages = [] compute = get_object_or_404(Compute, pk=compute_id) try: - conn = wvmStorages(compute.hostname, - compute.login, - compute.password, - compute.type) + conn = wvmStorages(compute.hostname, compute.login, compute.password, compute.type) storages = conn.get_storages_info() secrets = conn.get_secrets() @@ -48,12 +44,10 @@ def storages(request, compute_id): error_messages.append(msg) if not error_messages: if data['stg_type'] == 'rbd': - conn.create_storage_ceph(data['stg_type'], data['name'], - data['ceph_pool'], data['ceph_host'], + conn.create_storage_ceph(data['stg_type'], data['name'], data['ceph_pool'], data['ceph_host'], data['ceph_user'], data['secret']) elif data['stg_type'] == 'netfs': - conn.create_storage_netfs(data['stg_type'], data['name'], - data['netfs_host'], data['source'], + conn.create_storage_netfs(data['stg_type'], data['name'], data['netfs_host'], data['source'], data['source_format'], data['target']) else: conn.create_storage(data['stg_type'], data['name'], data['source'], data['target']) @@ -68,6 +62,7 @@ def storages(request, compute_id): return render(request, 'storages.html', locals()) +@superuser_only def storage(request, compute_id, pool): """ :param request: @@ -75,10 +70,6 @@ def storage(request, compute_id, pool): :param pool: :return: """ - - if not request.user.is_superuser: - return HttpResponseRedirect(reverse('index')) - def handle_uploaded_file(path, f_name): target = path + '/' + str(f_name) destination = open(target, 'wb+') @@ -91,11 +82,7 @@ def storage(request, compute_id, pool): meta_prealloc = False try: - conn = wvmStorage(compute.hostname, - compute.login, - compute.password, - compute.type, - pool) + conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, pool) storages = conn.get_storages() state = conn.is_active() @@ -216,11 +203,7 @@ def get_volumes(request, compute_id, pool): data = {} compute = get_object_or_404(Compute, pk=compute_id) try: - conn = wvmStorage(compute.hostname, - compute.login, - compute.password, - compute.type, - pool) + conn = wvmStorage(compute.hostname, compute.login, compute.password, compute.type, pool) conn.refresh() data['vols'] = sorted(conn.get_volumes()) except libvirtError: diff --git a/templates/403.html b/templates/403.html new file mode 100644 index 0000000..bb0a0ab --- /dev/null +++ b/templates/403.html @@ -0,0 +1,15 @@ +{% extends "base_auth.html" %} +{% load i18n %} +{% block title %}{% trans "404" %}{% endblock %} +{% block content %} +
+
+

{% trans 'Oops!'%}

+ +

{% trans "403 Forbidden" %}

+ +

{% trans "You do not have permission to access this page." %}

+ ← {% trans 'Back'%} +
+
+{% endblock %} diff --git a/templates/navbar.html b/templates/navbar.html index 1a1263b..f9896cf 100644 --- a/templates/navbar.html +++ b/templates/navbar.html @@ -1,48 +1,59 @@ {% load i18n %} -{% load tags_active %} - - \ No newline at end of file +{% load font_awesome %} +{% load common_tags %} + + \ No newline at end of file diff --git a/webvirtcloud/common_tags.py b/webvirtcloud/common_tags.py new file mode 100644 index 0000000..223a722 --- /dev/null +++ b/webvirtcloud/common_tags.py @@ -0,0 +1,26 @@ +from django import template +import re + +register = template.Library() + + +@register.simple_tag +def app_active(request, app_name): + if request.resolver_match.app_name == app_name: + return 'active' + return '' + + +@register.simple_tag +def view_active(request, view_name): + if request.resolver_match.view_name == view_name: + return 'active' + return '' + + +@register.simple_tag +def class_active(request, pattern): + if re.search(pattern, request.path): + # Not sure why 'class="active"' returns class=""active"" + return 'class=active' + return '' diff --git a/webvirtcloud/settings.py.template b/webvirtcloud/settings.py.template index ad2b200..339fcba 100644 --- a/webvirtcloud/settings.py.template +++ b/webvirtcloud/settings.py.template @@ -14,17 +14,18 @@ DEBUG = True ALLOWED_HOSTS = ['*'] - # Application definition INSTALLED_APPS = [ - 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', 'django.contrib.messages', 'django.contrib.staticfiles', + 'bootstrap3', + 'fa', 'accounts', + 'admin', 'computes', 'console', 'create', @@ -56,7 +57,9 @@ ROOT_URLCONF = 'webvirtcloud.urls' TEMPLATES = [ { 'BACKEND': 'django.template.backends.django.DjangoTemplates', - 'DIRS': [ os.path.join(BASE_DIR, 'templates'), ], + 'DIRS': [ + os.path.join(BASE_DIR, 'templates'), + ], 'APP_DIRS': True, 'OPTIONS': { 'context_processors': [ @@ -65,13 +68,15 @@ TEMPLATES = [ 'django.contrib.auth.context_processors.auth', 'django.contrib.messages.context_processors.messages', ], + 'libraries': { + 'common_tags': 'webvirtcloud.common_tags', + }, }, }, ] WSGI_APPLICATION = 'webvirtcloud.wsgi.application' - # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases @@ -84,12 +89,12 @@ DATABASES = { AUTHENTICATION_BACKENDS = [ 'django.contrib.auth.backends.ModelBackend', - #'django.contrib.auth.backends.RemoteUserBackend', - #'accounts.backends.MyRemoteUserBackend', ] LOGIN_URL = '/accounts/login' +LOGOUT_REDIRECT_URL = '/accounts/login' + LANGUAGE_CODE = 'en-us' TIME_ZONE = 'UTC' @@ -168,9 +173,6 @@ SHOW_ACCESS_ROOT_PASSWORD = False SHOW_ACCESS_SSH_KEYS = False SHOW_PROFILE_EDIT_PASSWORD = False -# available: default (grid), list -VIEW_ACCOUNTS_STYLE = 'grid' - # available list style: default (grouped), nongrouped VIEW_INSTANCES_LIST_STYLE = 'grouped'