diff --git a/.gitignore b/.gitignore
index 7d921d2..b3ebced 100644
--- a/.gitignore
+++ b/.gitignore
@@ -4,3 +4,6 @@ venv
.DS_*
*.pyc
db.sqlite3
+console/cert.pem
+tags
+dhcpd.*
diff --git a/README.md b/README.md
index 1d140ff..f1b3767 100644
--- a/README.md
+++ b/README.md
@@ -22,7 +22,7 @@ WebVirtCloud is a virtualization web interface for admins and users. It can dele
### Install WebVirtCloud panel (Ubuntu)
```bash
-sudo apt-get -y install git python-virtualenv python-dev libxml2-dev libvirt-dev zlib1g-dev nginx supervisor libsasl2-modules
+sudo apt-get -y install git python-virtualenv python-dev libxml2-dev libvirt-dev zlib1g-dev nginx supervisor libsasl2-modules gcc pkg-config
git clone https://github.com/retspen/webvirtcloud
cd webvirtcloud
sudo cp conf/supervisor/webvirtcloud.conf /etc/supervisor/conf.d
@@ -185,6 +185,12 @@ webvirtcloud RUNNING pid 24185, uptime 2:59:14
```
+#### Apache mod_wsgi configuration
+```
+WSGIDaemonProcess webvirtcloud threads=2 maximum-requests=1000 display-name=webvirtcloud
+WSGIScriptAlias / /srv/webvirtcloud/webvirtcloud/wsgi.py
+```
+
#### Install final required packages for libvirtd and others on Host Server
```bash
wget -O - https://clck.ru/9V9fH | sudo sh
diff --git a/accounts/backends.py b/accounts/backends.py
new file mode 100644
index 0000000..77aa509
--- /dev/null
+++ b/accounts/backends.py
@@ -0,0 +1,7 @@
+from django.contrib.auth.backends import RemoteUserBackend
+
+class MyRemoteUserBackend(RemoteUserBackend):
+ def configure_user(self, user):
+ user.is_superuser = True
+ return user
+
diff --git a/accounts/forms.py b/accounts/forms.py
index 55d5c29..4127ac4 100644
--- a/accounts/forms.py
+++ b/accounts/forms.py
@@ -2,13 +2,14 @@ import re
from django import forms
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
+from django.conf import settings
class UserAddForm(forms.Form):
name = forms.CharField(label="Name",
error_messages={'required': _('No User name has been entered')},
max_length=20)
- password = forms.CharField(required=True, error_messages={'required': _('No password has been entered')},)
+ password = forms.CharField(required=not settings.ALLOW_EMPTY_PASSWORD, error_messages={'required': _('No password has been entered')},)
def clean_name(self):
name = self.cleaned_data['name']
diff --git a/accounts/migrations/0004_userattributes.py b/accounts/migrations/0004_userattributes.py
new file mode 100644
index 0000000..fb32539
--- /dev/null
+++ b/accounts/migrations/0004_userattributes.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+from django.conf import settings
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ migrations.swappable_dependency(settings.AUTH_USER_MODEL),
+ ('accounts', '0003_usersshkey'),
+ ]
+
+ operations = [
+ migrations.CreateModel(
+ name='UserAttributes',
+ fields=[
+ ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)),
+ ('max_instances', models.IntegerField(default=0)),
+ ('max_cpus', models.IntegerField(default=0)),
+ ('max_memory', models.IntegerField(default=0)),
+ ('user', models.OneToOneField(to=settings.AUTH_USER_MODEL)),
+ ],
+ ),
+ ]
diff --git a/accounts/migrations/0005_userattributes_can_clone_instances.py b/accounts/migrations/0005_userattributes_can_clone_instances.py
new file mode 100644
index 0000000..4539657
--- /dev/null
+++ b/accounts/migrations/0005_userattributes_can_clone_instances.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('accounts', '0004_userattributes'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='userattributes',
+ name='can_clone_instances',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/accounts/migrations/0006_userattributes_max_disk_size.py b/accounts/migrations/0006_userattributes_max_disk_size.py
new file mode 100644
index 0000000..3d21f5f
--- /dev/null
+++ b/accounts/migrations/0006_userattributes_max_disk_size.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('accounts', '0005_userattributes_can_clone_instances'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='userattributes',
+ name='max_disk_size',
+ field=models.IntegerField(default=0),
+ ),
+ ]
diff --git a/accounts/migrations/0007_auto_20160426_0635.py b/accounts/migrations/0007_auto_20160426_0635.py
new file mode 100644
index 0000000..2f92aba
--- /dev/null
+++ b/accounts/migrations/0007_auto_20160426_0635.py
@@ -0,0 +1,34 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('accounts', '0006_userattributes_max_disk_size'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='userattributes',
+ name='max_cpus',
+ field=models.IntegerField(default=1),
+ ),
+ migrations.AlterField(
+ model_name='userattributes',
+ name='max_disk_size',
+ field=models.IntegerField(default=20),
+ ),
+ migrations.AlterField(
+ model_name='userattributes',
+ name='max_instances',
+ field=models.IntegerField(default=1),
+ ),
+ migrations.AlterField(
+ model_name='userattributes',
+ name='max_memory',
+ field=models.IntegerField(default=2048),
+ ),
+ ]
diff --git a/accounts/models.py b/accounts/models.py
index 20efc6f..06fefee 100644
--- a/accounts/models.py
+++ b/accounts/models.py
@@ -21,3 +21,14 @@ class UserSSHKey(models.Model):
def __unicode__(self):
return self.keyname
+
+class UserAttributes(models.Model):
+ user = models.OneToOneField(User, on_delete=models.CASCADE)
+ can_clone_instances = models.BooleanField(default=False)
+ max_instances = models.IntegerField(default=1)
+ max_cpus = models.IntegerField(default=1)
+ max_memory = models.IntegerField(default=2048)
+ max_disk_size = models.IntegerField(default=20)
+
+ def __unicode__(self):
+ return self.user.username
diff --git a/accounts/templates/accounts.html b/accounts/templates/accounts.html
index 9ebff0b..4fdfc8a 100644
--- a/accounts/templates/accounts.html
+++ b/accounts/templates/accounts.html
@@ -71,6 +71,48 @@
+
+
+
+
+
+
+
-{% endblock %}
\ No newline at end of file
+{% endblock %}
diff --git a/accounts/templates/create_user_block.html b/accounts/templates/create_user_block.html
index 7752f56..f4b4679 100644
--- a/accounts/templates/create_user_block.html
+++ b/accounts/templates/create_user_block.html
@@ -23,7 +23,7 @@
@@ -35,4 +35,4 @@
-{% endif %}
\ No newline at end of file
+{% endif %}
diff --git a/accounts/views.py b/accounts/views.py
index 3b6a63e..155ecc8 100644
--- a/accounts/views.py
+++ b/accounts/views.py
@@ -3,18 +3,19 @@ from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User
-from accounts.models import UserInstance, UserSSHKey
+from django.contrib.auth.decorators import login_required
+from accounts.models import *
from instances.models import Instance
from accounts.forms import UserAddForm
+from django.conf import settings
+@login_required
def profile(request):
"""
:param request:
:return:
"""
- if not request.user.is_authenticated():
- return HttpResponseRedirect(reverse('index'))
error_messages = []
user = User.objects.get(id=request.user.id)
@@ -63,21 +64,28 @@ def profile(request):
return HttpResponseRedirect(request.get_full_path())
return render(request, 'profile.html', locals())
-
+@login_required
def accounts(request):
"""
:param request:
:return:
"""
- if not request.user.is_authenticated():
- return HttpResponseRedirect(reverse('index'))
+ def create_missing_userattributes(users):
+ for user in users:
+ try:
+ userattributes = user.userattributes
+ except UserAttributes.DoesNotExist:
+ userattributes = UserAttributes(user=user)
+ userattributes.save()
if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index'))
error_messages = []
- users = User.objects.filter(is_staff=False, is_superuser=False)
+ users = User.objects.all().order_by('username')
+ create_missing_userattributes(users)
+ allow_empty_password = settings.ALLOW_EMPTY_PASSWORD
if request.method == 'POST':
if 'create' in request.POST:
@@ -96,7 +104,17 @@ def accounts(request):
user_pass = request.POST.get('user_pass', '')
user_edit = User.objects.get(id=user_id)
user_edit.set_password(user_pass)
+ user_edit.is_staff = request.POST.get('user_is_staff', False)
+ user_edit.is_superuser = request.POST.get('user_is_superuser', False)
user_edit.save()
+
+ userattributes = user_edit.userattributes
+ userattributes.can_clone_instances = request.POST.get('userattributes_can_clone_instances', False)
+ userattributes.max_instances = request.POST.get('userattributes_max_instances', 0)
+ userattributes.max_cpus = request.POST.get('userattributes_max_cpus', 0)
+ userattributes.max_memory = request.POST.get('userattributes_max_memory', 0)
+ userattributes.max_disk_size = request.POST.get('userattributes_max_disk_size', 0)
+ userattributes.save()
return HttpResponseRedirect(request.get_full_path())
if 'block' in request.POST:
user_id = request.POST.get('user_id', '')
@@ -123,22 +141,20 @@ def accounts(request):
return render(request, 'accounts.html', locals())
+@login_required
def account(request, user_id):
"""
:param request:
:return:
"""
- if not request.user.is_authenticated():
- return HttpResponseRedirect(reverse('index'))
-
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)
- instances = Instance.objects.all()
+ instances = Instance.objects.all().order_by('name')
if user.username == request.user.username:
return HttpResponseRedirect(reverse('profile'))
@@ -162,12 +178,17 @@ def account(request, user_id):
return HttpResponseRedirect(request.get_full_path())
if 'add' in request.POST:
inst_id = request.POST.get('inst_id', '')
- try:
- check_inst = UserInstance.objects.get(instance_id=int(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)
- except UserInstance.DoesNotExist:
- add_user_inst = UserInstance(instance_id=int(inst_id), user_id=user_id)
+ else:
+ add_user_inst = UserInstance(instance_id=int(inst_id), user_id=int(user_id))
add_user_inst.save()
return HttpResponseRedirect(request.get_full_path())
diff --git a/computes/forms.py b/computes/forms.py
index a626106..7dfcbe6 100644
--- a/computes/forms.py
+++ b/computes/forms.py
@@ -149,6 +149,8 @@ class ComputeEditHostForm(forms.Form):
class ComputeAddSocketForm(forms.Form):
name = forms.CharField(error_messages={'required': _('No hostname has been entered')},
max_length=20)
+ details = forms.CharField(error_messages={'required': _('No details has been entred')},
+ max_length=50)
def clean_name(self):
name = self.cleaned_data['name']
diff --git a/computes/migrations/0002_compute_details.py b/computes/migrations/0002_compute_details.py
new file mode 100644
index 0000000..1e0fdf5
--- /dev/null
+++ b/computes/migrations/0002_compute_details.py
@@ -0,0 +1,18 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('computes', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='Compute',
+ name='details',
+ field=models.CharField(max_length=50, null=True, blank=True),
+ ),
+ ]
diff --git a/computes/models.py b/computes/models.py
index 6ee7de8..df9bf02 100644
--- a/computes/models.py
+++ b/computes/models.py
@@ -6,6 +6,7 @@ class Compute(models.Model):
hostname = models.CharField(max_length=20)
login = models.CharField(max_length=20)
password = models.CharField(max_length=14, blank=True, null=True)
+ details = models.CharField(max_length=50, null=True, blank=True)
type = models.IntegerField()
def __unicode__(self):
diff --git a/computes/templates/computes.html b/computes/templates/computes.html
index 7c1c28f..2ffc6f4 100644
--- a/computes/templates/computes.html
+++ b/computes/templates/computes.html
@@ -45,6 +45,11 @@
{% else %}
{% trans "Not Connected" %}
{% endif %}
+ {% if compute.details %}
+ {% trans compute.details %}
+ {% else %}
+ {% trans "No details available" %}
+ {% endif %}
diff --git a/computes/templates/create_comp_block.html b/computes/templates/create_comp_block.html
index 57e327a..9e9a965 100644
--- a/computes/templates/create_comp_block.html
+++ b/computes/templates/create_comp_block.html
@@ -141,6 +141,14 @@
+
+
+
-{% endif %}
\ No newline at end of file
+{% endif %}
diff --git a/computes/templates/overview.html b/computes/templates/overview.html
index 09d5d83..8bfad6e 100644
--- a/computes/templates/overview.html
+++ b/computes/templates/overview.html
@@ -40,6 +40,7 @@
{% trans "Logical CPUs" %}
{% trans "Processor" %}
{% trans "Connection" %}
+ {% trans "Details" %}
{{ hostname }}
@@ -49,6 +50,7 @@
{{ logical_cpu }}
{{ model_cpu }}
{{ uri_conn }}
+
{{ compute.details }}
diff --git a/computes/views.py b/computes/views.py
index 39af4d0..13fb70d 100644
--- a/computes/views.py
+++ b/computes/views.py
@@ -3,6 +3,7 @@ import json
from django.http import HttpResponse, HttpResponseRedirect
from django.core.urlresolvers import reverse
from django.shortcuts import render, get_object_or_404
+from django.contrib.auth.decorators import login_required
from computes.models import Compute
from instances.models import Instance
from accounts.models import UserInstance
@@ -12,15 +13,13 @@ from vrtManager.connection import CONN_SSH, CONN_TCP, CONN_TLS, CONN_SOCKET, con
from libvirt import libvirtError
+@login_required
def computes(request):
"""
:param request:
:return:
"""
- if not request.user.is_authenticated():
- return HttpResponseRedirect(reverse('index'))
-
if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index'))
@@ -36,14 +35,15 @@ def computes(request):
'status': connection_manager.host_is_up(compute.type, compute.hostname),
'type': compute.type,
'login': compute.login,
- 'password': compute.password
+ 'password': compute.password,
+ 'details': compute.details
})
return compute_data
error_messages = []
- computes = Compute.objects.filter()
+ 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', '')
@@ -104,6 +104,7 @@ def computes(request):
if form.is_valid():
data = form.cleaned_data
new_socket_host = Compute(name=data['name'],
+ details=data['details'],
hostname='localhost',
type=CONN_SOCKET,
login='',
@@ -122,6 +123,7 @@ def computes(request):
compute_edit.hostname = data['hostname']
compute_edit.login = data['login']
compute_edit.password = data['password']
+ compute.edit_details = data['details']
compute_edit.save()
return HttpResponseRedirect(request.get_full_path())
else:
@@ -130,15 +132,13 @@ def computes(request):
return render(request, 'computes.html', locals())
+@login_required
def overview(request, compute_id):
"""
:param request:
:return:
"""
- if not request.user.is_authenticated():
- return HttpResponseRedirect(reverse('index'))
-
if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index'))
@@ -160,15 +160,13 @@ def overview(request, compute_id):
return render(request, 'overview.html', locals())
+@login_required
def compute_graph(request, compute_id):
"""
:param request:
:return:
"""
- if not request.user.is_authenticated():
- return HttpResponseRedirect(reverse('login'))
-
points = 5
datasets = {}
cookies = {}
diff --git a/console/views.py b/console/views.py
index a123064..4651c87 100644
--- a/console/views.py
+++ b/console/views.py
@@ -2,6 +2,7 @@ import re
from django.shortcuts import render
from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse
+from django.contrib.auth.decorators import login_required
from instances.models import Instance
from vrtManager.instance import wvmInstance
from webvirtcloud.settings import WS_PORT
@@ -9,15 +10,13 @@ from webvirtcloud.settings import WS_PUBLIC_HOST
from libvirt import libvirtError
+@login_required
def console(request):
"""
:param request:
:return:
"""
- if not request.user.is_authenticated():
- return HttpResponseRedirect(reverse('login'))
-
if request.method == 'GET':
token = request.GET.get('token', '')
diff --git a/create/templates/create_instance.html b/create/templates/create_instance.html
index 9df7326..d2be478 100644
--- a/create/templates/create_instance.html
+++ b/create/templates/create_instance.html
@@ -239,7 +239,7 @@
{% else %}
-
+
diff --git a/create/views.py b/create/views.py
index c3e1139..6c223da 100644
--- a/create/views.py
+++ b/create/views.py
@@ -2,6 +2,7 @@ from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect
from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse
+from django.contrib.auth.decorators import login_required
from computes.models import Compute
from create.models import Flavor
from create.forms import FlavorAddForm, NewVMForm
@@ -11,15 +12,13 @@ from vrtManager import util
from libvirt import libvirtError
+@login_required
def create_instance(request, compute_id):
"""
:param request:
:return:
"""
- if not request.user.is_authenticated():
- return HttpResponseRedirect(reverse('index'))
-
if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index'))
diff --git a/instances/migrations/0002_instance_is_template.py b/instances/migrations/0002_instance_is_template.py
new file mode 100644
index 0000000..cbf2cdd
--- /dev/null
+++ b/instances/migrations/0002_instance_is_template.py
@@ -0,0 +1,19 @@
+# -*- coding: utf-8 -*-
+from __future__ import unicode_literals
+
+from django.db import models, migrations
+
+
+class Migration(migrations.Migration):
+
+ dependencies = [
+ ('instances', '0001_initial'),
+ ]
+
+ operations = [
+ migrations.AddField(
+ model_name='instance',
+ name='is_template',
+ field=models.BooleanField(default=False),
+ ),
+ ]
diff --git a/instances/models.py b/instances/models.py
index 4345cc1..bf2a1c4 100644
--- a/instances/models.py
+++ b/instances/models.py
@@ -6,6 +6,7 @@ class Instance(models.Model):
compute = models.ForeignKey(Compute)
name = models.CharField(max_length=20)
uuid = models.CharField(max_length=36)
+ is_template = models.BooleanField(default=False)
def __unicode__(self):
return self.name
diff --git a/instances/templates/create_inst_block.html b/instances/templates/create_inst_block.html
index 36ec37d..264aa75 100644
--- a/instances/templates/create_inst_block.html
+++ b/instances/templates/create_inst_block.html
@@ -35,16 +35,16 @@
{% trans "Close" %}
{% if computes %}
-