mirror of
https://github.com/retspen/webvirtcloud
synced 2024-12-24 23:25:24 +00:00
Merge pull request #322 from Real-Gecko/master
Minor fixes and more tests
This commit is contained in:
commit
c791f582af
50 changed files with 808 additions and 712 deletions
|
@ -1,26 +1,30 @@
|
|||
import re
|
||||
from django import forms
|
||||
from django.forms import ModelForm, ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
|
||||
from appsettings.models import AppSettings
|
||||
|
||||
from .models import UserInstance
|
||||
|
||||
|
||||
class UserAddForm(forms.Form):
|
||||
name = forms.CharField(label="Name",
|
||||
error_messages={'required': _('No username has been entered')},
|
||||
max_length=20)
|
||||
password = forms.CharField(required=not settings.ALLOW_EMPTY_PASSWORD,
|
||||
error_messages={'required': _('No password has been entered')},)
|
||||
class UserInstanceForm(ModelForm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(UserInstanceForm, self).__init__(*args, **kwargs)
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name']
|
||||
have_symbol = re.match('^[a-z0-9]+$', name)
|
||||
if not have_symbol:
|
||||
raise forms.ValidationError(_('The flavor name must not contain any special characters'))
|
||||
elif len(name) > 20:
|
||||
raise forms.ValidationError(_('The flavor name must not exceed 20 characters'))
|
||||
try:
|
||||
User.objects.get(username=name)
|
||||
except User.DoesNotExist:
|
||||
return name
|
||||
raise forms.ValidationError(_('Flavor name is already use'))
|
||||
# Make user and instance fields not editable after creation
|
||||
instance = getattr(self, 'instance', None)
|
||||
if instance and instance.id is not None:
|
||||
self.fields['user'].disabled = True
|
||||
self.fields['instance'].disabled = True
|
||||
|
||||
def clean_instance(self):
|
||||
instance = self.cleaned_data['instance']
|
||||
if AppSettings.objects.get(key="ALLOW_INSTANCE_MULTIPLE_OWNER").value == 'False':
|
||||
exists = UserInstance.objects.filter(instance=instance)
|
||||
if exists:
|
||||
raise ValidationError(_('Instance owned by another user'))
|
||||
|
||||
return instance
|
||||
|
||||
class Meta:
|
||||
model = UserInstance
|
||||
fields = '__all__'
|
||||
|
|
44
accounts/migrations/0004_auto_20200615_0637.py
Normal file
44
accounts/migrations/0004_auto_20200615_0637.py
Normal file
|
@ -0,0 +1,44 @@
|
|||
# Generated by Django 2.2.13 on 2020-06-15 06:37
|
||||
|
||||
import django.core.validators
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('accounts', '0003_auto_20200604_0930'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='userattributes',
|
||||
name='max_cpus',
|
||||
field=models.IntegerField(default=2, help_text='-1 for unlimited. Any integer value', validators=[django.core.validators.MinValueValidator(-1)], verbose_name='max CPUs'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='userattributes',
|
||||
name='max_disk_size',
|
||||
field=models.IntegerField(default=20, help_text='-1 for unlimited. Any integer value', validators=[django.core.validators.MinValueValidator(-1)], verbose_name='max disk size'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='userattributes',
|
||||
name='max_instances',
|
||||
field=models.IntegerField(default=2, help_text='-1 for unlimited. Any integer value', validators=[django.core.validators.MinValueValidator(-1)], verbose_name='max instances'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='userattributes',
|
||||
name='max_memory',
|
||||
field=models.IntegerField(default=2048, help_text='-1 for unlimited. Any integer value', validators=[django.core.validators.MinValueValidator(-1)], verbose_name='max memory'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='usersshkey',
|
||||
name='keyname',
|
||||
field=models.CharField(max_length=25, verbose_name='key name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='usersshkey',
|
||||
name='keypublic',
|
||||
field=models.CharField(max_length=500, verbose_name='public key'),
|
||||
),
|
||||
]
|
20
accounts/migrations/0005_auto_20200616_1039.py
Normal file
20
accounts/migrations/0005_auto_20200616_1039.py
Normal file
|
@ -0,0 +1,20 @@
|
|||
# Generated by Django 2.2.13 on 2020-06-16 10:39
|
||||
|
||||
from django.conf import settings
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('instances', '0003_auto_20200615_0637'),
|
||||
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
||||
('accounts', '0004_auto_20200615_0637'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterUniqueTogether(
|
||||
name='userinstance',
|
||||
unique_together={('user', 'instance')},
|
||||
),
|
||||
]
|
|
@ -1,3 +1,4 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.db import models
|
||||
|
@ -13,8 +14,11 @@ class UserInstance(models.Model):
|
|||
is_delete = models.BooleanField(default=False)
|
||||
is_vnc = models.BooleanField(default=False)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.instance.name
|
||||
def __str__(self):
|
||||
return _('Instance "%(inst)s" of user %(user)s') % {'inst': self.instance, 'user': self.user}
|
||||
|
||||
class Meta:
|
||||
unique_together = ['user', 'instance']
|
||||
|
||||
|
||||
class UserSSHKey(models.Model):
|
||||
|
@ -22,7 +26,7 @@ class UserSSHKey(models.Model):
|
|||
keyname = models.CharField(_('key name'), max_length=25)
|
||||
keypublic = models.CharField(_('public key'), max_length=500)
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
return self.keyname
|
||||
|
||||
|
||||
|
@ -54,29 +58,7 @@ class UserAttributes(models.Model):
|
|||
validators=[MinValueValidator(-1)],
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def create_missing_userattributes(user):
|
||||
try:
|
||||
userattributes = user.userattributes
|
||||
except UserAttributes.DoesNotExist:
|
||||
userattributes = UserAttributes(user=user)
|
||||
userattributes.save()
|
||||
|
||||
@staticmethod
|
||||
def add_default_instances(user):
|
||||
existing_instances = UserInstance.objects.filter(user=user)
|
||||
if not existing_instances:
|
||||
for instance_name in settings.NEW_USER_DEFAULT_INSTANCES:
|
||||
instance = Instance.objects.get(name=instance_name)
|
||||
user_instance = UserInstance(user=user, instance=instance)
|
||||
user_instance.save()
|
||||
|
||||
@staticmethod
|
||||
def configure_user(user):
|
||||
UserAttributes.create_missing_userattributes(user)
|
||||
UserAttributes.add_default_instances(user)
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
return self.user.username
|
||||
|
||||
|
||||
|
|
|
@ -1,23 +1,68 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "User" %} - {{ user }}{% endblock %}
|
||||
{% load icons %}
|
||||
{% block title %}{% trans "User Profile" %} - {{ user }}{% endblock %}
|
||||
{% block content %}
|
||||
<!-- Page Heading -->
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
{% include 'create_user_inst_block.html' %}
|
||||
<h2 class="page-header">{{ user }}</h2>
|
||||
<a href="{% url 'user_instance_create' user.id %}" class="btn btn-success btn-header float-right">
|
||||
{% icon 'plus' %}
|
||||
</a>
|
||||
<h2 class="page-header">{% trans "User Profile" %}: {{ user }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
|
||||
{% include 'errors_block.html' %}
|
||||
|
||||
{% if request.user.is_superuser and publickeys %}
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
<ul class="nav nav-tabs">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" data-toggle="tab" href="#instances">{% trans "Instances" %}</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" data-toggle="tab" href="#public-keys">{% trans "Public Keys" %}</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="instances">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">{% trans "Instance" %}</th>
|
||||
<th scope="col">{% trans "VNC" %}</th>
|
||||
<th scope="col">{% trans "Resize" %}</th>
|
||||
<th scope="col">{% trans "Delete" %}</th>
|
||||
<th scope="colgroup" colspan="2">{% trans "Action" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for inst in user_insts %}
|
||||
<tr>
|
||||
<td>{{ forloop.counter }}</td>
|
||||
<td><a href="{% url 'instances:instance' inst.instance.compute.id inst.instance.name %}">{{ inst.instance.name }}</a></td>
|
||||
<td>{{ inst.is_vnc }}</td>
|
||||
<td>{{ inst.is_change }}</td>
|
||||
<td>{{ inst.is_delete }}</td>
|
||||
<td style="width:5px;">
|
||||
<a href="{% url 'user_instance_update' inst.id %}" class="btn btn-sm btn-secondary" title="{% trans "edit" %}">
|
||||
{% icon 'pencil' %}
|
||||
</a>
|
||||
</td>
|
||||
<td style="width:5px;">
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'user_instance_delete' inst.id %}" title="{% trans "Delete" %}">
|
||||
{% icon 'trash' %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="tab-pane fade" id="public-keys">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans "Key name" %}</th>
|
||||
|
@ -35,106 +80,4 @@
|
|||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
{% if not user_insts %}
|
||||
<div class="col-lg-12">
|
||||
<div class="alert alert-warning alert-dismissable">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
<i class="fa fa-exclamation-triangle"></i> <strong>{% trans "Warning" %}:</strong> {% trans "User doesn't have any Instance" %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="table-responsive">
|
||||
<table class="table table-bordered table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">{% trans "Instance" %}</th>
|
||||
<th scope="col">{% trans "VNC" %}</th>
|
||||
<th scope="col">{% trans "Resize" %}</th>
|
||||
<th scope="col">{% trans "Delete" %}</th>
|
||||
<th scope="colgroup" colspan="2">{% trans "Action" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for inst in user_insts %}
|
||||
<tr>
|
||||
<td>{{ forloop.counter }}</td>
|
||||
<td><a href="{% url 'instance' inst.instance.compute.id inst.instance.name %}">{{ inst.instance.name }}</a></td>
|
||||
<td>{{ inst.is_vnc }}</td>
|
||||
<td>{{ inst.is_change }}</td>
|
||||
<td>{{ inst.is_delete }}</td>
|
||||
<td style="width:5px;">
|
||||
<a href="#editPriv{{ forloop.counter }}" type="button" class="btn btn-sm btn-secondary" data-toggle="modal">
|
||||
<span class="fa fa-pencil" aria-hidden="true"></span>
|
||||
</a>
|
||||
|
||||
<!-- Modal pool -->
|
||||
<div class="modal fade" id="editPriv{{ forloop.counter }}" tabindex="-1" role="dialog" aria-labelledby="editPrivLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{% trans "Edit privilegies for" %} {{ inst.instance.name }}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" action="" role="form" aria-label="Edit privileges form">{% csrf_token %}
|
||||
<input type="hidden" name="user_inst" value="{{ inst.id }}">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "VNC" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<select class="form-control" name="inst_vnc">
|
||||
<option value="">{% trans 'False' %}</option>
|
||||
<option value="1" {% if inst.is_vnc %}selected{% endif %}>True</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Resize" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<select class="form-control" name="inst_change">
|
||||
<option value="">{% trans 'False' %}</option>
|
||||
<option value="1" {% if inst.is_change %}selected{% endif %}>True</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Delete" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<select class="form-control" name="inst_delete">
|
||||
<option value="">{% trans 'False' %}</option>
|
||||
<option value="1" {% if inst.is_delete %}selected{% endif %}>True</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans "Close" %}</button>
|
||||
<button type="submit" class="btn btn-primary" name="permission">{% trans "Edit" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div> <!-- /.modal-content -->
|
||||
</div> <!-- /.modal-dialog -->
|
||||
</div> <!-- /.modal -->
|
||||
</td>
|
||||
<td style="width:5px;">
|
||||
<form action="" method="post" role="form" aria-label="Delete user form">{% csrf_token %}
|
||||
<input type="hidden" name="user_inst" value="{{ inst.id }}">
|
||||
<button type="submit" class="btn btn-sm btn-secondary" name="delete" title="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
|
||||
<span class="fa fa-trash" aria-hidden="true"></span>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock content %}
|
||||
|
|
29
accounts/templates/accounts/change_password_form.html
Normal file
29
accounts/templates/accounts/change_password_form.html
Normal file
|
@ -0,0 +1,29 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% load bootstrap4 %}
|
||||
{% load i18n %}
|
||||
{% load icons %}
|
||||
|
||||
{% block title %}{%trans "Change Password" %}{% endblock title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h4 class="card-title">{%trans "Change Password" %}: {{ user }}</h4>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" id="password-change">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form form layout='horizontal' %}
|
||||
</form>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="float-right">
|
||||
<a class="btn btn-primary" href="javascript:history.back()">{% icon 'times' %} {% trans "Cancel" %}</a>
|
||||
<button type="submit" form="password-change" class="btn btn-success">
|
||||
{% icon 'check' %} {% trans "Change" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock content %}
|
|
@ -1,36 +0,0 @@
|
|||
{% load i18n %}
|
||||
{% if request.user.is_superuser %}
|
||||
<a href="#addUserInst" type="button" class="btn btn-success btn-header float-right" data-toggle="modal">
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
</a>
|
||||
|
||||
<!-- Modal pool -->
|
||||
<div class="modal fade" id="addUserInst" tabindex="-1" role="dialog" aria-labelledby="addUserInstLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{% trans "Add Instance for User" %}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" action="" role="form" aria-label="Add user instance form">{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Host" %} / {% trans "Instance" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<select class="custom-select" name="inst_id">
|
||||
{% for inst in instances %}
|
||||
<option value="{{ inst.id }}">{{ inst.compute.name }} / {{ inst.name }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans "Close" %}</button>
|
||||
<button type="submit" class="btn btn-primary" name="add">{% trans "Add" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div> <!-- /.modal-content -->
|
||||
</div> <!-- /.modal-dialog -->
|
||||
</div> <!-- /.modal -->
|
||||
{% endif %}
|
|
@ -1,5 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load icons %}
|
||||
{% load tags_fingerprint %}
|
||||
{% block title %}{% trans "Profile" %}{% endblock %}
|
||||
{% block content %}
|
||||
|
@ -16,6 +17,9 @@
|
|||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<h3 class="page-header">{% trans "Edit Profile" %}</h3>
|
||||
{% if perms.accounts.change_password %}
|
||||
<a href="{% url 'change_password' %}" class="btn btn-primary">{% icon 'lock' %} {% trans "Change Password" %}</a>
|
||||
{% endif %}
|
||||
<form method="post" action="" role="form" aria-label="Edit user info form">{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 col-form-label">{% trans "Login" %}</label>
|
||||
|
@ -41,34 +45,6 @@
|
|||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% if perms.accounts.change_password %}
|
||||
<h3 class="page-header">{% trans "Edit Password" %}</h3>
|
||||
<form method="post" action="" role="form" aria-label="Edit user password form">{% csrf_token %}
|
||||
<div class="form-group">
|
||||
<label class="col-sm-2 col-form-label">{% trans "Old" %}</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="password" class="form-control" name="oldpasswd" value="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group bridge_name_form_group_dhcp">
|
||||
<label class="col-sm-2 col-form-label">{% trans "New" %}</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="password" class="form-control" name="passwd1" value="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group bridge_name_form_group_dhcp">
|
||||
<label class="col-sm-2 col-form-label">{% trans "Retry" %}</label>
|
||||
<div class="col-sm-4">
|
||||
<input type="password" class="form-control" name="passwd2" value="">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<div class="col-sm-offset-2 col-sm-10">
|
||||
<button type="submit" class="btn btn-primary">{% trans "Change" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
<h3 class="page-header">{% trans "SSH Keys" %}</h3>
|
||||
{% if publickeys %}
|
||||
<div class="col-lg-12">
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from django.contrib.auth.models import User
|
||||
from django.contrib.auth.models import Permission, User
|
||||
from django.shortcuts import reverse
|
||||
from django.test import Client, TestCase
|
||||
|
||||
|
@ -6,7 +6,9 @@ from django.test import Client, TestCase
|
|||
class AccountsTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.client.login(username='admin', password='admin')
|
||||
User.objects.create_user(username='test', password='test')
|
||||
user = User.objects.create_user(username='test', password='test')
|
||||
permission = Permission.objects.get(codename='change_password')
|
||||
user.user_permissions.add(permission)
|
||||
|
||||
def test_profile(self):
|
||||
response = self.client.get(reverse('profile'))
|
||||
|
@ -26,3 +28,37 @@ class AccountsTestCase(TestCase):
|
|||
|
||||
response = client.get(reverse('logout'))
|
||||
self.assertRedirects(response, reverse('login'))
|
||||
|
||||
def test_password_change(self):
|
||||
client = Client()
|
||||
|
||||
logged_in = client.login(username='test', password='test')
|
||||
self.assertTrue(logged_in)
|
||||
|
||||
response = client.get(reverse('change_password'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = client.post(
|
||||
reverse('change_password'),
|
||||
{
|
||||
'old_password': 'wrongpass',
|
||||
'new_password1': 'newpw',
|
||||
'new_password2': 'newpw',
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = client.post(
|
||||
reverse('change_password'),
|
||||
{
|
||||
'old_password': 'test',
|
||||
'new_password1': 'newpw',
|
||||
'new_password2': 'newpw',
|
||||
},
|
||||
)
|
||||
self.assertRedirects(response, reverse('profile'))
|
||||
|
||||
client.logout()
|
||||
|
||||
logged_in = client.login(username='test', password='newpw')
|
||||
self.assertTrue(logged_in)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
from django.urls import path
|
||||
from django.contrib.auth import views as auth_views
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
|
@ -7,4 +8,8 @@ urlpatterns = [
|
|||
path('logout/', auth_views.LogoutView.as_view(template_name='logout.html'), name='logout'),
|
||||
path('profile/', views.profile, name='profile'),
|
||||
path('profile/<int:user_id>/', views.account, name='account'),
|
||||
path('change_password/', views.change_password, name='change_password'),
|
||||
path('user_instance/create/<int:user_id>/', views.user_instance_create, name='user_instance_create'),
|
||||
path('user_instance/<int:pk>/update/', views.user_instance_update, name='user_instance_update'),
|
||||
path('user_instance/<int:pk>/delete/', views.user_instance_delete, name='user_instance_delete'),
|
||||
]
|
||||
|
|
|
@ -1,25 +1,26 @@
|
|||
import os
|
||||
|
||||
import sass
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth import update_session_auth_hash
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.contrib.auth.forms import PasswordChangeForm
|
||||
from django.core.validators import ValidationError
|
||||
from django.http import HttpResponseRedirect
|
||||
from django.shortcuts import render
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from accounts.forms import UserAddForm
|
||||
|
||||
from accounts.models import *
|
||||
from admin.decorators import superuser_only
|
||||
from appsettings.models import AppSettings
|
||||
from instances.models import Instance
|
||||
|
||||
import sass
|
||||
import os
|
||||
from . import forms
|
||||
|
||||
|
||||
def profile(request):
|
||||
"""
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
|
||||
error_messages = []
|
||||
# user = User.objects.get(id=request.user.id)
|
||||
publickeys = UserSSHKey.objects.filter(user_id=request.user.id)
|
||||
|
||||
if request.method == 'POST':
|
||||
|
@ -30,20 +31,6 @@ def profile(request):
|
|||
user.email = email
|
||||
request.user.save()
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
if 'oldpasswd' in request.POST:
|
||||
oldpasswd = request.POST.get('oldpasswd', '')
|
||||
password1 = request.POST.get('passwd1', '')
|
||||
password2 = request.POST.get('passwd2', '')
|
||||
if not password1 or not password2:
|
||||
error_messages.append("Passwords didn't enter")
|
||||
if password1 and password2 and password1 != password2:
|
||||
error_messages.append("Passwords don't match")
|
||||
if not request.user.check_password(oldpasswd):
|
||||
error_messages.append("Old password is wrong!")
|
||||
if not error_messages:
|
||||
request.user.set_password(password1)
|
||||
request.user.save()
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
if 'keyname' in request.POST:
|
||||
keyname = request.POST.get('keyname', '')
|
||||
keypublic = request.POST.get('keypublic', '')
|
||||
|
@ -71,49 +58,78 @@ def profile(request):
|
|||
|
||||
@superuser_only
|
||||
def account(request, user_id):
|
||||
"""
|
||||
:param request:
|
||||
:param user_id:
|
||||
:return:
|
||||
"""
|
||||
|
||||
error_messages = []
|
||||
user = User.objects.get(id=user_id)
|
||||
user_insts = UserInstance.objects.filter(user_id=user_id)
|
||||
instances = Instance.objects.all().order_by('name')
|
||||
publickeys = UserSSHKey.objects.filter(user_id=user_id)
|
||||
|
||||
if request.method == 'POST':
|
||||
if 'delete' in request.POST:
|
||||
user_inst = request.POST.get('user_inst', '')
|
||||
del_user_inst = UserInstance.objects.get(id=user_inst)
|
||||
del_user_inst.delete()
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
if 'permission' in request.POST:
|
||||
user_inst = request.POST.get('user_inst', '')
|
||||
inst_vnc = request.POST.get('inst_vnc', '')
|
||||
inst_change = request.POST.get('inst_change', '')
|
||||
inst_delete = request.POST.get('inst_delete', '')
|
||||
edit_user_inst = UserInstance.objects.get(id=user_inst)
|
||||
edit_user_inst.is_change = bool(inst_change)
|
||||
edit_user_inst.is_delete = bool(inst_delete)
|
||||
edit_user_inst.is_vnc = bool(inst_vnc)
|
||||
edit_user_inst.save()
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
if 'add' in request.POST:
|
||||
inst_id = request.POST.get('inst_id', '')
|
||||
|
||||
if AppSettings.objects.get(key="ALLOW_INSTANCE_MULTIPLE_OWNER").value == 'True':
|
||||
check_inst = UserInstance.objects.filter(instance_id=int(inst_id), user_id=int(user_id))
|
||||
else:
|
||||
check_inst = UserInstance.objects.filter(instance_id=int(inst_id))
|
||||
|
||||
if check_inst:
|
||||
msg = _("Instance already added")
|
||||
error_messages.append(msg)
|
||||
else:
|
||||
add_user_inst = UserInstance(instance_id=int(inst_id), user_id=int(user_id))
|
||||
add_user_inst.save()
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
|
||||
return render(request, 'account.html', locals())
|
||||
|
||||
|
||||
@permission_required('accounts.change_password', raise_exception=True)
|
||||
def change_password(request):
|
||||
if request.method == 'POST':
|
||||
form = PasswordChangeForm(request.user, request.POST)
|
||||
if form.is_valid():
|
||||
user = form.save()
|
||||
update_session_auth_hash(request, user) # Important!
|
||||
messages.success(request, _('Password Changed'))
|
||||
return redirect('profile')
|
||||
else:
|
||||
messages.error(request, _('Wrong Data Provided'))
|
||||
else:
|
||||
form = PasswordChangeForm(request.user)
|
||||
return render(request, 'accounts/change_password_form.html', {'form': form})
|
||||
|
||||
|
||||
@superuser_only
|
||||
def user_instance_create(request, user_id):
|
||||
user = get_object_or_404(User, pk=user_id)
|
||||
|
||||
form = forms.UserInstanceForm(request.POST or None, initial={'user': user})
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect(reverse('account', args=[user.id]))
|
||||
|
||||
return render(
|
||||
request,
|
||||
'common/form.html',
|
||||
{
|
||||
'form': form,
|
||||
'title': _('Create User Instance'),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@superuser_only
|
||||
def user_instance_update(request, pk):
|
||||
user_instance = get_object_or_404(UserInstance, pk=pk)
|
||||
form = forms.UserInstanceForm(request.POST or None, instance=user_instance)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect(reverse('account', args=[user_instance.user.id]))
|
||||
|
||||
return render(
|
||||
request,
|
||||
'common/form.html',
|
||||
{
|
||||
'form': form,
|
||||
'title': _('Update User Instance'),
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@superuser_only
|
||||
def user_instance_delete(request, pk):
|
||||
user_instance = get_object_or_404(UserInstance, pk=pk)
|
||||
if request.method == 'POST':
|
||||
user = user_instance.user
|
||||
user_instance.delete()
|
||||
return redirect(reverse('account', args=[user.id]))
|
||||
|
||||
return render(
|
||||
request,
|
||||
'common/confirm_delete.html',
|
||||
{'object': user_instance},
|
||||
)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
{% extends "base.html" %}
|
||||
{% load font_awesome %}
|
||||
{% load icons %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load font_awesome %}
|
||||
{% load icons %}
|
||||
{% block title %}{% trans "Users" %}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap4 %}
|
||||
{% block title %}{% trans "Logs" %}{% endblock %}
|
||||
{% block content %}
|
||||
<!-- Page Heading -->
|
||||
|
@ -47,7 +48,7 @@
|
|||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% include "paging.html" %}
|
||||
{% bootstrap_pagination logs %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% load font_awesome %}
|
||||
{% load icons %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "User" %}{% endblock %}
|
||||
|
|
|
@ -2,7 +2,7 @@
|
|||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load common_tags %}
|
||||
{% load font_awesome %}
|
||||
{% load icons %}
|
||||
{% block title %}{% trans "Users" %}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
|
|
|
@ -1,9 +1,10 @@
|
|||
from django.conf import settings
|
||||
from django.contrib.auth.models import Group, User
|
||||
from django.core.paginator import Paginator
|
||||
from django.shortcuts import get_object_or_404, redirect, render
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from accounts.models import UserAttributes
|
||||
from accounts.models import UserAttributes, UserInstance, Instance
|
||||
from appsettings.models import AppSettings
|
||||
from logs.models import Logs
|
||||
|
||||
|
@ -32,7 +33,7 @@ def group_create(request):
|
|||
|
||||
return render(
|
||||
request,
|
||||
'admin/common/form.html',
|
||||
'common/form.html',
|
||||
{
|
||||
'form': form,
|
||||
'title': _('Create Group'),
|
||||
|
@ -50,7 +51,7 @@ def group_update(request, pk):
|
|||
|
||||
return render(
|
||||
request,
|
||||
'admin/common/form.html',
|
||||
'common/form.html',
|
||||
{
|
||||
'form': form,
|
||||
'title': _('Update Group'),
|
||||
|
@ -67,7 +68,7 @@ def group_delete(request, pk):
|
|||
|
||||
return render(
|
||||
request,
|
||||
'admin/common/confirm_delete.html',
|
||||
'common/confirm_delete.html',
|
||||
{'object': group},
|
||||
)
|
||||
|
||||
|
@ -97,6 +98,7 @@ def user_create(request):
|
|||
attributes = attributes_form.save(commit=False)
|
||||
attributes.user = user
|
||||
attributes.save()
|
||||
add_default_instances(user)
|
||||
return redirect('admin:user_list')
|
||||
|
||||
return render(
|
||||
|
@ -141,7 +143,7 @@ def user_delete(request, pk):
|
|||
|
||||
return render(
|
||||
request,
|
||||
'admin/common/confirm_delete.html',
|
||||
'common/confirm_delete.html',
|
||||
{'object': user},
|
||||
)
|
||||
|
||||
|
@ -169,3 +171,15 @@ def logs(request):
|
|||
page = request.GET.get('page', 1)
|
||||
logs = paginator.page(page)
|
||||
return render(request, 'admin/logs.html', {'logs': logs})
|
||||
|
||||
|
||||
def add_default_instances(user):
|
||||
"""
|
||||
Adds instances listed in NEW_USER_DEFAULT_INSTANCES to user
|
||||
"""
|
||||
existing_instances = UserInstance.objects.filter(user=user)
|
||||
if not existing_instances:
|
||||
for instance_name in settings.NEW_USER_DEFAULT_INSTANCES:
|
||||
instance = Instance.objects.get(name=instance_name)
|
||||
user_instance = UserInstance(user=user, instance=instance)
|
||||
user_instance.save()
|
||||
|
|
38
appsettings/migrations/0003_auto_20200615_0637.py
Normal file
38
appsettings/migrations/0003_auto_20200615_0637.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
# Generated by Django 2.2.13 on 2020-06-15 06:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('appsettings', '0002_auto_20200527_1603'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='appsettings',
|
||||
name='choices',
|
||||
field=models.CharField(max_length=50, verbose_name='choices'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='appsettings',
|
||||
name='description',
|
||||
field=models.CharField(max_length=100, null=True, verbose_name='description'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='appsettings',
|
||||
name='key',
|
||||
field=models.CharField(db_index=True, max_length=50, unique=True, verbose_name='key'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='appsettings',
|
||||
name='name',
|
||||
field=models.CharField(max_length=25, verbose_name='name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='appsettings',
|
||||
name='value',
|
||||
field=models.CharField(max_length=25, verbose_name='value'),
|
||||
),
|
||||
]
|
|
@ -40,31 +40,3 @@ class SocketComputeForm(forms.ModelForm):
|
|||
class Meta:
|
||||
model = Compute
|
||||
fields = ['name', 'details', 'hostname', 'type']
|
||||
|
||||
|
||||
class ComputeEditHostForm(forms.Form):
|
||||
host_id = forms.CharField()
|
||||
name = forms.CharField(error_messages={'required': _('No hostname has been entered')}, max_length=64)
|
||||
hostname = forms.CharField(error_messages={'required': _('No IP / Domain name has been entered')}, max_length=100)
|
||||
login = forms.CharField(error_messages={'required': _('No login has been entered')}, max_length=100)
|
||||
password = forms.CharField(max_length=100)
|
||||
details = forms.CharField(max_length=50, required=False)
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name']
|
||||
have_symbol = re.match('[^a-zA-Z0-9._-]+', name)
|
||||
if have_symbol:
|
||||
raise forms.ValidationError(_('The name of the host must not contain any special characters'))
|
||||
elif len(name) > 20:
|
||||
raise forms.ValidationError(_('The name of the host must not exceed 20 characters'))
|
||||
return name
|
||||
|
||||
def clean_hostname(self):
|
||||
hostname = self.cleaned_data['hostname']
|
||||
have_symbol = re.match('[^a-zA-Z0-9._-]+', hostname)
|
||||
wrong_ip = re.match('^0.|^255.', hostname)
|
||||
if have_symbol:
|
||||
raise forms.ValidationError(_('Hostname must contain only numbers, or the domain name separated by "."'))
|
||||
elif wrong_ip:
|
||||
raise forms.ValidationError(_('Wrong IP address'))
|
||||
return hostname
|
||||
|
|
38
computes/migrations/0003_auto_20200615_0637.py
Normal file
38
computes/migrations/0003_auto_20200615_0637.py
Normal file
|
@ -0,0 +1,38 @@
|
|||
# Generated by Django 2.2.13 on 2020-06-15 06:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('computes', '0002_auto_20200529_1320'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='compute',
|
||||
name='details',
|
||||
field=models.CharField(blank=True, max_length=64, null=True, verbose_name='details'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='compute',
|
||||
name='hostname',
|
||||
field=models.CharField(max_length=64, verbose_name='hostname'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='compute',
|
||||
name='login',
|
||||
field=models.CharField(max_length=20, verbose_name='login'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='compute',
|
||||
name='name',
|
||||
field=models.CharField(max_length=64, unique=True, verbose_name='name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='compute',
|
||||
name='password',
|
||||
field=models.CharField(blank=True, max_length=14, null=True, verbose_name='password'),
|
||||
),
|
||||
]
|
|
@ -1,6 +1,10 @@
|
|||
from django.db.models import Model, CharField, IntegerField
|
||||
from django.db.models import CharField, IntegerField, Model
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from vrtManager.connection import connection_manager
|
||||
|
||||
|
||||
class Compute(Model):
|
||||
name = CharField(_('name'), max_length=64, unique=True)
|
||||
hostname = CharField(_('hostname'), max_length=64)
|
||||
|
@ -9,5 +13,9 @@ class Compute(Model):
|
|||
details = CharField(_('details'), max_length=64, null=True, blank=True)
|
||||
type = IntegerField()
|
||||
|
||||
def __unicode__(self):
|
||||
return self.hostname
|
||||
@cached_property
|
||||
def status(self):
|
||||
return connection_manager.host_is_up(self.type, self.hostname)
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
|
|
@ -1,246 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% block title %}{% trans "Computes" %}{% endblock %}
|
||||
{% block content %}
|
||||
<!-- Page Heading -->
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
{% include 'create_comp_block.html' %}
|
||||
<h2 class="page-header">{% trans "Computes" %}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
|
||||
{% include 'errors_block.html' %}
|
||||
|
||||
<div class="row">
|
||||
{% if computes_info %}
|
||||
{% for compute in computes_info %}
|
||||
<div id="{{ compute.name }}" class="mb-3 col-12 col-sm-4">
|
||||
{% if compute.status is True %}
|
||||
<div class="card border-success shadow h-100">
|
||||
<div class="card-header bg-success">
|
||||
{% else %}
|
||||
<div class="card border-danger shadow h-100">
|
||||
<div class="card-header bg-danger">
|
||||
{% endif %}
|
||||
<h5 class="my-0 card-title">
|
||||
{% if compute.status is True %}
|
||||
<a class="card-link text-light" href="{% url 'overview' compute.id %}"><strong>{{ compute.name }}</strong></a>
|
||||
{% else %}
|
||||
<span class="card-link text-light" href="#"><strong>{{ compute.name }}</strong></span>
|
||||
{% endif %}
|
||||
<a class="card-link text-light float-right" data-toggle="modal" href="#editHost{{ compute.id }}" title="{% trans "Edit" %}">
|
||||
<i class="fa fa-cog"></i>
|
||||
</a>
|
||||
</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<dl class="row">
|
||||
<dt class="col-5">{% trans "Status" %}</dt>
|
||||
{% if compute.status %}
|
||||
<dd class="col-7">{% trans "Connected" %}</dd>
|
||||
{% else %}
|
||||
<dd class="col-7">{% trans "Not Connected" %}</dd>
|
||||
{% endif %}
|
||||
<dt class="col-5">{% trans "Details" %}</dt>
|
||||
{% if compute.details %}
|
||||
<dd class="col-7">{% trans compute.details %}</dd>
|
||||
{% else %}
|
||||
<dd class="col-7">{% trans "No details available" %}</dd>
|
||||
{% endif %}
|
||||
</dl>
|
||||
|
||||
<!-- Modal Edit -->
|
||||
<div class="modal fade" id="editHost{{ compute.id }}" tabindex="-1" role="dialog" aria-labelledby="editHostLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{% trans "Edit connection" %}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
{% if compute.type == 1 %}
|
||||
<form method="post" role="form" aria-label="Edit tcp host form">{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Name" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="hidden" name="host_id" value="{{ compute.id }}">
|
||||
<input type="text" name="name" class="form-control" value="{{ compute.name }}" maxlength="20" required pattern="[a-zA-Z0-9\.\-_]+">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "FQDN / IP" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="hostname" class="form-control" value="{{ compute.hostname }}" required pattern="[a-z0-9\.\-_]+">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Username" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="login" class="form-control" value="{{ compute.login }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Password" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="password" name="password" class="form-control" value="{{ compute.password }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Details" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="details" class="form-control" placeholder="{% trans "Details" %}" value="{{ compute.details }}">
|
||||
</div>
|
||||
</div></div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-danger mr-auto" name="host_del">
|
||||
{% trans "Delete" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
{% trans "Close" %}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" name="host_edit" autofocus>
|
||||
{% trans "Change" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if compute.type == 2 %}
|
||||
<form method="post" role="form" aria-label="Edit ssh host form">{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<p class="modal-body">{% trans "Need create ssh <a href='https://github.com/retspen/webvirtmgr/wiki/Setup-SSH-Authorization'>authorization key</a>. If you have another SSH port on your server, you can add IP:PORT like '192.168.1.1:2222'." %}</p>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Name" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="hidden" name="host_id" value="{{ compute.id }}">
|
||||
<input type="text" name="name" class="form-control" value="{{ compute.name }}" maxlength="20" required pattern="[a-z0-9\.\-_]+">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "FQDN / IP" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="hostname" class="form-control" value="{{ compute.hostname }}" required pattern="[a-z0-9\:\.\-_]+">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Username" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="login" class="form-control" value="{{ compute.login }}">
|
||||
<input type="hidden" name="password" value="{{ compute.password }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Details" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="details" class="form-control" placeholder="{% trans "Details" %}" value="{{ compute.details }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-danger mr-auto" name="host_del">
|
||||
{% trans "Delete" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
{% trans "Close" %}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" name="host_edit">
|
||||
{% trans "Change" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if compute.type == 3 %}
|
||||
<form method="post" role="form" aria-label="Edit tls host form">{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Name" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="hidden" name="host_id" value="{{ compute.id }}">
|
||||
<input type="text" name="name" class="form-control" value="{{ compute.name }}" maxlength="20" required pattern="[a-z0-9\.\-_]+">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "FQDN / IP" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="hostname" class="form-control" value="{{ compute.hostname }}" required pattern="[a-z0-9\:\.\-_]+">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Username" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="login" class="form-control" placeholder="{% trans "Name" %}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Password" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="password" name="password" class="form-control" value="{{ compute.password }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Details" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="details" class="form-control" placeholder="{% trans "Details" %}" value="{{ compute.details }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-danger mr-auto" name="host_del">
|
||||
{% trans "Delete" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
{% trans "Close" %}
|
||||
</button>
|
||||
<button type="submit" class="btn btn-primary" name="host_edit">
|
||||
{% trans "Change" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% if compute.type == 4 %}
|
||||
<form method="post" role="form" aria-label="Edit/delete host form">{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Name" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="hidden" name="host_id" value="{{ compute.id }}">
|
||||
<input type="text" name="name" class="form-control" value="{{ compute.name }}" maxlength="20" required pattern="[a-z0-9\.\-_]+">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Details" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="details" class="form-control" placeholder="{% trans 'Details' %}" value="{{ compute.details }}">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="submit" class="btn btn-danger mr-auto" name="host_del">
|
||||
{% trans "Delete" %}
|
||||
</button>
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
{% trans "Close" %}
|
||||
<button type="submit" class="btn btn-primary" name="host_edit">
|
||||
{% trans "Change" %}
|
||||
</button>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div><!-- /.modal-content -->
|
||||
</div><!-- /.modal-dialog -->
|
||||
</div><!-- /.modal -->
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<div class="col-lg-12">
|
||||
<div class="alert alert-warning alert-dismissable">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
<i class="fa fa-exclamation-triangle"></i> <strong>{% trans "Warning" %}:</strong> {% trans "Hypervisor doesn't have any Computes" %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% load font_awesome %}
|
||||
{% load icons %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "Add Compute" %}{% endblock %}
|
||||
|
@ -11,7 +11,6 @@
|
|||
<h2 class="page-header">{% trans "Create Compute" %}</h2>
|
||||
</div>
|
||||
</div>
|
||||
{% bootstrap_messages %}
|
||||
<div class="row">
|
||||
<div class="thumbnail col-sm-10 offset-1">
|
||||
<form id="create-update" action="" method="post">
|
||||
|
|
69
computes/templates/computes/list.html
Normal file
69
computes/templates/computes/list.html
Normal file
|
@ -0,0 +1,69 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load static %}
|
||||
{% load common_tags %}
|
||||
{% load icons %}
|
||||
{% block title %}{% trans "Computes" %}{% endblock %}
|
||||
{% block content %}
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
{% include 'create_comp_block.html' %}
|
||||
{% include 'search_block.html' %}
|
||||
<h3 class="page-header">{% trans "Computes" %}</h3>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'errors_block.html' %}
|
||||
<div class="row">
|
||||
{% if not computes %}
|
||||
<div class="col-lg-12">
|
||||
<div class="alert alert-warning alert-dismissable">
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
{% icon 'exclamation-triangle '%} <strong>{% trans "Warning" %}:</strong> {% trans "You don't have any computes" %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-lg-12">
|
||||
<table class="table table-striped table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th span="col">{% trans "Name" %}</th>
|
||||
<th span="col">{% trans "Status" %}</th>
|
||||
<th span="col">{% trans "Details" %}</th>
|
||||
<th span="col">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="searchable">
|
||||
{% for compute in computes %}
|
||||
<tr>
|
||||
<td>
|
||||
{{ compute.name }}
|
||||
</td>
|
||||
<td>
|
||||
{% if compute.status is True %}{% trans "Connected" %}{% else %}{% trans "Not Connected" %}{% endif %}
|
||||
</td>
|
||||
<td>
|
||||
{{ compute.details|default:"" }}
|
||||
</td>
|
||||
<td>
|
||||
<div class="float-right btn-group">
|
||||
{% if compute.status is True %}
|
||||
<a class="btn btn-success" title="{%trans "Overview" %}" href="{% url 'overview' compute.id %}">{% icon 'eye' %}</a>
|
||||
{% else %}
|
||||
<a class="btn btn-light" title="{%trans "Overview" %}">{% icon 'eye' %}</a>
|
||||
{% endif %}
|
||||
<a class="btn btn-primary" title="{%trans "Edit" %}" href="{% url 'compute_update' compute.id %}">{% icon 'pencil' %}</a>
|
||||
<a class="btn btn-danger" title="{%trans "Delete" %}" href="{% url 'compute_delete' compute.id %}">{% icon 'times' %}</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
|
||||
{% block script %}
|
||||
<script src="{% static "js/filter-table.js" %}"></script>
|
||||
{% endblock script %}
|
|
@ -1,7 +1,7 @@
|
|||
{% load i18n %}
|
||||
{% load bootstrap4 %}
|
||||
{% load font_awesome %}
|
||||
<div class="btn-group float-right" role="group" aria-label="Add host button group">
|
||||
{% load icons %}
|
||||
<div class="btn-group float-right mt-1" role="group" aria-label="Add host button group">
|
||||
<a href="{% url 'add_tcp_host' %}" class="btn btn-success">{% trans "TCP" %}</a>
|
||||
<a href="{% url 'add_ssh_host' %}" class="btn btn-success">{% trans "SSH" %}</a>
|
||||
<a href="{% url 'add_tls_host' %}" class="btn btn-success">{% trans "TLS" %}</a>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase
|
||||
|
||||
|
@ -20,6 +21,52 @@ class ComputesTestCase(TestCase):
|
|||
response = self.client.get(reverse('computes'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_create_update_delete(self):
|
||||
response = self.client.get(reverse('add_socket_host'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('add_socket_host'),
|
||||
{
|
||||
'name': 'l1',
|
||||
'details': 'Created',
|
||||
'hostname': 'localhost',
|
||||
'type': 4,
|
||||
},
|
||||
)
|
||||
self.assertRedirects(response, reverse('computes'))
|
||||
|
||||
compute = Compute.objects.get(pk=2)
|
||||
self.assertEqual(compute.name, 'l1')
|
||||
self.assertEqual(compute.details, 'Created')
|
||||
|
||||
response = self.client.get(reverse('compute_update', args=[2]))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('compute_update', args=[2]),
|
||||
{
|
||||
'name': 'l2',
|
||||
'details': 'Updated',
|
||||
'hostname': 'localhost',
|
||||
'type': 4,
|
||||
},
|
||||
)
|
||||
self.assertRedirects(response, reverse('computes'))
|
||||
|
||||
compute = Compute.objects.get(pk=2)
|
||||
self.assertEqual(compute.name, 'l2')
|
||||
self.assertEqual(compute.details, 'Updated')
|
||||
|
||||
response = self.client.get(reverse('compute_delete', args=[2]))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.post(reverse('compute_delete', args=[2]))
|
||||
self.assertRedirects(response, reverse('computes'))
|
||||
|
||||
with self.assertRaises(ObjectDoesNotExist):
|
||||
Compute.objects.get(id=2)
|
||||
|
||||
def test_overview(self):
|
||||
response = self.client.get(reverse('overview', args=[1]))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
@ -78,6 +125,16 @@ class ComputesTestCase(TestCase):
|
|||
response = self.client.get(reverse('machines', kwargs={'compute_id': 1, 'arch': 'x86_64'}))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# TODO: get_compute_disk_buses
|
||||
def test_compute_disk_buses(self):
|
||||
response = self.client.get(
|
||||
reverse('buses', kwargs={
|
||||
'compute_id': 1,
|
||||
'arch': 'x86_64',
|
||||
'machine': 'pc',
|
||||
'disk': 'disk',
|
||||
}))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# TODO: domcaps
|
||||
def test_dom_capabilities(self):
|
||||
response = self.client.get(reverse('domcaps', kwargs={'compute_id': 1, 'arch': 'x86_64', 'machine': 'pc'}))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
|
|
@ -12,12 +12,16 @@ from storages.views import get_volumes, storage, storages
|
|||
|
||||
urlpatterns = [
|
||||
path('', views.computes, name='computes'),
|
||||
path('add_tcp_host/', views.add_host, {'FormClass': forms.TcpComputeForm}, name='add_tcp_host'),
|
||||
path('add_ssh_host/', views.add_host, {'FormClass': forms.SshComputeForm}, name='add_ssh_host'),
|
||||
path('add_tls_host/', views.add_host, {'FormClass': forms.TlsComputeForm}, name='add_tls_host'),
|
||||
path('add_socket_host/', views.add_host, {'FormClass': forms.SocketComputeForm}, name='add_socket_host'),
|
||||
path('<int:compute_id>/', include([
|
||||
path('add_tcp_host/', views.compute_create, {'FormClass': forms.TcpComputeForm}, name='add_tcp_host'),
|
||||
path('add_ssh_host/', views.compute_create, {'FormClass': forms.SshComputeForm}, name='add_ssh_host'),
|
||||
path('add_tls_host/', views.compute_create, {'FormClass': forms.TlsComputeForm}, name='add_tls_host'),
|
||||
path('add_socket_host/', views.compute_create, {'FormClass': forms.SocketComputeForm}, name='add_socket_host'),
|
||||
path(
|
||||
'<int:compute_id>/',
|
||||
include([
|
||||
path('', views.overview, name='overview'),
|
||||
path('update/', views.compute_update, name='compute_update'),
|
||||
path('delete/', views.compute_delete, name='compute_delete'),
|
||||
path('statistics', views.compute_graph, name='compute_graph'),
|
||||
path('instances/', instances, name='instances'),
|
||||
path('storages/', storages, name='storages'),
|
||||
|
@ -32,8 +36,12 @@ urlpatterns = [
|
|||
path('secrets/', secrets, name='secrets'),
|
||||
path('create/', create_instance_select_type, name='create_instance_select_type'),
|
||||
path('create/archs/<str:arch>/machines/<str:machine>/', create_instance, name='create_instance'),
|
||||
path('archs/<str:arch>/machines', views.get_compute_machine_types, name='machines'),
|
||||
path('archs/<str:arch>/machines/<str:machine>/disks/<str:disk>/buses', views.get_compute_disk_buses, name='buses'),
|
||||
path('archs/<str:arch>/machines/<str:machine>/capabilities', views.get_dom_capabilities, name='domcaps'),
|
||||
path('archs/<str:arch>/machines/', views.get_compute_machine_types, name='machines'),
|
||||
path(
|
||||
'archs/<str:arch>/machines/<str:machine>/disks/<str:disk>/buses/',
|
||||
views.get_compute_disk_buses,
|
||||
name='buses',
|
||||
),
|
||||
path('archs/<str:arch>/machines/<str:machine>/capabilities/', views.get_dom_capabilities, name='domcaps'),
|
||||
])),
|
||||
]
|
||||
|
|
|
@ -9,7 +9,7 @@ from libvirt import libvirtError
|
|||
|
||||
from accounts.models import UserInstance
|
||||
from admin.decorators import superuser_only
|
||||
from computes.forms import (ComputeEditHostForm, SocketComputeForm, SshComputeForm, TcpComputeForm, TlsComputeForm)
|
||||
from computes.forms import (SocketComputeForm, SshComputeForm, TcpComputeForm, TlsComputeForm)
|
||||
from computes.models import Compute
|
||||
from instances.models import Instance
|
||||
from vrtManager.connection import (CONN_SOCKET, CONN_SSH, CONN_TCP, CONN_TLS, connection_manager, wvmConnect)
|
||||
|
@ -22,58 +22,10 @@ def computes(request):
|
|||
:param request:
|
||||
:return:
|
||||
"""
|
||||
def get_hosts_status(computes):
|
||||
"""
|
||||
Function return all hosts all vds on host
|
||||
"""
|
||||
compute_data = []
|
||||
for compute in computes:
|
||||
compute_data.append({
|
||||
'id': compute.id,
|
||||
'name': compute.name,
|
||||
'hostname': compute.hostname,
|
||||
'status': connection_manager.host_is_up(compute.type, compute.hostname),
|
||||
'type': compute.type,
|
||||
'login': compute.login,
|
||||
'password': compute.password,
|
||||
'details': compute.details
|
||||
})
|
||||
return compute_data
|
||||
|
||||
error_messages = []
|
||||
computes = Compute.objects.filter().order_by('name')
|
||||
computes_info = get_hosts_status(computes)
|
||||
|
||||
if request.method == 'POST':
|
||||
if 'host_del' in request.POST:
|
||||
compute_id = request.POST.get('host_id', '')
|
||||
try:
|
||||
del_user_inst_on_host = UserInstance.objects.filter(instance__compute_id=compute_id)
|
||||
del_user_inst_on_host.delete()
|
||||
finally:
|
||||
try:
|
||||
del_inst_on_host = Instance.objects.filter(compute_id=compute_id)
|
||||
del_inst_on_host.delete()
|
||||
finally:
|
||||
del_host = Compute.objects.get(id=compute_id)
|
||||
del_host.delete()
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
if 'host_edit' in request.POST:
|
||||
form = ComputeEditHostForm(request.POST)
|
||||
if form.is_valid():
|
||||
data = form.cleaned_data
|
||||
compute_edit = Compute.objects.get(id=data['host_id'])
|
||||
compute_edit.name = data['name']
|
||||
compute_edit.hostname = data['hostname']
|
||||
compute_edit.login = data['login']
|
||||
compute_edit.password = data['password']
|
||||
compute_edit.details = data['details']
|
||||
compute_edit.save()
|
||||
return HttpResponseRedirect(request.get_full_path())
|
||||
else:
|
||||
for msg_err in form.errors.values():
|
||||
error_messages.append(msg_err.as_text())
|
||||
return render(request, 'computes.html', locals())
|
||||
return render(request, 'computes/list.html', {'computes': computes})
|
||||
|
||||
|
||||
@superuser_only
|
||||
|
@ -108,6 +60,51 @@ def overview(request, compute_id):
|
|||
return render(request, 'overview.html', locals())
|
||||
|
||||
|
||||
@superuser_only
|
||||
def compute_create(request, FormClass):
|
||||
form = FormClass(request.POST or None)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect(reverse('computes'))
|
||||
|
||||
return render(request, 'computes/form.html', {'form': form})
|
||||
|
||||
|
||||
@superuser_only
|
||||
def compute_update(request, compute_id):
|
||||
compute = get_object_or_404(Compute, pk=compute_id)
|
||||
|
||||
if compute.type == 1:
|
||||
FormClass = TcpComputeForm
|
||||
elif compute.type == 2:
|
||||
FormClass = SshComputeForm
|
||||
elif compute.type == 3:
|
||||
FormClass = TlsComputeForm
|
||||
elif compute.type == 4:
|
||||
FormClass = SocketComputeForm
|
||||
|
||||
form = FormClass(request.POST or None, instance=compute)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect(reverse('computes'))
|
||||
|
||||
return render(request, 'computes/form.html', {'form': form})
|
||||
|
||||
|
||||
@superuser_only
|
||||
def compute_delete(request, compute_id):
|
||||
compute = get_object_or_404(Compute, pk=compute_id)
|
||||
if request.method == 'POST':
|
||||
compute.delete()
|
||||
return redirect('computes')
|
||||
|
||||
return render(
|
||||
request,
|
||||
'common/confirm_delete.html',
|
||||
{'object': compute},
|
||||
)
|
||||
|
||||
|
||||
def compute_graph(request, compute_id):
|
||||
"""
|
||||
:param request:
|
||||
|
@ -248,13 +245,3 @@ def get_dom_capabilities(request, compute_id, arch, machine):
|
|||
pass
|
||||
|
||||
return HttpResponse(json.dumps(data))
|
||||
|
||||
|
||||
@superuser_only
|
||||
def add_host(request, FormClass):
|
||||
form = FormClass(request.POST or None)
|
||||
if form.is_valid():
|
||||
form.save()
|
||||
return redirect(reverse('computes'))
|
||||
|
||||
return render(request, 'computes/form.html', {'form': form})
|
||||
|
|
19
conf/requirements-dev.txt
Normal file
19
conf/requirements-dev.txt
Normal file
|
@ -0,0 +1,19 @@
|
|||
beautifulsoup4==4.9.1
|
||||
coverage==5.1
|
||||
Django==2.2.13
|
||||
django-bootstrap4==2.0.1
|
||||
django-debug-toolbar==2.2
|
||||
django-icons==2.0.0
|
||||
django-login-required-middleware==0.5.0
|
||||
gunicorn==20.0.4
|
||||
libsass==0.20.0
|
||||
libvirt-python==6.3.0
|
||||
lxml==4.5.0
|
||||
numpy==1.18.4
|
||||
pytz==2020.1
|
||||
rwlock==0.0.7
|
||||
six==1.15.0
|
||||
soupsieve==2.0.1
|
||||
sqlparse==0.3.1
|
||||
websockify==0.9.0
|
||||
yapf==0.30.0
|
|
@ -1,14 +1,16 @@
|
|||
beautifulsoup4==4.9.1
|
||||
Django==2.2.13
|
||||
django-bootstrap4==2.0.1
|
||||
django-fa==1.0.0
|
||||
django-icons==2.0.0
|
||||
django-login-required-middleware==0.5.0
|
||||
gunicorn==20.0.4
|
||||
libvirt-python==6.3.0
|
||||
libsass==0.20.0
|
||||
libvirt-python==6.3.0
|
||||
lxml==4.5.0
|
||||
numpy==1.18.4
|
||||
pytz==2020.1
|
||||
rwlock==0.0.7
|
||||
six==1.15.0
|
||||
soupsieve==2.0.1
|
||||
sqlparse==0.3.1
|
||||
websockify==0.9.0
|
||||
|
|
33
create/migrations/0003_auto_20200615_0637.py
Normal file
33
create/migrations/0003_auto_20200615_0637.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 2.2.13 on 2020-06-15 06:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('create', '0002_addFlavors'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='flavor',
|
||||
name='disk',
|
||||
field=models.IntegerField(verbose_name='disk'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='flavor',
|
||||
name='label',
|
||||
field=models.CharField(max_length=12, verbose_name='label'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='flavor',
|
||||
name='memory',
|
||||
field=models.IntegerField(verbose_name='memory'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='flavor',
|
||||
name='vcpu',
|
||||
field=models.IntegerField(verbose_name='vcpu'),
|
||||
),
|
||||
]
|
|
@ -1,11 +1,12 @@
|
|||
from django.db.models import Model, CharField, IntegerField
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class Flavor(Model):
|
||||
label = CharField(_('label'), max_length=12)
|
||||
memory = IntegerField(_('memory'))
|
||||
vcpu = IntegerField(_('vcpu'))
|
||||
disk = IntegerField(_('disk'))
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
|
33
instances/migrations/0003_auto_20200615_0637.py
Normal file
33
instances/migrations/0003_auto_20200615_0637.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 2.2.13 on 2020-06-15 06:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('instances', '0002_permissionset'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='instance',
|
||||
name='created',
|
||||
field=models.DateField(auto_now_add=True, verbose_name='created'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='instance',
|
||||
name='is_template',
|
||||
field=models.BooleanField(default=False, verbose_name='is template'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='instance',
|
||||
name='name',
|
||||
field=models.CharField(max_length=120, verbose_name='name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='instance',
|
||||
name='uuid',
|
||||
field=models.CharField(max_length=36, verbose_name='uuid'),
|
||||
),
|
||||
]
|
|
@ -1,5 +1,4 @@
|
|||
from django.db.models import (CASCADE, BooleanField, CharField, DateField,
|
||||
ForeignKey, Model)
|
||||
from django.db.models import (CASCADE, BooleanField, CharField, DateField, ForeignKey, Model)
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from computes.models import Compute
|
||||
|
@ -12,8 +11,9 @@ class Instance(Model):
|
|||
is_template = BooleanField(_('is template'), default=False)
|
||||
created = DateField(_('created'), auto_now_add=True)
|
||||
|
||||
def __unicode__(self):
|
||||
return self.name
|
||||
def __str__(self):
|
||||
return f'{self.compute}/{self.name}'
|
||||
|
||||
|
||||
class PermissionSet(Model):
|
||||
"""
|
||||
|
@ -21,8 +21,6 @@ class PermissionSet(Model):
|
|||
"""
|
||||
class Meta:
|
||||
default_permissions = ()
|
||||
permissions = (
|
||||
('clone_instances', _('Can clone instances')),
|
||||
)
|
||||
permissions = (('clone_instances', _('Can clone instances')), )
|
||||
|
||||
managed = False
|
||||
|
|
|
@ -63,7 +63,7 @@
|
|||
<tbody class="searchable">
|
||||
{% for inst, vm in all_user_vms.items %}
|
||||
<tr>
|
||||
<td><a href="{% url 'instance' vm.compute_id vm.name %}">{{ vm.name }}</a><br><small><em>{{ vm.title }}</em></small></td>
|
||||
<td><a href="{% url 'instances:instance' vm.compute_id vm.name %}">{{ vm.name }}</a><br><small><em>{{ vm.title }}</em></small></td>
|
||||
<td>{% if vm.status == 1 %}
|
||||
<span class="text-success">{% trans "Active" %}</span>
|
||||
{% endif %}
|
||||
|
|
|
@ -42,7 +42,7 @@
|
|||
<tr host="{{ host.1 }}">
|
||||
<td class="text-right">{{ forloop.counter }} </td>
|
||||
<td> 
|
||||
<a class="text-secondary" href="{% url 'instance' host.0 inst %}">{{ inst }}</a><br>
|
||||
<a class="text-secondary" href="{% url 'instances:instance' host.0 inst %}">{{ inst }}</a><br>
|
||||
<small><em>{{ vm.title }}</em></small>
|
||||
</td>
|
||||
<td class="d-none d-sm-table-cell">
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
{% for host, inst in all_host_vms.items %}
|
||||
{% for inst, vm in inst.items %}
|
||||
<tr>
|
||||
<td><a href="{% url 'instance' host.0 inst %}">{{ inst }}</a><br><small><em>{{ info.title }}</em></small></td>
|
||||
<td><a href="{% url 'instances:instance' host.0 inst %}">{{ inst }}</a><br><small><em>{{ info.title }}</em></small></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>
|
||||
{% if vm.status == 1 %}<span class="text-success">{% trans "Active" %}</span>{% endif %}
|
||||
|
|
|
@ -17,7 +17,7 @@
|
|||
<a class="nav-link" href="{% url 'instances' compute.id %}"><i class="fa fa-desktop"></i> {% trans "Instances" %}</a>
|
||||
</li>
|
||||
<li class="nav-item active">
|
||||
<a class="nav-link" href="{% url 'instance' compute.id vname %}"><i class="fa fa-hdd-o"></i> {{ vname }}</a>
|
||||
<a class="nav-link" href="{% url 'instances:instance' compute.id vname %}"><i class="fa fa-hdd-o"></i> {{ vname }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
|
@ -52,7 +52,7 @@
|
|||
{{ ipv4 }} |
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
<a class="text-secondary" href="{% url 'instance' compute.id vname %}" title="{% trans 'Refresh instance info' %}"><span class="fa fa-refresh"></span></a>
|
||||
<a class="text-secondary" href="{% url 'instances:instance' compute.id vname %}" title="{% trans 'Refresh instance info' %}"><span class="fa fa-refresh"></span></a>
|
||||
</div>
|
||||
{% if user_quota_msg %}
|
||||
<div class="alert alert-warning fade show">
|
||||
|
@ -1749,7 +1749,7 @@
|
|||
</script>
|
||||
<script>
|
||||
function random_mac(net) {
|
||||
$.getJSON('{% url 'random_mac_address' %}', function (data) {
|
||||
$.getJSON('{% url 'instances:random_mac_address' %}', function (data) {
|
||||
$('input[name="' + net + '"]').val(data['mac']);
|
||||
});
|
||||
}
|
||||
|
@ -1766,7 +1766,7 @@
|
|||
<script>
|
||||
function guess_mac_address(src_elem, net) {
|
||||
new_vname = $(src_elem).val();
|
||||
guess_mac_address_url = "{% url 'guess_mac_address' 1 %}".replace(1, new_vname);
|
||||
guess_mac_address_url = "{% url 'instances:guess_mac_address' 1 %}".replace(1, new_vname);
|
||||
$.getJSON(guess_mac_address_url, function(data) {
|
||||
$('input[name="clone-net-mac-'+net+'"]').val(data['mac']);
|
||||
});
|
||||
|
@ -1774,7 +1774,7 @@
|
|||
</script>
|
||||
<script>
|
||||
function guess_clone_name() {
|
||||
$.getJSON('{% url 'guess_clone_name' %}', function(data) {
|
||||
$.getJSON('{% url 'instances:guess_clone_name' %}', function(data) {
|
||||
guessed_name = data['name'].split(".")[0];
|
||||
$('#clone_name').val(guessed_name);
|
||||
update_clone_disk_name(guessed_name);
|
||||
|
@ -2181,7 +2181,7 @@
|
|||
{% endfor %}
|
||||
|
||||
var graph_interval = window.setInterval(function graph_usage() {
|
||||
$.getJSON('{% url 'inst_graph' compute_id vname %}', function (data) {
|
||||
$.getJSON('{% url 'instances:inst_graph' compute_id vname %}', function (data) {
|
||||
|
||||
cpuChart.data.labels.push(data.timeline);
|
||||
cpuChart.data.datasets[0].data.push(data.cpudata);
|
||||
|
@ -2234,7 +2234,7 @@
|
|||
backgroundJobRunning = false;
|
||||
var status_interval = window.setInterval(function get_status() {
|
||||
var status = {{ status|lower }};
|
||||
$.getJSON('{% url 'inst_status' compute_id vname %}', function (data) {
|
||||
$.getJSON('{% url 'instances:inst_status' compute_id vname %}', function (data) {
|
||||
if (data['status'] != status && !backgroundJobRunning) {
|
||||
window.location.reload()
|
||||
}
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
{% for host, insts in all_host_vms.items %}
|
||||
{% for inst, vm in insts.items %}
|
||||
<tr>
|
||||
<td><a class="text-secondary" href="{% url 'instance' host.0 inst %}">{{ inst }}</a><br><small><em>{{ vm.title }}</em></small></td>
|
||||
<td><a class="text-secondary" href="{% url 'instances:instance' host.0 inst %}">{{ inst }}</a><br><small><em>{{ vm.title }}</em></small></td>
|
||||
<td class="d-none d-md-table-cell"><small><em>{% if vm.userinstances.count > 0 %}{{ vm.userinstances.first_user.user.username }}{% if vm.userinstances.count > 1 %} (+{{ vm.userinstances.count|add:"-1" }}){% endif %}{% endif %}</em></small></td>
|
||||
<td>
|
||||
{% if vm.status == 1 %}<span class="text-success">{% trans "Active" %}</span>{% endif %}
|
||||
|
|
|
@ -1,8 +1,11 @@
|
|||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
app_name = 'instances'
|
||||
|
||||
urlpatterns = [
|
||||
path('', views.allinstances, name='allinstances'),
|
||||
path('', views.allinstances, name='index'),
|
||||
path('<int:compute_id>/<vname>/', views.instance, name='instance'),
|
||||
path('statistics/<int:compute_id>/<vname>/', views.inst_graph, name='inst_graph'),
|
||||
path('status/<int:compute_id>/<vname>/', views.inst_status, name='inst_status'),
|
||||
|
|
33
logs/migrations/0002_auto_20200615_0637.py
Normal file
33
logs/migrations/0002_auto_20200615_0637.py
Normal file
|
@ -0,0 +1,33 @@
|
|||
# Generated by Django 2.2.13 on 2020-06-15 06:37
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('logs', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='logs',
|
||||
name='date',
|
||||
field=models.DateTimeField(auto_now=True, verbose_name='date'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='logs',
|
||||
name='instance',
|
||||
field=models.CharField(max_length=50, verbose_name='instance'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='logs',
|
||||
name='message',
|
||||
field=models.CharField(max_length=255, verbose_name='message'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='logs',
|
||||
name='user',
|
||||
field=models.CharField(max_length=50, verbose_name='user'),
|
||||
),
|
||||
]
|
|
@ -1,11 +1,12 @@
|
|||
from django.db.models import Model, CharField, DateTimeField
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
|
||||
class Logs(Model):
|
||||
user = CharField(_('user'), max_length=50)
|
||||
instance = CharField(_('instance'), max_length=50)
|
||||
message = CharField(_('message'), max_length=255)
|
||||
date = DateTimeField(_('date'), auto_now=True)
|
||||
|
||||
def __unicode__(self):
|
||||
def __str__(self):
|
||||
return self.instance
|
||||
|
|
|
@ -169,3 +169,7 @@ p {
|
|||
vertical-align: middle;
|
||||
}
|
||||
|
||||
/* remove ugly dotted outline on tabs */
|
||||
a {
|
||||
outline: 0;
|
||||
}
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
{% load static %}
|
||||
{% load bootstrap4 %}
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
|
@ -36,7 +37,7 @@
|
|||
{% include 'navbar.html' %}
|
||||
|
||||
<div role="main" class="container">
|
||||
|
||||
{% bootstrap_messages %}
|
||||
{% block content %}{% endblock %}
|
||||
|
||||
</div> <!-- /container -->
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
{% extends "base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% load font_awesome %}
|
||||
{% load icons %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{%trans "Delete" %}{% endblock %}
|
|
@ -1,9 +1,9 @@
|
|||
{% extends "base.html" %}
|
||||
{% load bootstrap4 %}
|
||||
{% load font_awesome %}
|
||||
{% load icons %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}{% trans "User" %}{% endblock %}
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="row">
|
|
@ -1,5 +1,5 @@
|
|||
{% load i18n %}
|
||||
{% load font_awesome %}
|
||||
{% load icons %}
|
||||
{% load common_tags %}
|
||||
<!-- Fixed navbar -->
|
||||
<nav class="navbar navbar-expand-md navbar-dark bg-primary mb-3" aria-label="Main top navbar">
|
||||
|
@ -11,7 +11,7 @@
|
|||
<div id="navbar" class="navbar-collapse collapse">
|
||||
<ul class="navbar-nav mr-auto mt-2 mt-md-0">
|
||||
<li class="nav-item {% class_active request '^/instances' %}">
|
||||
<a class="nav-link" href="{% url 'allinstances' %}"><i class="fa fa-fw fa-desktop"></i> {% trans "Instances" %}</a>
|
||||
<a class="nav-link" href="{% url 'instances:index' %}"><i class="fa fa-fw fa-desktop"></i> {% trans "Instances" %}</a>
|
||||
</li>
|
||||
{% if request.user.is_superuser %}
|
||||
<li class="nav-item {% class_active request '^/computes' %}">
|
||||
|
@ -42,7 +42,7 @@
|
|||
<a class="dropdown-item disabled" href="#">
|
||||
{% trans "Language" %}: <span class="badge badge-secondary">{{ LANGUAGE_CODE }}</span>
|
||||
</a>
|
||||
<a class="dropdown-item {% view_active request 'profile' %}" href="{% url 'profile' %}"><i class="fa fa-fw fa-pencil-square-o"></i> {% trans "Profile" %}</a>
|
||||
<a class="dropdown-item {% view_active request 'profile' %}" href="{% url 'profile' %}">{% icon 'vcard' %} {% trans "Profile" %}</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="{% url 'logout' %}"><i class="fa fa-fw fa-power-off"></i> {% trans "Log Out" %}</a>
|
||||
</div>
|
||||
|
|
4
templates/search_block.html
Normal file
4
templates/search_block.html
Normal file
|
@ -0,0 +1,4 @@
|
|||
{% load i18n %}
|
||||
<div class="float-right search">
|
||||
<input id="filter" class="form-control" type="text" placeholder="{% trans "Search" %}">
|
||||
</div>
|
|
@ -22,7 +22,7 @@ INSTALLED_APPS = [
|
|||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'bootstrap4',
|
||||
'fa',
|
||||
'django_icons',
|
||||
'accounts',
|
||||
'admin',
|
||||
'appsettings',
|
||||
|
|
Loading…
Reference in a new issue