1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2024-12-24 15:15:22 +00:00

Merge pull request #88 from honza801/master

multiple enhancements and fixes
This commit is contained in:
Anatoliy Guskov 2017-03-09 15:34:21 +02:00 committed by GitHub
commit 531ea1e652
48 changed files with 1188 additions and 197 deletions

3
.gitignore vendored
View file

@ -4,3 +4,6 @@ venv
.DS_* .DS_*
*.pyc *.pyc
db.sqlite3 db.sqlite3
console/cert.pem
tags
dhcpd.*

View file

@ -22,7 +22,7 @@ WebVirtCloud is a virtualization web interface for admins and users. It can dele
### Install WebVirtCloud panel (Ubuntu) ### Install WebVirtCloud panel (Ubuntu)
```bash ```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 git clone https://github.com/retspen/webvirtcloud
cd webvirtcloud cd webvirtcloud
sudo cp conf/supervisor/webvirtcloud.conf /etc/supervisor/conf.d 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 #### Install final required packages for libvirtd and others on Host Server
```bash ```bash
wget -O - https://clck.ru/9V9fH | sudo sh wget -O - https://clck.ru/9V9fH | sudo sh

7
accounts/backends.py Normal file
View file

@ -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

View file

@ -2,13 +2,14 @@ import re
from django import forms from django import forms
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User from django.contrib.auth.models import User
from django.conf import settings
class UserAddForm(forms.Form): class UserAddForm(forms.Form):
name = forms.CharField(label="Name", name = forms.CharField(label="Name",
error_messages={'required': _('No User name has been entered')}, error_messages={'required': _('No User name has been entered')},
max_length=20) 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): def clean_name(self):
name = self.cleaned_data['name'] name = self.cleaned_data['name']

View file

@ -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)),
],
),
]

View file

@ -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),
),
]

View file

@ -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),
),
]

View file

@ -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),
),
]

View file

@ -0,0 +1,15 @@
# -*- coding: utf-8 -*-
from __future__ import unicode_literals
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('accounts', '0004_userinstance_is_vnc'),
('accounts', '0007_auto_20160426_0635'),
]
operations = [
]

View file

@ -21,3 +21,14 @@ class UserSSHKey(models.Model):
def __unicode__(self): def __unicode__(self):
return self.keyname 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

View file

@ -71,6 +71,48 @@
<input type="password" name="user_pass" class="form-control" value=""> <input type="password" name="user_pass" class="form-control" value="">
</div> </div>
</div> </div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Is staff" %}</label>
<div class="col-sm-2">
<input type="checkbox" name="user_is_staff" {% if user.is_staff %}checked{% endif %}>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Is superuser" %}</label>
<div class="col-sm-2">
<input type="checkbox" name="user_is_superuser" {% if user.is_superuser %}checked{% endif %}>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Can clone instances" %}</label>
<div class="col-sm-2">
<input type="checkbox" name="userattributes_can_clone_instances" {% if user.userattributes.can_clone_instances %}checked{% endif %}>
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max instances" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_instances" class="form-control" value="{{ user.userattributes.max_instances }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max cpus" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_cpus" class="form-control" value="{{ user.userattributes.max_cpus }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max memory (MB)" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_memory" class="form-control" value="{{ user.userattributes.max_memory }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Max disk size (GB)" %}</label>
<div class="col-sm-6">
<input type="text" name="userattributes_max_disk_size" class="form-control" value="{{ user.userattributes.max_disk_size }}">
</div>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="submit" class="pull-left btn btn-danger" name="delete"> <button type="submit" class="pull-left btn btn-danger" name="delete">
@ -99,4 +141,4 @@
{% endfor %} {% endfor %}
{% endif %} {% endif %}
</div> </div>
{% endblock %} {% endblock %}

View file

@ -23,7 +23,7 @@
<div class="form-group"> <div class="form-group">
<label class="col-sm-4 control-label">{% trans "Password" %}</label> <label class="col-sm-4 control-label">{% trans "Password" %}</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="password" class="form-control" name="password" placeholder="*******" required> <input type="password" class="form-control" name="password" placeholder="*******" {% if not allow_empty_password %}required{% endif %}>
</div> </div>
</div> </div>
</div> </div>
@ -35,4 +35,4 @@
</div> <!-- /.modal-content --> </div> <!-- /.modal-content -->
</div> <!-- /.modal-dialog --> </div> <!-- /.modal-dialog -->
</div> <!-- /.modal --> </div> <!-- /.modal -->
{% endif %} {% endif %}

View file

@ -3,18 +3,19 @@ from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.models import User 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 instances.models import Instance
from accounts.forms import UserAddForm from accounts.forms import UserAddForm
from django.conf import settings
@login_required
def profile(request): def profile(request):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
error_messages = [] error_messages = []
user = User.objects.get(id=request.user.id) user = User.objects.get(id=request.user.id)
@ -63,21 +64,28 @@ def profile(request):
return HttpResponseRedirect(request.get_full_path()) return HttpResponseRedirect(request.get_full_path())
return render(request, 'profile.html', locals()) return render(request, 'profile.html', locals())
@login_required
def accounts(request): def accounts(request):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated(): def create_missing_userattributes(users):
return HttpResponseRedirect(reverse('index')) for user in users:
try:
userattributes = user.userattributes
except UserAttributes.DoesNotExist:
userattributes = UserAttributes(user=user)
userattributes.save()
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
error_messages = [] 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 request.method == 'POST':
if 'create' in request.POST: if 'create' in request.POST:
@ -96,7 +104,17 @@ def accounts(request):
user_pass = request.POST.get('user_pass', '') user_pass = request.POST.get('user_pass', '')
user_edit = User.objects.get(id=user_id) user_edit = User.objects.get(id=user_id)
user_edit.set_password(user_pass) 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() 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()) return HttpResponseRedirect(request.get_full_path())
if 'block' in request.POST: if 'block' in request.POST:
user_id = request.POST.get('user_id', '') user_id = request.POST.get('user_id', '')
@ -123,22 +141,20 @@ def accounts(request):
return render(request, 'accounts.html', locals()) return render(request, 'accounts.html', locals())
@login_required
def account(request, user_id): def account(request, user_id):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
error_messages = [] error_messages = []
user = User.objects.get(id=user_id) user = User.objects.get(id=user_id)
user_insts = UserInstance.objects.filter(user_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: if user.username == request.user.username:
return HttpResponseRedirect(reverse('profile')) return HttpResponseRedirect(reverse('profile'))
@ -162,12 +178,17 @@ def account(request, user_id):
return HttpResponseRedirect(request.get_full_path()) return HttpResponseRedirect(request.get_full_path())
if 'add' in request.POST: if 'add' in request.POST:
inst_id = request.POST.get('inst_id', '') 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") msg = _("Instance already added")
error_messages.append(msg) error_messages.append(msg)
except UserInstance.DoesNotExist: else:
add_user_inst = UserInstance(instance_id=int(inst_id), user_id=user_id) add_user_inst = UserInstance(instance_id=int(inst_id), user_id=int(user_id))
add_user_inst.save() add_user_inst.save()
return HttpResponseRedirect(request.get_full_path()) return HttpResponseRedirect(request.get_full_path())

View file

@ -149,6 +149,8 @@ class ComputeEditHostForm(forms.Form):
class ComputeAddSocketForm(forms.Form): class ComputeAddSocketForm(forms.Form):
name = forms.CharField(error_messages={'required': _('No hostname has been entered')}, name = forms.CharField(error_messages={'required': _('No hostname has been entered')},
max_length=20) max_length=20)
details = forms.CharField(error_messages={'required': _('No details has been entred')},
max_length=50)
def clean_name(self): def clean_name(self):
name = self.cleaned_data['name'] name = self.cleaned_data['name']

View file

@ -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),
),
]

View file

@ -6,6 +6,7 @@ class Compute(models.Model):
hostname = models.CharField(max_length=20) hostname = models.CharField(max_length=20)
login = models.CharField(max_length=20) login = models.CharField(max_length=20)
password = models.CharField(max_length=14, blank=True, null=True) password = models.CharField(max_length=14, blank=True, null=True)
details = models.CharField(max_length=50, null=True, blank=True)
type = models.IntegerField() type = models.IntegerField()
def __unicode__(self): def __unicode__(self):

View file

@ -45,6 +45,11 @@
{% else %} {% else %}
<p>{% trans "Not Connected" %}</p> <p>{% trans "Not Connected" %}</p>
{% endif %} {% endif %}
{% if compute.details %}
<p>{% trans compute.details %}</p>
{% else %}
<p>{% trans "No details available" %}</p>
{% endif %}
</div> </div>
</div> </div>

View file

@ -141,6 +141,14 @@
<input type="text" name="name" class="form-control" placeholder="Label Name" maxlength="20" required pattern="[a-z0-9\.\-_]+"> <input type="text" name="name" class="form-control" placeholder="Label Name" maxlength="20" required pattern="[a-z0-9\.\-_]+">
</div> </div>
</div> </div>
<div class="form-group">
<label class="col-sm-4 control-label">{% trans "Details" %}</label>
<div class="col-sm-6">
<input type="text" name="details" class="form-control" placeholder="{% trans "Details" %}">
</div>
</div>
</div> </div>
<div class="modal-footer"> <div class="modal-footer">
<button type="button" class="btn btn-default" data-dismiss="modal"> <button type="button" class="btn btn-default" data-dismiss="modal">
@ -156,4 +164,4 @@
</div> <!-- /.modal-content --> </div> <!-- /.modal-content -->
</div> <!-- /.modal-dialog --> </div> <!-- /.modal-dialog -->
</div><!-- /.modal --> </div><!-- /.modal -->
{% endif %} {% endif %}

View file

@ -40,6 +40,7 @@
<p>{% trans "Logical CPUs" %}</p> <p>{% trans "Logical CPUs" %}</p>
<p>{% trans "Processor" %}</p> <p>{% trans "Processor" %}</p>
<p>{% trans "Connection" %}</p> <p>{% trans "Connection" %}</p>
<p>{% trans "Details" %}</p>
</div> </div>
<div class="col-xs-8 col-sm-7"> <div class="col-xs-8 col-sm-7">
<p>{{ hostname }}</p> <p>{{ hostname }}</p>
@ -49,6 +50,7 @@
<p>{{ logical_cpu }}</p> <p>{{ logical_cpu }}</p>
<p>{{ model_cpu }}</p> <p>{{ model_cpu }}</p>
<p>{{ uri_conn }}</p> <p>{{ uri_conn }}</p>
<p>{{ compute.details }}</p>
</div> </div>
</div> </div>
<div class="row"> <div class="row">

View file

@ -3,6 +3,7 @@ import json
from django.http import HttpResponse, HttpResponseRedirect from django.http import HttpResponse, HttpResponseRedirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.contrib.auth.decorators import login_required
from computes.models import Compute from computes.models import Compute
from instances.models import Instance from instances.models import Instance
from accounts.models import UserInstance 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 from libvirt import libvirtError
@login_required
def computes(request): def computes(request):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
@ -36,14 +35,15 @@ def computes(request):
'status': connection_manager.host_is_up(compute.type, compute.hostname), 'status': connection_manager.host_is_up(compute.type, compute.hostname),
'type': compute.type, 'type': compute.type,
'login': compute.login, 'login': compute.login,
'password': compute.password 'password': compute.password,
'details': compute.details
}) })
return compute_data return compute_data
error_messages = [] error_messages = []
computes = Compute.objects.filter() computes = Compute.objects.filter().order_by('name')
computes_info = get_hosts_status(computes) computes_info = get_hosts_status(computes)
if request.method == 'POST': if request.method == 'POST':
if 'host_del' in request.POST: if 'host_del' in request.POST:
compute_id = request.POST.get('host_id', '') compute_id = request.POST.get('host_id', '')
@ -104,6 +104,7 @@ def computes(request):
if form.is_valid(): if form.is_valid():
data = form.cleaned_data data = form.cleaned_data
new_socket_host = Compute(name=data['name'], new_socket_host = Compute(name=data['name'],
details=data['details'],
hostname='localhost', hostname='localhost',
type=CONN_SOCKET, type=CONN_SOCKET,
login='', login='',
@ -122,6 +123,7 @@ def computes(request):
compute_edit.hostname = data['hostname'] compute_edit.hostname = data['hostname']
compute_edit.login = data['login'] compute_edit.login = data['login']
compute_edit.password = data['password'] compute_edit.password = data['password']
compute.edit_details = data['details']
compute_edit.save() compute_edit.save()
return HttpResponseRedirect(request.get_full_path()) return HttpResponseRedirect(request.get_full_path())
else: else:
@ -130,15 +132,13 @@ def computes(request):
return render(request, 'computes.html', locals()) return render(request, 'computes.html', locals())
@login_required
def overview(request, compute_id): def overview(request, compute_id):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
@ -160,15 +160,13 @@ def overview(request, compute_id):
return render(request, 'overview.html', locals()) return render(request, 'overview.html', locals())
@login_required
def compute_graph(request, compute_id): def compute_graph(request, compute_id):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('login'))
points = 5 points = 5
datasets = {} datasets = {}
cookies = {} cookies = {}

View file

@ -2,6 +2,7 @@ import re
from django.shortcuts import render from django.shortcuts import render
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from instances.models import Instance from instances.models import Instance
from vrtManager.instance import wvmInstance from vrtManager.instance import wvmInstance
from webvirtcloud.settings import WS_PORT from webvirtcloud.settings import WS_PORT
@ -9,15 +10,13 @@ from webvirtcloud.settings import WS_PUBLIC_HOST
from libvirt import libvirtError from libvirt import libvirtError
@login_required
def console(request): def console(request):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('login'))
if request.method == 'GET': if request.method == 'GET':
token = request.GET.get('token', '') token = request.GET.get('token', '')

View file

@ -239,7 +239,7 @@
</div> </div>
{% else %} {% else %}
<div class="col-lg-12"> <div class="col-lg-12">
<h3 class="page-header">{% trans "Create from flover" %}</h3> <h3 class="page-header">{% trans "Create from flavor" %}</h3>
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-bordered table-hover"> <table class="table table-bordered table-hover">
<thead> <thead>

View file

@ -2,6 +2,7 @@ from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from computes.models import Compute from computes.models import Compute
from create.models import Flavor from create.models import Flavor
from create.forms import FlavorAddForm, NewVMForm from create.forms import FlavorAddForm, NewVMForm
@ -11,15 +12,13 @@ from vrtManager import util
from libvirt import libvirtError from libvirt import libvirtError
@login_required
def create_instance(request, compute_id): def create_instance(request, compute_id):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))

View file

@ -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),
),
]

View file

@ -6,6 +6,7 @@ class Instance(models.Model):
compute = models.ForeignKey(Compute) compute = models.ForeignKey(Compute)
name = models.CharField(max_length=20) name = models.CharField(max_length=20)
uuid = models.CharField(max_length=36) uuid = models.CharField(max_length=36)
is_template = models.BooleanField(default=False)
def __unicode__(self): def __unicode__(self):
return self.name return self.name

View file

@ -35,16 +35,16 @@
{% trans "Close" %} {% trans "Close" %}
</button> </button>
{% if computes %} {% if computes %}
<button type="submit" class="btn btn-primary" name="chose" onclick='goto_compute()'> <button type="submit" class="btn btn-primary" name="choose" onclick='goto_compute()'>
{% trans "Chose" %} {% trans "Choose" %}
</button> </button>
{% else %} {% else %}
<button class="btn btn-primary disabled"> <button class="btn btn-primary disabled">
{% trans "Chose" %} {% trans "Choose" %}
</button> </button>
{% endif %} {% endif %}
</div> </div>
</div> <!-- /.modal-content --> </div> <!-- /.modal-content -->
</div> <!-- /.modal-dialog --> </div> <!-- /.modal-dialog -->
</div> <!-- /.modal --> </div> <!-- /.modal -->
{% endif %} {% endif %}

View file

@ -2,6 +2,7 @@
{% load i18n %} {% load i18n %}
{% block title %}{% trans "Instance" %} - {{ vname }}{% endblock %} {% block title %}{% trans "Instance" %} - {{ vname }}{% endblock %}
{% block content %} {% block content %}
{% include 'pleasewaitdialog.html' %}
<!-- Page Heading --> <!-- Page Heading -->
<div class="row"> <div class="row">
<table> <table>
@ -18,6 +19,9 @@
<span class="label label-warning">{% trans "Suspend" %}</span> <span class="label label-warning">{% trans "Suspend" %}</span>
{% endifequal %} {% endifequal %}
</td> </td>
<td>
<a href="{% url 'instance' compute.id vname %}" type="button" class="btn btn-xs btn-default"><span class="glyphicon glyphicon-refresh"></span></a>
</td>
</tr> </tr>
</table> </table>
<table width="65%"> <table width="65%">
@ -39,6 +43,9 @@
{% endfor %} {% endfor %}
</tr> </tr>
</table> </table>
{% if user_quota_msg %}
<span class="label label-warning">{{ user_quota_msg|capfirst }} quota reached.</span>
{% endif %}
<hr> <hr>
</div> </div>
@ -204,7 +211,12 @@
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="boot"> <div role="tabpanel" class="tab-pane tab-pane-bordered active" id="boot">
<p>{% trans "Click on Boot button to start this instance." %}</p> <p>{% trans "Click on Boot button to start this instance." %}</p>
<form action="" method="post" role="form">{% csrf_token %} <form action="" method="post" role="form">{% csrf_token %}
<input type="submit" name="poweron" class="btn btn-lg btn-success pull-right" value="{% trans "Power On" %}"> {% if instance.is_template %}
<p>{% trans "Template instance cannot be started." %}</p>
<input type="submit" name="poweron" class="btn btn-lg btn-success pull-right disabled" value="{% trans "Power On" %}">
{% else %}
<input type="submit" name="poweron" class="btn btn-lg btn-success pull-right" value="{% trans "Power On" %}">
{% endif %}
<div class="clearfix"></div> <div class="clearfix"></div>
</form> </form>
</div> </div>
@ -355,6 +367,15 @@
<small><input type="checkbox" class="js-custom__checkbox" /> {% trans "Custom value" %}</small> <small><input type="checkbox" class="js-custom__checkbox" /> {% trans "Custom value" %}</small>
</div> </div>
</div> </div>
<p style="font-weight:bold;">{% trans "Disk allocation (B):" %}</p>
{% for disk in disks %}
<div class="form-group">
<label class="col-sm-4 control-label" style="font-weight:normal;">{% trans "Current allocation" %} ({{ disk.dev }})</label>
<div class="col-sm-4 js-custom__container">
<input type="text" name="disk_size_{{ disk.dev }}" class="form-control" value="{{ disk.size|filesizeformat }}" />
</div>
</div>
{% endfor %}
{% ifequal status 5 %} {% ifequal status 5 %}
<button type="submit" class="btn btn-lg btn-success pull-right" name="resize">{% trans "Resize" %}</button> <button type="submit" class="btn btn-lg btn-success pull-right" name="resize">{% trans "Resize" %}</button>
{% else %} {% else %}
@ -486,11 +507,20 @@
</li> </li>
{% endif %} {% endif %}
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
<li role="presentation">
<a href="#network" aria-controls="network" role="tab" data-toggle="tab">
{% trans "Network" %}
</a>
</li>
{% endif %}
{% if request.user.is_superuser or request.user.userattributes.can_clone_instances %}
<li role="presentation"> <li role="presentation">
<a href="#clone" aria-controls="clone" role="tab" data-toggle="tab"> <a href="#clone" aria-controls="clone" role="tab" data-toggle="tab">
{% trans "Clone" %} {% trans "Clone" %}
</a> </a>
</li> </li>
{% endif %}
{% if request.user.is_superuser %}
<li role="presentation"> <li role="presentation">
<a href="#migrate" aria-controls="migrate" role="tab" data-toggle="tab"> <a href="#migrate" aria-controls="migrate" role="tab" data-toggle="tab">
{% trans "Migrate" %} {% trans "Migrate" %}
@ -501,6 +531,16 @@
{% trans "XML" %} {% trans "XML" %}
</a> </a>
</li> </li>
<li role="presentation">
<a href="#options" aria-controls="options" role="tab" data-toggle="tab">
{% trans "Options" %}
</a>
</li>
<li role="presentation">
<a href="#users" aria-controls="users" role="tab" data-toggle="tab">
{% trans "Users" %}
</a>
</li>
{% endif %} {% endif %}
</ul> </ul>
<!-- Tab panes --> <!-- Tab panes -->
@ -651,51 +691,118 @@
</div> </div>
{% endif %} {% endif %}
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="network">
<p>{% trans "Assign network device to bridge" %}</p>
<form class="form-horizontal" action="" method="post" role="form">{% csrf_token %}
<p style="font-weight:bold;">{% trans "Network devices" %}</p>
{% for network in networks %}
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">eth{{ forloop.counter0 }}</label>
<div class="col-sm-4">
<input type="text" class="form-control" name="net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
</div>
<div class="col-sm-3">
<input type="text" class="form-control" name="net-source-{{ forloop.counter0 }}" value="{{ network.nic }}"/>
</div>
</div>
{% endfor %}
{% ifequal status 5 %}
<button type="submit" class="btn btn-lg btn-success pull-right" name="change_network">{% trans "Change" %}</button>
{% else %}
<button class="btn btn-lg btn-success pull-right disabled" name="change_network">{% trans "Change" %}</button>
{% endifequal %}
</form>
<div class="clearfix"></div>
</div>
{% endif %}
{% if request.user.is_superuser or request.user.userattributes.can_clone_instances %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="clone"> <div role="tabpanel" class="tab-pane tab-pane-bordered" id="clone">
<p style="font-weight:bold;">{% trans "Create a clone" %}</p> <p style="font-weight:bold;">{% trans "Create a clone" %}</p>
<form class="form-horizontal" action="" method="post" role="form">{% csrf_token %} <form class="form-horizontal" action="" method="post" role="form">{% csrf_token %}
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Clone Name" %}</label> <label class="col-sm-3 control-label" style="font-weight:normal;">{% trans "Clone Name" %}</label>
<div class="col-sm-3"> {% if request.user.is_superuser %}
<input type="text" class="form-control" name="name" value="{{ vname }}-clone"/> <div class="col-sm-4">
<input id="clone_name" type="text" class="form-control" name="name" value="{{ vname }}-clone"/>
</div>
<div class="col-sm-4">
<button type="button" class="btn btn-sm btn-success pull-left" name="guess-clone-name"
onclick="guess_clone_name()" style="margin-top: 2px;">{% trans "Guess" %}</button>
</div>
{% else %}
<div class="col-sm-4">
<select id="select_clone_name" class="form-control" name="name" size="1"/>
{% for name in clone_free_names %}
<option value="{{ name }}">{{ name }}</option>
{% endfor %}
</select>
</div>
{% endif %}
</div>
{% if request.user.is_superuser %}
<p style="font-weight:bold;">{% trans "Network devices" %}</p>
{% for network in networks %}
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">eth{{ forloop.counter0 }} ({{ network.nic }})</label>
<div class="col-sm-4">
<input type="text" class="form-control" name="clone-net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
</div>
<div class="col-sm-4">
<button type="button" class="btn btn-sm btn-success pull-left" name="random-mac-{{ forloop.counter0 }}"
onclick="random_mac({{ forloop.counter0 }})" style="margin-top: 2px;">{% trans "Random" %}</button>
<button type="button" class="btn btn-sm btn-success pull-left" name="guess-mac-{{ forloop.counter0 }}"
onclick="guess_mac_address('#clone_name', {{ forloop.counter0 }})" style="margin-top: 2px;">{% trans "Guess" %}</button>
</div>
</div>
{% endfor %}
{% else %}
{% for network in networks %}
<input type="hidden" class="form-control" name="clone-net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
{% endfor %}
{% endif %}
{% if request.user.is_superuser %}
<p style="font-weight:bold;">{% trans "Storage devices" %}</p>
{% for disk in clone_disks %}
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{{ disk.dev }} ({{ disk.storage }})</label>
<div class="col-sm-4">
<input id="disk_name-{{ disk.dev }}" type="text" class="form-control" name="disk-{{ disk.dev }}" value="{{ disk.image }}"/>
</div>
{% ifequal disk.format 'qcow2' %}
<label class="col-sm-2 control-label" style="font-weight:normal;margin-left:-35px;">Metadata</label>
<div class="col-sm-1">
<input type="checkbox" name="meta-{{ disk.dev }}" value="true" style="margin-top: 10px;">
</div>
{% endifequal %}
</div>
{% endfor %}
{% else %}
{% for disk in clone_disks %}
<input id="disk_name-{{ disk.dev }}" type="hidden" class="form-control" name="disk-{{ disk.dev }}" value="{{ disk.image }}"/>
{% endfor %}
{% endif %}
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Title" %}</label>
<div class="col-sm-6">
<input type="text" name="clone-title" class="form-control">
</div> </div>
</div> </div>
<p style="font-weight:bold;">{% trans "Network devices" %}</p> <div class="form-group">
{% for network in networks %} <label class="col-sm-3 control-label">{% trans "Description" %}</label>
<div class="form-group"> <div class="col-sm-6">
<label class="col-sm-3 control-label" style="font-weight:normal;">eth{{ forloop.counter0 }} ({{ network.nic }})</label> <textarea name="clone-description" class="form-control"></textarea>
<div class="col-sm-3">
<input type="text" class="form-control" name="net-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
</div>
<div class="col-sm-1">
<button type="button" class="btn btn-sm btn-success pull-left" name="random-mac-{{ forloop.counter0 }}"
onclick="random_mac({{ forloop.counter0 }})" style="margin-top: 2px;">{% trans "Random" %}</button>
</div>
</div> </div>
{% endfor %} </div>
<p style="font-weight:bold;">{% trans "Storage devices" %}</p>
{% for disk in clone_disks %}
<div class="form-group">
<label class="col-sm-3 control-label" style="font-weight:normal;">{{ disk.dev }} ({{ disk.storage }})</label>
<div class="col-sm-3">
<input type="text" class="form-control" name="disk-{{ disk.dev }}" value="{{ disk.image }}"/>
</div>
{% ifequal disk.format 'qcow2' %}
<label class="col-sm-2 control-label" style="font-weight:normal;margin-left:-35px;">Metadata</label>
<div class="col-sm-1">
<input type="checkbox" name="meta-{{ disk.dev }}" value="true" style="margin-top: 10px;">
</div>
{% endifequal %}
</div>
{% endfor %}
{% ifequal status 5 %} {% ifequal status 5 %}
<button type="submit" class="btn btn-lg btn-success pull-right" name="clone">{% trans "Clone" %}</button> <button type="submit" class="btn btn-lg btn-success pull-right" name="clone" onclick="showPleaseWaitDialog();">{% trans "Clone" %}</button>
{% else %} {% else %}
<button class="btn btn-lg btn-success pull-right disabled" name="clone">{% trans "Clone" %}</button> <button class="btn btn-lg btn-success pull-right disabled" name="clone">{% trans "Clone" %}</button>
{% endifequal %} {% endifequal %}
</form> </form>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
{% endif %}
{% if request.user.is_superuser %}
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="migrate"> <div role="tabpanel" class="tab-pane tab-pane-bordered" id="migrate">
<p>{% trans "For migration both host servers must have equal settings and OS type" %}</p> <p>{% trans "For migration both host servers must have equal settings and OS type" %}</p>
<form class="form-horizontal" method="post" role="form">{% csrf_token %} <form class="form-horizontal" method="post" role="form">{% csrf_token %}
@ -722,7 +829,7 @@
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Live migration" %}</label> <label class="col-sm-3 control-label">{% trans "Live migration" %}</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="checkbox" name="live_migrate" value="true" id="vm_live_migrate"> <input type="checkbox" name="live_migrate" value="true" id="vm_live_migrate" checked>
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
@ -734,11 +841,17 @@
<div class="form-group"> <div class="form-group">
<label class="col-sm-3 control-label">{% trans "Delete original" %}</label> <label class="col-sm-3 control-label">{% trans "Delete original" %}</label>
<div class="col-sm-6"> <div class="col-sm-6">
<input type="checkbox" name="xml_delete" value="true" id="xml_delete"> <input type="checkbox" name="xml_delete" value="true" id="xml_delete" checked>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Offline migration" %}</label>
<div class="col-sm-6">
<input type="checkbox" name="offline_migrate" value="true" id="offline_migrate">
</div> </div>
</div> </div>
{% if computes_count != 1 %} {% if computes_count != 1 %}
<button type="submit" class="btn btn-lg btn-success pull-right" name="migrate">{% trans "Migrate" %}</button> <button type="submit" class="btn btn-lg btn-success pull-right" name="migrate" onclick="showPleaseWaitDialog();">{% trans "Migrate" %}</button>
{% else %} {% else %}
<button class="btn btn-lg btn-success pull-right disabled">{% trans "Migrate" %}</button> <button class="btn btn-lg btn-success pull-right disabled">{% trans "Migrate" %}</button>
{% endif %} {% endif %}
@ -764,6 +877,41 @@
</form> </form>
<div class="clearfix"></div> <div class="clearfix"></div>
</div> </div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="options">
<form class="form-horizontal" action="" method="post" role="form">{% csrf_token %}
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Title" %}</label>
<div class="col-sm-6">
<input type="text" name="title" class="form-control" value="{{ title }}">
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Description" %}</label>
<div class="col-sm-6">
<textarea name="description" class="form-control">{{ description }}</textarea>
</div>
</div>
<div class="form-group">
<label class="col-sm-3 control-label">{% trans "Is template" %}</label>
<div class="col-sm-6">
<input type="checkbox" name="is_template" value="true" id="is_template" {% if instance.is_template %}checked{% endif %}>
</div>
</div>
{% ifequal status 5 %}
<button type="submit" class="btn btn-lg btn-success pull-right" name="change_options">{% trans "Change" %}</button>
{% else %}
<button class="btn btn-lg btn-success pull-right disabled" name="change_options">{% trans "Change" %}</button>
{% endifequal %}
</form>
<div class="clearfix"></div>
</div>
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="users">
<p style="font-weight:bold;">{% trans "Instance owners" %}</p>
{% for userinstance in userinstances %}
<p><a href="{% url 'account' userinstance.user.id %}">{{ userinstance.user }}</a></p>
{% endfor %}
<div class="clearfix"></div>
</div>
{% endif %} {% endif %}
</div> </div>
</div> </div>
@ -847,7 +995,7 @@
<form class="form-group" method="post" role="form">{% csrf_token %} <form class="form-group" method="post" role="form">{% csrf_token %}
<div class="checkbox" style="margin-left: 8px;"> <div class="checkbox" style="margin-left: 8px;">
<label> <label>
<input type="checkbox" name="delete_disk" value="true"> <input type="checkbox" name="delete_disk" value="true" checked>
<strong>{% trans "Remove Instance's data" %}</strong> <strong>{% trans "Remove Instance's data" %}</strong>
</label> </label>
</div> </div>
@ -892,7 +1040,7 @@
macAddress+=hexDigits.charAt(Math.round(Math.random()*16)); macAddress+=hexDigits.charAt(Math.round(Math.random()*16));
if (i != 2) macAddress+=":"; if (i != 2) macAddress+=":";
} }
$('input[name="net-'+net+'"]').val(macAddress); $('input[name="clone-net-mac-'+net+'"]').val(macAddress);
}; };
</script> </script>
<script> <script>
@ -904,6 +1052,46 @@
} }
} }
</script> </script>
<script>
function guess_mac_address(src_elem, net) {
new_vname = $(src_elem).val();
$.getJSON('/instance/guess_mac_address/' + new_vname + '/', function(data) {
$('input[name="clone-net-mac-'+net+'"]').val(data['mac']);
});
}
</script>
<script>
function guess_clone_name() {
$.getJSON('/instance/guess_clone_name/', function(data) {
guessed_name = data['name'].split(".")[0];
$('#clone_name').val(guessed_name);
update_clone_disk_name(guessed_name);
guess_mac_address('#clone_name', 0);
});
}
</script>
<script>
function update_clone_disk_name(new_vname) {
vname = '{{ vname }}-clone';
{% for disk in clone_disks %}
disk_name = '{{ disk.image }}';
disk_minus = disk_name.split('-');
disk_minus_suffix = disk_minus[disk_minus.length-1];
disk_minus.pop();
disk_minus_name = disk_minus.join('-');
disk_dot = disk_name.split('.')
disk_dot_suffix = disk_dot[disk_dot.length-1];
if (disk_name.lastIndexOf('-') > -1 && disk_minus_name == vname) {
image = new_vname + "-" + disk_minus_suffix;
} else if (disk_name.lastIndexOf('.') > -1 && disk_dot_suffix.length <= 7) {
image = new_vname + "." + disk_dot_suffix
} else {
image = new_vname + '-clone';
}
$('#disk_name-{{ disk.dev }}').val(image);
{% endfor %}
}
</script>
<script> <script>
$(document).on('change', '#console_passwd_gen', function () { $(document).on('change', '#console_passwd_gen', function () {
if ($(this).prop('checked')) { if ($(this).prop('checked')) {
@ -928,6 +1116,9 @@
$('#console_keymap_selection').show(); $('#console_keymap_selection').show();
} }
}); });
$('#clone_name').on('input', function () {
update_clone_disk_name($(this).val());
});
$(document).ready(function () { $(document).ready(function () {
// set current console keymap or fall back to default // set current console keymap or fall back to default
var keymap = "{{ console_keymap }}" var keymap = "{{ console_keymap }}"
@ -942,6 +1133,16 @@
$("#console_select_type option[value='" + console_type + "']").prop('selected', true); $("#console_select_type option[value='" + console_type + "']").prop('selected', true);
} }
}); });
{% if not request.user.is_superuser %}
$('#select_clone_name').on('change', function () {
update_clone_disk_name($(this).val());
guess_mac_address('#select_clone_name', 0);
});
$(document).ready(function () {
update_clone_disk_name($('#select_clone_name').val());
guess_mac_address('#select_clone_name', 0);
});
{% endif %}
</script> </script>
<script> <script>
$(function () { $(function () {
@ -1116,7 +1317,7 @@
} }
}); });
} }
if (~$.inArray(hash, ['#media', '#clone', '#autostart', '#xmledit', '#vncsettings', '#migrate'])) { if (~$.inArray(hash, ['#media', '#network', '#clone', '#autostart', '#xmledit', '#vncsettings', '#migrate', '#options', '#users'])) {
var btnsect = $('#navbtn>li>a'); var btnsect = $('#navbtn>li>a');
$(btnsect).each(function () { $(btnsect).each(function () {
if ($(this).attr('href') === '#settings') { if ($(this).attr('href') === '#settings') {

View file

@ -39,8 +39,8 @@
<table class="table table-hover table-striped sortable-theme-bootstrap" data-sortable> <table class="table table-hover table-striped sortable-theme-bootstrap" data-sortable>
<thead> <thead>
<tr> <tr>
<th>Name</th> <th>Name<br>Description</th>
<th>Host</th> <th>Host<br>User</th>
<th>Status</th> <th>Status</th>
<th>VCPU</th> <th>VCPU</th>
<th>Memory</th> <th>Memory</th>
@ -51,8 +51,8 @@
{% for host, inst in all_host_vms.items %} {% for host, inst in all_host_vms.items %}
{% for vm, info in inst.items %} {% for vm, info in inst.items %}
<tr> <tr>
<td><a href="{% url 'instance' host.0 vm %}">{{ vm }}</a></td> <td><a href="{% url 'instance' host.0 vm %}">{{ vm }}</a><br><small><em>{{ info.title }}</em></small></td>
<td><a href="{% url 'overview' host.0 %}">{{ host.1 }}</a></td> <td><a href="{% url 'overview' host.0 %}">{{ host.1 }}</a><br><small><em>{% if info.userinstances.count > 0 %}{{ info.userinstances.first_user.user.username }}{% if info.userinstances.count > 1 %} (+{{ info.userinstances.count|add:"-1" }}){% endif %}{% endif %}</em></small></td>
<td>{% ifequal info.status 1 %} <td>{% ifequal info.status 1 %}
<span class="text-success">{% trans "Active" %}</span> <span class="text-success">{% trans "Active" %}</span>
{% endifequal %} {% endifequal %}
@ -69,9 +69,15 @@
<input type="hidden" name="name" value="{{ vm }}"/> <input type="hidden" name="name" value="{{ vm }}"/>
<input type="hidden" name="compute_id" value="{{ host.0 }}"/> <input type="hidden" name="compute_id" value="{{ host.0 }}"/>
{% ifequal info.status 5 %} {% ifequal info.status 5 %}
<button class="btn btn-sm btn-default" type="submit" name="poweron" title="{% trans "Power On" %}"> {% if info.is_template %}
<span class="glyphicon glyphicon-play"></span> <button class="btn btn-sm btn-default" type="button" name="clone" title="{% trans "Clone" %}" onclick="goto_instance_clone({{ host.0 }}, '{{ vm }}');">
</button> <span class="glyphicon glyphicon-duplicate"></span>
</button>
{% else %}
<button class="btn btn-sm btn-default" type="submit" name="poweron" title="{% trans "Power On" %}">
<span class="glyphicon glyphicon-play"></span>
</button>
{% endif %}
<button class="btn btn-sm btn-default disabled" title="{% trans "Suspend" %}"> <button class="btn btn-sm btn-default disabled" title="{% trans "Suspend" %}">
<span class="glyphicon glyphicon-pause"></span> <span class="glyphicon glyphicon-pause"></span>
</button> </button>
@ -149,7 +155,7 @@
<tbody class="searchable"> <tbody class="searchable">
{% for inst, vm in all_user_vms.items %} {% for inst, vm in all_user_vms.items %}
<tr> <tr>
<td><a href="{% url 'instance' vm.compute_id vm.name %}">{{ vm.name }}</a></td> <td><a href="{% url 'instance' vm.compute_id vm.name %}">{{ vm.name }}</a><br><small><em>{{ vm.title }}</em></small></td>
<td>{% ifequal vm.status 1 %} <td>{% ifequal vm.status 1 %}
<span class="text-success">{% trans "Active" %}</span> <span class="text-success">{% trans "Active" %}</span>
{% endifequal %} {% endifequal %}
@ -166,9 +172,15 @@
<input type="hidden" name="name" value="{{ vm.name }}"/> <input type="hidden" name="name" value="{{ vm.name }}"/>
<input type="hidden" name="compute_id" value="{{ vm.compute_id }}"/> <input type="hidden" name="compute_id" value="{{ vm.compute_id }}"/>
{% ifequal vm.status 5 %} {% ifequal vm.status 5 %}
<button class="btn btn-sm btn-default" type="submit" name="poweron" title="Power On"> {% if inst.instance.is_template %}
<span class="glyphicon glyphicon-play"></span> <button class="btn btn-sm btn-default" type="button" name="clone" title="{% trans "Clone" %}" onclick="goto_instance_clone({{ vm.compute_id }}, '{{ vm.name }}');">
</button> <span class="glyphicon glyphicon-duplicate"></span>
</button>
{% else %}
<button class="btn btn-sm btn-default" type="submit" name="poweron" title="{% trans "Power On" %}">
<span class="glyphicon glyphicon-play"></span>
</button>
{% endif %}
<button class="btn btn-sm btn-default disabled" title="{% trans "Power Off" %}"> <button class="btn btn-sm btn-default disabled" title="{% trans "Power Off" %}">
<span class="glyphicon glyphicon-off"></span> <span class="glyphicon glyphicon-off"></span>
</button> </button>
@ -227,18 +239,30 @@
} }
</script> </script>
<script> <script>
function filter_table() {
var rex = new RegExp($(this).val(), 'i');
$('.searchable tr').hide();
$('.searchable tr').filter(function () {
return rex.test($(this).text());
}).show();
Cookies.set("instances_filter", $(this).val(), { expires: 1 });
}
$(document).ready(function () { $(document).ready(function () {
instances_filter_cookie = Cookies.get("instances_filter");
if (instances_filter_cookie) {
$('#filter').val(instances_filter_cookie);
$('#filter').each(filter_table);
}
(function ($) { (function ($) {
$('#filter').keyup(function () { $('#filter').keyup(filter_table)
var rex = new RegExp($(this).val(), 'i');
$('.searchable tr').hide();
$('.searchable tr').filter(function () {
return rex.test($(this).text());
}).show();
})
}(jQuery)); }(jQuery));
}); });
</script> </script>
<script>
function goto_instance_clone(compute, instance) {
window.location = "/instance/" + compute + "/" + instance + "/#clone";
}
</script>
{% if request.user.is_superuser %} {% if request.user.is_superuser %}
<script> <script>
function goto_compute() { function goto_compute() {

View file

@ -8,4 +8,10 @@ urlpatterns = [
views.inst_graph, name='inst_graph'), views.inst_graph, name='inst_graph'),
url(r'^status/(?P<compute_id>[0-9]+)/(?P<vname>[\w\-\.]+)/$', url(r'^status/(?P<compute_id>[0-9]+)/(?P<vname>[\w\-\.]+)/$',
views.inst_status, name='inst_status'), views.inst_status, name='inst_status'),
url(r'^guess_mac_address/(?P<vname>[\w\-\.]+)/$',
views.guess_mac_address, name='guess_mac_address'),
url(r'^guess_clone_name/$',
views.guess_clone_name, name='guess_clone_name'),
url(r'^check_instance/(?P<vname>[\w\-\.]+)/$',
views.check_instance, name='check_instance'),
] ]

View file

@ -1,7 +1,9 @@
import os
import time import time
import json import json
import socket import socket
import crypt import crypt
import re
from string import letters, digits from string import letters, digits
from random import choice from random import choice
from bisect import insort from bisect import insort
@ -9,6 +11,7 @@ from django.http import HttpResponse, HttpResponseRedirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.contrib.auth.decorators import login_required
from computes.models import Compute from computes.models import Compute
from instances.models import Instance from instances.models import Instance
from accounts.models import UserInstance, UserSSHKey from accounts.models import UserInstance, UserSSHKey
@ -19,34 +22,41 @@ from vrtManager.util import randomPasswd
from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE from libvirt import libvirtError, VIR_DOMAIN_XML_SECURE
from webvirtcloud.settings import QEMU_KEYMAPS, QEMU_CONSOLE_TYPES from webvirtcloud.settings import QEMU_KEYMAPS, QEMU_CONSOLE_TYPES
from logs.views import addlogmsg from logs.views import addlogmsg
from django.conf import settings
@login_required
def index(request): def index(request):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated(): return HttpResponseRedirect(reverse('instances'))
return HttpResponseRedirect(reverse('login'))
else:
return HttpResponseRedirect(reverse('instances'))
@login_required
def instances(request): def instances(request):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
error_messages = [] error_messages = []
all_host_vms = {} all_host_vms = {}
all_user_vms = {} all_user_vms = {}
computes = Compute.objects.all() computes = Compute.objects.all()
def get_userinstances_info(instance):
info = {}
uis = UserInstance.objects.filter(instance=instance)
info['count'] = len(uis)
if len(uis) > 0:
info['first_user'] = uis[0]
else:
info['first_user'] = None
return info
if not request.user.is_superuser: if not request.user.is_superuser:
user_instances = UserInstance.objects.filter(user_id=request.user.id) user_instances = UserInstance.objects.filter(user_id=request.user.id)
for usr_inst in user_instances: for usr_inst in user_instances:
@ -70,6 +80,8 @@ def instances(request):
check_uuid = Instance.objects.get(compute_id=comp.id, name=vm) check_uuid = Instance.objects.get(compute_id=comp.id, name=vm)
if check_uuid.uuid != info['uuid']: if check_uuid.uuid != info['uuid']:
check_uuid.save() check_uuid.save()
all_host_vms[comp.id, comp.name][vm]['is_template'] = check_uuid.is_template
all_host_vms[comp.id, comp.name][vm]['userinstances'] = get_userinstances_info(check_uuid)
except Instance.DoesNotExist: except Instance.DoesNotExist:
check_uuid = Instance(compute_id=comp.id, name=vm, uuid=info['uuid']) check_uuid = Instance(compute_id=comp.id, name=vm, uuid=info['uuid'])
check_uuid.save() check_uuid.save()
@ -145,15 +157,13 @@ def instances(request):
return render(request, 'instances.html', locals()) return render(request, 'instances.html', locals())
@login_required
def instance(request, compute_id, vname): def instance(request, compute_id, vname):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
error_messages = [] error_messages = []
messages = [] messages = []
compute = get_object_or_404(Compute, pk=compute_id) compute = get_object_or_404(Compute, pk=compute_id)
@ -173,12 +183,15 @@ def instance(request, compute_id, vname):
if not userinstace: if not userinstace:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
def show_clone_disk(disks): def show_clone_disk(disks, vname=''):
clone_disk = [] clone_disk = []
for disk in disks: for disk in disks:
if disk['image'] is None: if disk['image'] is None:
continue continue
if disk['image'].count(".") and len(disk['image'].rsplit(".", 1)[1]) <= 7: if disk['image'].count("-") and disk['image'].rsplit("-", 1)[0] == vname:
name, suffix = disk['image'].rsplit("-", 1)
image = name + "-clone" + "-" + suffix
elif disk['image'].count(".") and len(disk['image'].rsplit(".", 1)[1]) <= 7:
name, suffix = disk['image'].rsplit(".", 1) name, suffix = disk['image'].rsplit(".", 1)
image = name + "-clone" + "." + suffix image = name + "-clone" + "." + suffix
else: else:
@ -187,6 +200,71 @@ def instance(request, compute_id, vname):
{'dev': disk['dev'], 'storage': disk['storage'], {'dev': disk['dev'], 'storage': disk['storage'],
'image': image, 'format': disk['format']}) 'image': image, 'format': disk['format']})
return clone_disk return clone_disk
def filesizefstr(size_str):
if size_str == '':
return 0
size_str = size_str.encode('ascii', 'ignore').upper().translate(None, " B")
if 'K' == size_str[-1]:
return long(float(size_str[:-1]))<<10
elif 'M' == size_str[-1]:
return long(float(size_str[:-1]))<<20
elif 'G' == size_str[-1]:
return long(float(size_str[:-1]))<<30
elif 'T' == size_str[-1]:
return long(float(size_str[:-1]))<<40
elif 'P' == size_str[-1]:
return long(float(size_str[:-1]))<<50
else:
return long(float(size_str))
def get_clone_free_names(size=10):
prefix = settings.CLONE_INSTANCE_DEFAULT_PREFIX
free_names = []
existing_names = [i.name for i in Instance.objects.filter(name__startswith=prefix)]
index = 1
while len(free_names) < size:
new_name = prefix + str(index)
if new_name not in existing_names:
free_names.append(new_name)
index += 1
return free_names
def check_user_quota(instance, cpu, memory, disk_size):
user_instances = UserInstance.objects.filter(user_id=request.user.id, instance__is_template=False)
instance += len(user_instances)
for usr_inst in user_instances:
if connection_manager.host_is_up(usr_inst.instance.compute.type,
usr_inst.instance.compute.hostname):
conn = wvmInstance(usr_inst.instance.compute,
usr_inst.instance.compute.login,
usr_inst.instance.compute.password,
usr_inst.instance.compute.type,
usr_inst.instance.name)
cpu += int(conn.get_vcpu())
memory += int(conn.get_memory())
for disk in conn.get_disk_device():
disk_size += int(disk['size'])>>30
ua = request.user.userattributes
msg = ""
if ua.max_instances > 0 and instance > ua.max_instances:
msg = "instance"
if settings.QUOTA_DEBUG:
msg += " (%s > %s)" % (instance, ua.max_instances)
if ua.max_cpus > 0 and cpu > ua.max_cpus:
msg = "cpu"
if settings.QUOTA_DEBUG:
msg += " (%s > %s)" % (cpu, ua.max_cpus)
if ua.max_memory > 0 and memory > ua.max_memory:
msg = "memory"
if settings.QUOTA_DEBUG:
msg += " (%s > %s)" % (memory, ua.max_memory)
if ua.max_disk_size > 0 and disk_size > ua.max_disk_size:
msg = "disk"
if settings.QUOTA_DEBUG:
msg += " (%s > %s)" % (disk_size, ua.max_disk_size)
return msg
try: try:
conn = wvmInstance(compute.hostname, conn = wvmInstance(compute.hostname,
@ -202,6 +280,7 @@ def instance(request, compute_id, vname):
uuid = conn.get_uuid() uuid = conn.get_uuid()
memory = conn.get_memory() memory = conn.get_memory()
cur_memory = conn.get_cur_memory() cur_memory = conn.get_cur_memory()
title = conn.get_title()
description = conn.get_description() description = conn.get_description()
disks = conn.get_disk_device() disks = conn.get_disk_device()
media = conn.get_media_device() media = conn.get_media_device()
@ -222,8 +301,10 @@ def instance(request, compute_id, vname):
snapshots = sorted(conn.get_snapshot(), reverse=True) snapshots = sorted(conn.get_snapshot(), reverse=True)
inst_xml = conn._XMLDesc(VIR_DOMAIN_XML_SECURE) inst_xml = conn._XMLDesc(VIR_DOMAIN_XML_SECURE)
has_managed_save_image = conn.get_managed_save_image() has_managed_save_image = conn.get_managed_save_image()
clone_disks = show_clone_disk(disks) clone_disks = show_clone_disk(disks, vname)
console_passwd = conn.get_console_passwd() console_passwd = conn.get_console_passwd()
clone_free_names = get_clone_free_names()
user_quota_msg = check_user_quota(0, 0, 0, 0)
try: try:
instance = Instance.objects.get(compute_id=compute_id, name=vname) instance = Instance.objects.get(compute_id=compute_id, name=vname)
@ -234,6 +315,8 @@ def instance(request, compute_id, vname):
instance = Instance(compute_id=compute_id, name=vname, uuid=uuid) instance = Instance(compute_id=compute_id, name=vname, uuid=uuid)
instance.save() instance.save()
userinstances = UserInstance.objects.filter(instance=instance).order_by('user__username')
if request.method == 'POST': if request.method == 'POST':
if 'poweron' in request.POST: if 'poweron' in request.POST:
conn.start() conn.start()
@ -264,6 +347,8 @@ def instance(request, compute_id, vname):
if conn.get_status() == 1: if conn.get_status() == 1:
conn.force_shutdown() conn.force_shutdown()
if request.POST.get('delete_disk', ''): if request.POST.get('delete_disk', ''):
for snap in snapshots:
conn.snapshot_delete(snap['name'])
conn.delete_disk() conn.delete_disk()
conn.delete() conn.delete()
@ -271,15 +356,11 @@ def instance(request, compute_id, vname):
instance_name = instance.name instance_name = instance.name
instance.delete() instance.delete()
if not request.user.is_superuser: try:
del_userinstance = UserInstance.objects.get(id=userinstace.id) del_userinstance = UserInstance.objects.filter(instance__compute_id=compute_id, instance__name=vname)
del_userinstance.delete() del_userinstance.delete()
else: except UserInstance.DoesNotExist:
try: pass
del_userinstance = UserInstance.objects.filter(instance__compute_id=compute_id, instance__name=vname)
del_userinstance.delete()
except UserInstance.DoesNotExist:
pass
msg = _("Destroy") msg = _("Destroy")
addlogmsg(request.user.username, instance_name, msg) addlogmsg(request.user.username, instance_name, msg)
@ -331,20 +412,37 @@ def instance(request, compute_id, vname):
error_messages.append(msg) error_messages.append(msg)
if 'resize' in request.POST and (request.user.is_superuser or userinstace.is_change): if 'resize' in request.POST and (request.user.is_superuser or userinstace.is_change):
vcpu = request.POST.get('vcpu', '') new_vcpu = request.POST.get('vcpu', '')
cur_vcpu = request.POST.get('cur_vcpu', '') new_cur_vcpu = request.POST.get('cur_vcpu', '')
memory = request.POST.get('memory', '') new_memory = request.POST.get('memory', '')
memory_custom = request.POST.get('memory_custom', '') new_memory_custom = request.POST.get('memory_custom', '')
if memory_custom: if new_memory_custom:
memory = memory_custom new_memory = new_memory_custom
cur_memory = request.POST.get('cur_memory', '') new_cur_memory = request.POST.get('cur_memory', '')
cur_memory_custom = request.POST.get('cur_memory_custom', '') new_cur_memory_custom = request.POST.get('cur_memory_custom', '')
if cur_memory_custom: if new_cur_memory_custom:
cur_memory = cur_memory_custom new_cur_memory = new_cur_memory_custom
conn.resize(cur_memory, memory, cur_vcpu, vcpu) disks_new = []
msg = _("Resize") for disk in disks:
addlogmsg(request.user.username, instance.name, msg) input_disk_size = filesizefstr(request.POST.get('disk_size_' + disk['dev'], ''))
return HttpResponseRedirect(request.get_full_path() + '#resize') if input_disk_size > disk['size']+(64<<20):
disk['size_new'] = input_disk_size
disks_new.append(disk)
disk_sum = sum([disk['size']>>30 for disk in disks_new])
disk_new_sum = sum([disk['size_new']>>30 for disk in disks_new])
quota_msg = check_user_quota(0, int(new_vcpu)-vcpu, int(new_memory)-memory, disk_new_sum-disk_sum)
if not request.user.is_superuser and quota_msg:
msg = _("User %s quota reached, cannot resize '%s'!" % (quota_msg, instance.name))
error_messages.append(msg)
else:
cur_memory = new_cur_memory
memory = new_memory
cur_vcpu = new_cur_vcpu
vcpu = new_vcpu
conn.resize(cur_memory, memory, cur_vcpu, vcpu, disks_new)
msg = _("Resize")
addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#resize')
if 'umount_iso' in request.POST: if 'umount_iso' in request.POST:
image = request.POST.get('path', '') image = request.POST.get('path', '')
@ -463,30 +561,85 @@ def instance(request, compute_id, vname):
live = request.POST.get('live_migrate', False) live = request.POST.get('live_migrate', False)
unsafe = request.POST.get('unsafe_migrate', False) unsafe = request.POST.get('unsafe_migrate', False)
xml_del = request.POST.get('xml_delete', False) xml_del = request.POST.get('xml_delete', False)
offline = request.POST.get('offline_migrate', False)
new_compute = Compute.objects.get(id=compute_id) new_compute = Compute.objects.get(id=compute_id)
conn_migrate = wvmInstances(new_compute.hostname, conn_migrate = wvmInstances(new_compute.hostname,
new_compute.login, new_compute.login,
new_compute.password, new_compute.password,
new_compute.type) new_compute.type)
conn_migrate.moveto(conn, vname, live, unsafe, xml_del) conn_migrate.moveto(conn, vname, live, unsafe, xml_del, offline)
conn_migrate.define_move(vname) instance.compute = new_compute
instance.save()
conn_migrate.close() conn_migrate.close()
msg = _("Migrate") if autostart:
conn_new = wvmInstance(new_compute.hostname,
new_compute.login,
new_compute.password,
new_compute.type,
vname)
conn_new.set_autostart(1)
conn_new.close()
msg = _("Migrate to %s" % new_compute.hostname)
addlogmsg(request.user.username, instance.name, msg) addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(reverse('instance', args=[compute_id, vname])) return HttpResponseRedirect(reverse('instance', args=[compute_id, vname]))
if 'change_network' in request.POST:
network_data = {}
for post in request.POST:
if post.startswith('net-'):
network_data[post] = request.POST.get(post, '')
conn.change_network(network_data)
msg = _("Edit network")
addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#network')
if 'change_options' in request.POST:
instance.is_template = request.POST.get('is_template', False)
instance.save()
options = {}
for post in request.POST:
if post in ['title', 'description']:
options[post] = request.POST.get(post, '')
conn.set_options(options)
msg = _("Edit options")
addlogmsg(request.user.username, instance.name, msg)
return HttpResponseRedirect(request.get_full_path() + '#options')
if request.user.is_superuser or request.user.userattributes.can_clone_instances:
if 'clone' in request.POST: if 'clone' in request.POST:
clone_data = {} clone_data = {}
clone_data['name'] = request.POST.get('name', '') clone_data['name'] = request.POST.get('name', '')
for post in request.POST: disk_sum = sum([disk['size']>>30 for disk in disks])
if 'disk' or 'meta' in post: quota_msg = check_user_quota(1, vcpu, memory, disk_sum)
check_instance = Instance.objects.filter(name=clone_data['name'])
if not request.user.is_superuser and quota_msg:
msg = _("User %s quota reached, cannot create '%s'!" % (quota_msg, clone_data['name']))
error_messages.append(msg)
elif check_instance:
msg = _("Instance '%s' already exists!" % clone_data['name'])
error_messages.append(msg)
elif not re.match(r'^[a-zA-Z0-9-]+$', clone_data['name']):
msg = _("Instance name '%s' contains invalid characters!" % clone_data['name'])
error_messages.append(msg)
else:
for post in request.POST:
clone_data[post] = request.POST.get(post, '') clone_data[post] = request.POST.get(post, '')
conn.clone_instance(clone_data) new_uuid = conn.clone_instance(clone_data)
msg = _("Clone") new_instance = Instance(compute_id=compute_id, name=clone_data['name'], uuid=new_uuid)
addlogmsg(request.user.username, instance.name, msg) new_instance.save()
return HttpResponseRedirect(reverse('instance', args=[compute_id, clone_data['name']])) userinstance = UserInstance(instance_id=new_instance.id, user_id=request.user.id, is_delete=True)
userinstance.save()
msg = _("Clone of '%s'" % instance.name)
addlogmsg(request.user.username, new_instance.name, msg)
return HttpResponseRedirect(reverse('instance', args=[compute_id, clone_data['name']]))
conn.close() conn.close()
@ -497,15 +650,13 @@ def instance(request, compute_id, vname):
return render(request, 'instance.html', locals()) return render(request, 'instance.html', locals())
@login_required
def inst_status(request, compute_id, vname): def inst_status(request, compute_id, vname):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('login'))
compute = get_object_or_404(Compute, pk=compute_id) compute = get_object_or_404(Compute, pk=compute_id)
response = HttpResponse() response = HttpResponse()
response['Content-Type'] = "text/javascript" response['Content-Type'] = "text/javascript"
@ -524,15 +675,13 @@ def inst_status(request, compute_id, vname):
return response return response
@login_required
def inst_graph(request, compute_id, vname): def inst_graph(request, compute_id, vname):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('login'))
datasets = {} datasets = {}
json_blk = [] json_blk = []
datasets_blk = {} datasets_blk = {}
@ -632,3 +781,42 @@ def inst_graph(request, compute_id, vname):
response.write(data) response.write(data)
return response return response
@login_required
def guess_mac_address(request, vname):
dhcp_file = '/srv/webvirtcloud/dhcpd.conf'
data = { 'vname': vname, 'mac': '52:54:00:' }
if os.path.isfile(dhcp_file):
with open(dhcp_file, 'r') as f:
name_found = False
for line in f:
if "host %s." % vname in line:
name_found = True
if name_found and "hardware ethernet" in line:
data['mac'] = line.split(' ')[-1].strip().strip(';')
break
return HttpResponse(json.dumps(data));
@login_required
def guess_clone_name(request):
dhcp_file = '/srv/webvirtcloud/dhcpd.conf'
prefix = settings.CLONE_INSTANCE_DEFAULT_PREFIX
if os.path.isfile(dhcp_file):
instance_names = [i.name for i in Instance.objects.filter(name__startswith=prefix)]
with open(dhcp_file, 'r') as f:
for line in f:
line = line.strip()
if "host %s" % prefix in line:
fqdn = line.split(' ')[1]
hostname = fqdn.split('.')[0]
if hostname.startswith(prefix) and hostname not in instance_names:
return HttpResponse(json.dumps({'name': hostname}))
return HttpResponse(json.dumps({}));
@login_required
def check_instance(request, vname):
check_instance = Instance.objects.filter(name=vname)
data = { 'vname': vname, 'exists': False }
if check_instance:
data['exists'] = True
return HttpResponse(json.dumps(data));

View file

@ -1,21 +1,20 @@
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from computes.models import Compute from computes.models import Compute
from interfaces.forms import AddInterface from interfaces.forms import AddInterface
from vrtManager.interface import wvmInterface, wvmInterfaces from vrtManager.interface import wvmInterface, wvmInterfaces
from libvirt import libvirtError from libvirt import libvirtError
@login_required
def interfaces(request, compute_id): def interfaces(request, compute_id):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
@ -57,15 +56,13 @@ def interfaces(request, compute_id):
return render(request, 'interfaces.html', locals()) return render(request, 'interfaces.html', locals())
@login_required
def interface(request, compute_id, iface): def interface(request, compute_id, iface):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))

View file

@ -0,0 +1,12 @@
<center>
{% if page > 1 %}
<a href="{% url 'showlogspage' page|add:"-1" %}">&larr;</a>
{% else %}
&nbsp;
{% endif %}
{% if has_next_page %}
<a href="{% url 'showlogspage' page|add:"1" %}">&rarr;</a>
{% else %}
&nbsp;
{% endif %}
</center>

View file

@ -22,31 +22,33 @@
</div> </div>
</div> </div>
{% else %} {% else %}
{% include "paging.html" %}
<div class="table-responsive"> <div class="table-responsive">
<table class="table table-bordered table-hover"> <table class="table table-bordered table-hover">
<thead> <thead>
<tr> <tr>
<th>#</th> <th>#</th>
<th>{% trans "Date" %}</th>
<th>{% trans "User" %}</th> <th>{% trans "User" %}</th>
<th>{% trans "Instance" %}</th> <th>{% trans "Instance" %}</th>
<th>{% trans "Message" %}</th> <th>{% trans "Message" %}</th>
<th>{% trans "Date" %}</th>
</tr> </tr>
</thead> </thead>
<tbody> <tbody>
{% for log in logs %} {% for log in logs %}
<tr> <tr>
<td>{{ forloop.counter }}</td> <td>{{ log.id }}</td>
<td style="width:130px;">{{ log.date|date:"M d H:i:s" }}</td>
<td>{{ log.user }}</a></td> <td>{{ log.user }}</a></td>
<td>{{ log.instance }}</a></td> <td>{{ log.instance }}</a></td>
<td>{{ log.message }}</td> <td>{{ log.message }}</td>
<td style="width:130px;">{{ log.date|date:"M d H:i:s" }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
</table> </table>
</div> </div>
{% include "paging.html" %}
{% endif %} {% endif %}
</div> </div>
</div> </div>
{% endblock %} {% endblock %}

7
logs/urls.py Normal file
View file

@ -0,0 +1,7 @@
from django.conf.urls import url
from . import views
urlpatterns = [
url(r'^$', views.showlogs, name='showlogs'),
url(r'^(?P<page>[0-9]+)/$', views.showlogs, name='showlogspage'),
]

View file

@ -2,6 +2,7 @@ from django.shortcuts import render
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from logs.models import Logs from logs.models import Logs
from django.conf import settings
def addlogmsg(user, instance, message): def addlogmsg(user, instance, message):
@ -13,7 +14,7 @@ def addlogmsg(user, instance, message):
add_log_msg.save() add_log_msg.save()
def showlogs(request): def showlogs(request, page=1):
""" """
:param request: :param request:
:return: :return:
@ -25,6 +26,11 @@ def showlogs(request):
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
logs = Logs.objects.all() 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()) return render(request, 'showlogs.html', locals())

View file

@ -2,6 +2,7 @@ from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from computes.models import Compute from computes.models import Compute
from networks.forms import AddNetPool from networks.forms import AddNetPool
from vrtManager.network import wvmNetwork, wvmNetworks from vrtManager.network import wvmNetwork, wvmNetworks
@ -9,15 +10,13 @@ from vrtManager.network import network_size
from libvirt import libvirtError from libvirt import libvirtError
@login_required
def networks(request, compute_id): def networks(request, compute_id):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
@ -60,15 +59,13 @@ def networks(request, compute_id):
return render(request, 'networks.html', locals()) return render(request, 'networks.html', locals())
@login_required
def network(request, compute_id, pool): def network(request, compute_id, pool):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))

View file

@ -1,21 +1,20 @@
from django.shortcuts import render, get_object_or_404 from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from computes.models import Compute from computes.models import Compute
from secrets.forms import AddSecret from secrets.forms import AddSecret
from vrtManager.secrets import wvmSecrets from vrtManager.secrets import wvmSecrets
from libvirt import libvirtError from libvirt import libvirtError
@login_required
def secrets(request, compute_id): def secrets(request, compute_id):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))

View file

@ -12,7 +12,7 @@ body {
} }
.container { .container {
max-width: 768px; max-width: 900px;
} }
.page-header { .page-header {
@ -132,4 +132,4 @@ p {
.keyselect { .keyselect {
display: inline; display: inline;
min-width: 250px; min-width: 250px;
} }

151
static/js/js.cookie.js Normal file
View file

@ -0,0 +1,151 @@
/*!
* JavaScript Cookie v2.1.1
* https://github.com/js-cookie/js-cookie
*
* Copyright 2006, 2015 Klaus Hartl & Fagner Brack
* Released under the MIT license
*/
;(function (factory) {
if (typeof define === 'function' && define.amd) {
define(factory);
} else if (typeof exports === 'object') {
module.exports = factory();
} else {
var OldCookies = window.Cookies;
var api = window.Cookies = factory();
api.noConflict = function () {
window.Cookies = OldCookies;
return api;
};
}
}(function () {
function extend () {
var i = 0;
var result = {};
for (; i < arguments.length; i++) {
var attributes = arguments[ i ];
for (var key in attributes) {
result[key] = attributes[key];
}
}
return result;
}
function init (converter) {
function api (key, value, attributes) {
var result;
if (typeof document === 'undefined') {
return;
}
// Write
if (arguments.length > 1) {
attributes = extend({
path: '/'
}, api.defaults, attributes);
if (typeof attributes.expires === 'number') {
var expires = new Date();
expires.setMilliseconds(expires.getMilliseconds() + attributes.expires * 864e+5);
attributes.expires = expires;
}
try {
result = JSON.stringify(value);
if (/^[\{\[]/.test(result)) {
value = result;
}
} catch (e) {}
if (!converter.write) {
value = encodeURIComponent(String(value))
.replace(/%(23|24|26|2B|3A|3C|3E|3D|2F|3F|40|5B|5D|5E|60|7B|7D|7C)/g, decodeURIComponent);
} else {
value = converter.write(value, key);
}
key = encodeURIComponent(String(key));
key = key.replace(/%(23|24|26|2B|5E|60|7C)/g, decodeURIComponent);
key = key.replace(/[\(\)]/g, escape);
return (document.cookie = [
key, '=', value,
attributes.expires && '; expires=' + attributes.expires.toUTCString(), // use expires attribute, max-age is not supported by IE
attributes.path && '; path=' + attributes.path,
attributes.domain && '; domain=' + attributes.domain,
attributes.secure ? '; secure' : ''
].join(''));
}
// Read
if (!key) {
result = {};
}
// To prevent the for loop in the first place assign an empty array
// in case there are no cookies at all. Also prevents odd result when
// calling "get()"
var cookies = document.cookie ? document.cookie.split('; ') : [];
var rdecode = /(%[0-9A-Z]{2})+/g;
var i = 0;
for (; i < cookies.length; i++) {
var parts = cookies[i].split('=');
var name = parts[0].replace(rdecode, decodeURIComponent);
var cookie = parts.slice(1).join('=');
if (cookie.charAt(0) === '"') {
cookie = cookie.slice(1, -1);
}
try {
cookie = converter.read ?
converter.read(cookie, name) : converter(cookie, name) ||
cookie.replace(rdecode, decodeURIComponent);
if (this.json) {
try {
cookie = JSON.parse(cookie);
} catch (e) {}
}
if (key === name) {
result = cookie;
break;
}
if (!key) {
result[name] = cookie;
}
} catch (e) {}
}
return result;
}
api.set = api;
api.get = function (key) {
return api(key);
};
api.getJSON = function () {
return api.apply({
json: true
}, [].slice.call(arguments));
};
api.defaults = {};
api.remove = function (key, attributes) {
api(key, '', extend(attributes, {
expires: -1
}));
};
api.withConverter = init;
return api;
}
return init(function () {});
}));

View file

@ -2,21 +2,20 @@ from django.shortcuts import render, get_object_or_404
from django.http import HttpResponseRedirect from django.http import HttpResponseRedirect
from django.utils.translation import ugettext_lazy as _ from django.utils.translation import ugettext_lazy as _
from django.core.urlresolvers import reverse from django.core.urlresolvers import reverse
from django.contrib.auth.decorators import login_required
from computes.models import Compute from computes.models import Compute
from storages.forms import AddStgPool, AddImage, CloneImage from storages.forms import AddStgPool, AddImage, CloneImage
from vrtManager.storage import wvmStorage, wvmStorages from vrtManager.storage import wvmStorage, wvmStorages
from libvirt import libvirtError from libvirt import libvirtError
@login_required
def storages(request, compute_id): def storages(request, compute_id):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))
@ -68,15 +67,13 @@ def storages(request, compute_id):
return render(request, 'storages.html', locals()) return render(request, 'storages.html', locals())
@login_required
def storage(request, compute_id, pool): def storage(request, compute_id, pool):
""" """
:param request: :param request:
:return: :return:
""" """
if not request.user.is_authenticated():
return HttpResponseRedirect(reverse('index'))
if not request.user.is_superuser: if not request.user.is_superuser:
return HttpResponseRedirect(reverse('index')) return HttpResponseRedirect(reverse('index'))

View file

@ -48,6 +48,8 @@
<script src="{% static "js/jquery.js" %}"></script> <script src="{% static "js/jquery.js" %}"></script>
<!-- Bootstrap Core JavaScript --> <!-- Bootstrap Core JavaScript -->
<script src="{% static "js/bootstrap.min.js" %}"></script> <script src="{% static "js/bootstrap.min.js" %}"></script>
<!-- JavaScript Cookie -->
<script src="{% static "js/js.cookie.js" %}"></script>
{% block script %}{% endblock %} {% block script %}{% endblock %}
</body> </body>
</html> </html>

View file

@ -0,0 +1,25 @@
{% load i18n %}
<!-- Please wait dialog -->
<div class="modal fade" id="pleaseWaitDialog" tabindex="-1" role="dialog" aria-labelledby="pleaseWaitDialogLabel" data-backdrop="static" data-keyboard="false">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">{% trans "Processing" %}...</h4>
</div>
<div class="modal-body">
<div class="progress">
<div class="progress-bar progress-bar-striped active" role="progressbar" aria-valuenow="10" aria-valuemin="0" aria-valuemax="10" style="width:100%">
</div>
</div>
</div>
</div>
</div>
</div>
<script>
function showPleaseWaitDialog() {
$('#pleaseWaitDialog').modal();
}
function hidePleaseWaitDialog() {
$('#pleaseWaitDialog').modal('hide');
}
</script>

View file

@ -442,7 +442,16 @@ class wvmConnect(object):
vcpu = cur_vcpu vcpu = cur_vcpu
else: else:
vcpu = util.get_xml_path(dom.XMLDesc(0), "/domain/vcpu") vcpu = util.get_xml_path(dom.XMLDesc(0), "/domain/vcpu")
vname[dom.name()] = {'status': dom.info()[0], 'uuid': dom.UUIDString(), 'vcpu': vcpu, 'memory': mem} title = util.get_xml_path(dom.XMLDesc(0), "/domain/title")
description = util.get_xml_path(dom.XMLDesc(0), "/domain/description")
vname[dom.name()] = {
'status': dom.info()[0],
'uuid': dom.UUIDString(),
'vcpu': vcpu,
'memory': mem,
'title': title if title else '',
'description': description if description else '',
}
return vname return vname
def get_user_instances(self, name): def get_user_instances(self, name):
@ -454,7 +463,17 @@ class wvmConnect(object):
vcpu = cur_vcpu vcpu = cur_vcpu
else: else:
vcpu = util.get_xml_path(dom.XMLDesc(0), "/domain/vcpu") vcpu = util.get_xml_path(dom.XMLDesc(0), "/domain/vcpu")
return {'name': dom.name(), 'status': dom.info()[0], 'uuid': dom.UUIDString(), 'vcpu': vcpu, 'memory': mem} title = util.get_xml_path(dom.XMLDesc(0), "/domain/title")
description = util.get_xml_path(dom.XMLDesc(0), "/domain/description")
return {
'name': dom.name(),
'status': dom.info()[0],
'uuid': dom.UUIDString(),
'vcpu': vcpu,
'memory': mem,
'title': title if title else '',
'description': description if description else '',
}
def close(self): def close(self):
"""Close connection""" """Close connection"""

View file

@ -220,16 +220,15 @@ class wvmCreate(wvmConnect):
xml += """<interface type='network'>""" xml += """<interface type='network'>"""
if mac: if mac:
xml += """<mac address='%s'/>""" % mac xml += """<mac address='%s'/>""" % mac
xml += """<source network='%s'/>""" % net xml += """<source network='%s'/>
<filterref filter='clean-traffic'/>""" % net
if virtio: if virtio:
xml += """<model type='virtio'/>""" xml += """<model type='virtio'/>"""
xml += """</interface>""" xml += """</interface>"""
xml += """ <input type='mouse' bus='ps2'/> xml += """ <input type='mouse' bus='ps2'/>
<input type='tablet' bus='usb'/> <input type='tablet' bus='usb'/>
<graphics type='%s' port='-1' autoport='yes' listen='0.0.0.0' passwd='%s'> <graphics type='%s' port='-1' autoport='yes' passwd='%s' listen='127.0.0.1'/>
<listen type='address' address='0.0.0.0'/>
</graphics>
<console type='pty'/> <console type='pty'/>
<video> <video>
<model type='cirrus'/> <model type='cirrus'/>

View file

@ -8,6 +8,7 @@ from vrtManager import util
from xml.etree import ElementTree from xml.etree import ElementTree
from datetime import datetime from datetime import datetime
from vrtManager.connection import wvmConnect from vrtManager.connection import wvmConnect
from vrtManager.storage import wvmStorage
from webvirtcloud.settings import QEMU_CONSOLE_TYPES from webvirtcloud.settings import QEMU_CONSOLE_TYPES
@ -66,20 +67,18 @@ class wvmInstances(wvmConnect):
dom = self.get_instance(name) dom = self.get_instance(name)
dom.resume() dom.resume()
def moveto(self, conn, name, live, unsafe, undefine): def moveto(self, conn, name, live, unsafe, undefine, offline):
flags = 0 flags = 0
if live and conn.get_status() == 1: if live and conn.get_status() == 1:
flags |= VIR_MIGRATE_LIVE flags |= VIR_MIGRATE_LIVE
if unsafe and conn.get_status() == 1: if unsafe and conn.get_status() == 1:
flags |= VIR_MIGRATE_UNSAFE flags |= VIR_MIGRATE_UNSAFE
dom = conn.get_instance(name) dom = conn.get_instance(name)
dom.migrate(self.wvm, flags, name, None, 0) xml = dom.XMLDesc(VIR_DOMAIN_XML_SECURE)
if not offline:
dom.migrate(self.wvm, flags, None, None, 0)
if undefine: if undefine:
dom.undefine() dom.undefine()
def define_move(self, name):
dom = self.get_instance(name)
xml = dom.XMLDesc(VIR_DOMAIN_XML_SECURE)
self.wvm.defineXML(xml) self.wvm.defineXML(xml)
def graphics_type(self, name): def graphics_type(self, name):
@ -184,8 +183,13 @@ class wvmInstance(wvmConnect):
mem = util.get_xml_path(self._XMLDesc(0), "/domain/currentMemory") mem = util.get_xml_path(self._XMLDesc(0), "/domain/currentMemory")
return int(mem) / 1024 return int(mem) / 1024
def get_title(self):
title = util.get_xml_path(self._XMLDesc(0), "/domain/title")
return title if title else ''
def get_description(self): def get_description(self):
return util.get_xml_path(self._XMLDesc(0), "/domain/description") description = util.get_xml_path(self._XMLDesc(0), "/domain/description")
return description if description else ''
def get_max_memory(self): def get_max_memory(self):
return self.wvm.getInfo()[1] * 1048576 return self.wvm.getInfo()[1] * 1048576
@ -523,7 +527,7 @@ class wvmInstance(wvmConnect):
return util.get_xml_path(self._XMLDesc(VIR_DOMAIN_XML_SECURE), return util.get_xml_path(self._XMLDesc(VIR_DOMAIN_XML_SECURE),
"/domain/devices/graphics/@keymap") or '' "/domain/devices/graphics/@keymap") or ''
def resize(self, cur_memory, memory, cur_vcpu, vcpu): def resize(self, cur_memory, memory, cur_vcpu, vcpu, disks=[]):
""" """
Function change ram and cpu on vds. Function change ram and cpu on vds.
""" """
@ -541,6 +545,11 @@ class wvmInstance(wvmConnect):
set_vcpu.text = vcpu set_vcpu.text = vcpu
set_vcpu.set('current', cur_vcpu) set_vcpu.set('current', cur_vcpu)
for disk in disks:
source_dev = disk['path']
vol = self.get_volume_by_path(source_dev)
vol.resize(disk['size_new'])
new_xml = ElementTree.tostring(tree) new_xml = ElementTree.tostring(tree)
self._defineXML(new_xml) self._defineXML(new_xml)
@ -598,6 +607,22 @@ class wvmInstance(wvmConnect):
def get_managed_save_image(self): def get_managed_save_image(self):
return self.instance.hasManagedSaveImage(0) return self.instance.hasManagedSaveImage(0)
def get_wvmStorage(self, pool):
storage = wvmStorage(self.host,
self.login,
self.passwd,
self.conn,
pool)
return storage
def fix_mac(self, mac):
if ":" in mac:
return mac
# if mac does not contain ":", try to split into tuples and join with ":"
n = 2
mac_tuples = [mac[i:i+n] for i in range(0, len(mac), n)]
return ':'.join(mac_tuples)
def clone_instance(self, clone_data): def clone_instance(self, clone_data):
clone_dev_path = [] clone_dev_path = []
@ -610,7 +635,8 @@ class wvmInstance(wvmConnect):
for num, net in enumerate(tree.findall('devices/interface')): for num, net in enumerate(tree.findall('devices/interface')):
elm = net.find('mac') elm = net.find('mac')
elm.set('address', clone_data['net-' + str(num)]) mac_address = self.fix_mac(clone_data['clone-net-mac-' + str(num)])
elm.set('address', mac_address)
for disk in tree.findall('devices/disk'): for disk in tree.findall('devices/disk'):
if disk.get('device') == 'disk': if disk.get('device') == 'disk':
@ -649,5 +675,65 @@ class wvmInstance(wvmConnect):
</volume>""" % (target_file, vol_format) </volume>""" % (target_file, vol_format)
stg = vol.storagePoolLookupByVolume() stg = vol.storagePoolLookupByVolume()
stg.createXMLFrom(vol_clone_xml, vol, meta_prealloc) stg.createXMLFrom(vol_clone_xml, vol, meta_prealloc)
source_dev = elm.get('dev')
if source_dev:
clone_path = os.path.join(os.path.dirname(source_dev), target_file)
elm.set('dev', clone_path)
vol = self.get_volume_by_path(source_dev)
stg = vol.storagePoolLookupByVolume()
vol_name = util.get_xml_path(vol.XMLDesc(0), "/volume/name")
pool_name = util.get_xml_path(stg.XMLDesc(0), "/pool/name")
storage = self.get_wvmStorage(pool_name)
storage.clone_volume(vol_name, target_file)
options = {
'title': clone_data.get('clone-title', ''),
'description': clone_data.get('clone-description', ''),
}
self._set_options(tree, options)
self._defineXML(ElementTree.tostring(tree)) self._defineXML(ElementTree.tostring(tree))
return self.get_instance(clone_data['name']).UUIDString()
def change_network(self, network_data):
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
tree = ElementTree.fromstring(xml)
for num, interface in enumerate(tree.findall('devices/interface')):
if interface.get('type') == 'bridge':
source = interface.find('mac')
source.set('address', network_data['net-mac-' + str(num)])
source = interface.find('source')
source.set('bridge', network_data['net-source-' + str(num)])
new_xml = ElementTree.tostring(tree)
self._defineXML(new_xml)
def _set_options(self, tree, options):
for o in ['title', 'description']:
option = tree.find(o)
option_value = options.get(o, '').strip()
if not option_value:
if not option is None:
tree.remove(option)
else:
if option is None:
option = ElementTree.SubElement(tree, o)
option.text = option_value
def set_options(self, options):
"""
Function change description, title
"""
xml = self._XMLDesc(VIR_DOMAIN_XML_SECURE)
tree = ElementTree.fromstring(xml)
self._set_options(tree, options)
new_xml = ElementTree.tostring(tree)
self._defineXML(new_xml)

View file

@ -8,7 +8,7 @@ BASE_DIR = os.path.dirname(os.path.dirname(__file__))
SECRET_KEY = '4y(f4rfqc6f2!i8_vfuu)kav6tdv5#sc=n%o451dm+th0&3uci' SECRET_KEY = '4y(f4rfqc6f2!i8_vfuu)kav6tdv5#sc=n%o451dm+th0&3uci'
DEBUG = False DEBUG = True
TEMPLATE_DEBUG = DEBUG TEMPLATE_DEBUG = DEBUG
@ -38,11 +38,19 @@ MIDDLEWARE_CLASSES = (
'django.middleware.common.CommonMiddleware', 'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', 'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware', 'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.RemoteUserMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware', 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware', 'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware', 'django.middleware.clickjacking.XFrameOptionsMiddleware',
) )
AUTHENTICATION_BACKENDS = (
'django.contrib.auth.backends.RemoteUserBackend',
#'accounts.backends.MyRemoteUserBackend',
)
LOGIN_URL = '/accounts/login'
ROOT_URLCONF = 'webvirtcloud.urls' ROOT_URLCONF = 'webvirtcloud.urls'
WSGI_APPLICATION = 'webvirtcloud.wsgi.application' WSGI_APPLICATION = 'webvirtcloud.wsgi.application'
@ -103,3 +111,9 @@ QEMU_KEYMAPS = ['ar', 'da', 'de', 'de-ch', 'en-gb', 'en-us', 'es', 'et', 'fi',
# keepalive interval and count for libvirt connections # keepalive interval and count for libvirt connections
LIBVIRT_KEEPALIVE_INTERVAL = 5 LIBVIRT_KEEPALIVE_INTERVAL = 5
LIBVIRT_KEEPALIVE_COUNT = 5 LIBVIRT_KEEPALIVE_COUNT = 5
ALLOW_INSTANCE_MULTIPLE_OWNER = True
CLONE_INSTANCE_DEFAULT_PREFIX = 'ourea'
LOGS_PER_PAGE = 100
QUOTA_DEBUG = True
ALLOW_EMPTY_PASSWORD = True

View file

@ -8,6 +8,7 @@ urlpatterns = patterns('',
url(r'^instance/', include('instances.urls')), url(r'^instance/', include('instances.urls')),
url(r'^accounts/', include('accounts.urls')), url(r'^accounts/', include('accounts.urls')),
url(r'^computes/', include('computes.urls')), url(r'^computes/', include('computes.urls')),
url(r'^logs/', include('logs.urls')),
url(r'^compute/(?P<compute_id>[0-9]+)/storages/$', url(r'^compute/(?P<compute_id>[0-9]+)/storages/$',
'storages.views.storages', name='storages'), 'storages.views.storages', name='storages'),
@ -27,6 +28,5 @@ urlpatterns = patterns('',
'create.views.create_instance', name='create_instance'), 'create.views.create_instance', name='create_instance'),
url(r'^console/$', 'console.views.console', name='console'), url(r'^console/$', 'console.views.console', name='console'),
url(r'^logs/$', 'logs.views.showlogs', name='showlogs'),
# (r'^admin/', include(admin.site.urls)), # (r'^admin/', include(admin.site.urls)),
) )

View file

@ -7,7 +7,10 @@ For more information on this file, see
https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/ https://docs.djangoproject.com/en/1.7/howto/deployment/wsgi/
""" """
import os execfile('/srv/webvirtcloud/venv/bin/activate_this.py', dict(__file__='/srv/webvirtcloud/venv/bin/activate_this.py'))
import os, sys
sys.path.append('/srv/webvirtcloud')
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webvirtcloud.settings") os.environ.setdefault("DJANGO_SETTINGS_MODULE", "webvirtcloud.settings")
from django.core.wsgi import get_wsgi_application from django.core.wsgi import get_wsgi_application