mirror of
https://github.com/retspen/webvirtcloud
synced 2025-07-31 12:41:08 +00:00
Instances overhaul
This commit is contained in:
parent
f23e6b000f
commit
47009d47ca
69 changed files with 5011 additions and 4127 deletions
62
instances/fixtures/initial_data.json
Normal file
62
instances/fixtures/initial_data.json
Normal file
|
@ -0,0 +1,62 @@
|
|||
[
|
||||
{
|
||||
"model": "create.Flavor",
|
||||
"pk": 1,
|
||||
"fields": {
|
||||
"label": "micro",
|
||||
"vcpu": "1",
|
||||
"memory": "512",
|
||||
"disk": "20"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "create.Flavor",
|
||||
"pk": 2,
|
||||
"fields": {
|
||||
"label": "mini",
|
||||
"vcpu": "2",
|
||||
"memory": "1024",
|
||||
"disk": "30"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "create.Flavor",
|
||||
"pk": 3,
|
||||
"fields": {
|
||||
"label": "small",
|
||||
"vcpu": "2",
|
||||
"memory": "2048",
|
||||
"disk": "40"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "create.Flavor",
|
||||
"pk": 4,
|
||||
"fields": {
|
||||
"label": "medium",
|
||||
"vcpu": "2",
|
||||
"memory": "4096",
|
||||
"disk": "60"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "create.Flavor",
|
||||
"pk": 5,
|
||||
"fields": {
|
||||
"label": "large",
|
||||
"vcpu": "4",
|
||||
"memory": "8192",
|
||||
"disk": "80"
|
||||
}
|
||||
},
|
||||
{
|
||||
"model": "create.Flavor",
|
||||
"pk": 6,
|
||||
"fields": {
|
||||
"label": "xlarge",
|
||||
"vcpu": "8",
|
||||
"memory": "16384",
|
||||
"disk": "160"
|
||||
}
|
||||
}
|
||||
]
|
66
instances/forms.py
Normal file
66
instances/forms.py
Normal file
|
@ -0,0 +1,66 @@
|
|||
import re
|
||||
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from appsettings.models import AppSettings
|
||||
from webvirtcloud.settings import QEMU_CONSOLE_LISTEN_ADDRESSES, QEMU_KEYMAPS
|
||||
|
||||
from .models import Flavor
|
||||
|
||||
|
||||
class FlavorForm(forms.ModelForm):
|
||||
class Meta:
|
||||
model = Flavor
|
||||
fields = '__all__'
|
||||
|
||||
|
||||
class ConsoleForm(forms.Form):
|
||||
type = forms.ChoiceField()
|
||||
listen_on = forms.ChoiceField()
|
||||
generate_password = forms.BooleanField(required=False)
|
||||
clear_password = forms.BooleanField(required=False)
|
||||
password = forms.CharField(widget=forms.PasswordInput(render_value=True), required=False)
|
||||
clear_keymap = forms.BooleanField(required=False)
|
||||
keymap = forms.ChoiceField(required=False)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super(ConsoleForm, self).__init__(*args, **kwargs)
|
||||
type_choices = ((c, c) for c in AppSettings.objects.get(key="QEMU_CONSOLE_DEFAULT_TYPE").choices_as_list())
|
||||
keymap_choices = [('auto', 'Auto')] + list((c, c) for c in QEMU_KEYMAPS)
|
||||
self.fields['type'] = forms.ChoiceField(choices=type_choices)
|
||||
self.fields['listen_on'] = forms.ChoiceField(choices=QEMU_CONSOLE_LISTEN_ADDRESSES)
|
||||
self.fields['keymap'] = forms.ChoiceField(choices=keymap_choices)
|
||||
|
||||
|
||||
class NewVMForm(forms.Form):
|
||||
name = forms.CharField(error_messages={'required': _('No Virtual Machine name has been entered')}, max_length=64)
|
||||
firmware = forms.CharField(max_length=50, required=False)
|
||||
vcpu = forms.IntegerField(error_messages={'required': _('No VCPU has been entered')})
|
||||
vcpu_mode = forms.CharField(max_length=20, required=False)
|
||||
disk = forms.IntegerField(required=False)
|
||||
memory = forms.IntegerField(error_messages={'required': _('No RAM size has been entered')})
|
||||
networks = forms.CharField(error_messages={'required': _('No Network pool has been choosen')})
|
||||
nwfilter = forms.CharField(required=False)
|
||||
storage = forms.CharField(max_length=20, required=False)
|
||||
template = forms.CharField(required=False)
|
||||
images = forms.CharField(required=False)
|
||||
cache_mode = forms.CharField(error_messages={'required': _('Please select HDD cache mode')})
|
||||
hdd_size = forms.IntegerField(required=False)
|
||||
meta_prealloc = forms.BooleanField(required=False)
|
||||
virtio = forms.BooleanField(required=False)
|
||||
qemu_ga = forms.BooleanField(required=False)
|
||||
mac = forms.CharField(required=False)
|
||||
console_pass = forms.CharField(required=False, empty_value="", widget=forms.PasswordInput())
|
||||
graphics = forms.CharField(error_messages={'required': _('Please select a graphics type')})
|
||||
video = forms.CharField(error_messages={'required': _('Please select a video driver')})
|
||||
listener_addr = forms.ChoiceField(required=True, widget=forms.RadioSelect, choices=QEMU_CONSOLE_LISTEN_ADDRESSES)
|
||||
|
||||
def clean_name(self):
|
||||
name = self.cleaned_data['name']
|
||||
have_symbol = re.match('^[a-zA-Z0-9._-]+$', name)
|
||||
if not have_symbol:
|
||||
raise forms.ValidationError(_('The name of the virtual machine must not contain any special characters'))
|
||||
elif len(name) > 64:
|
||||
raise forms.ValidationError(_('The name of the virtual machine must not exceed 20 characters'))
|
||||
return name
|
23
instances/migrations/0004_auto_20200618_0817.py
Normal file
23
instances/migrations/0004_auto_20200618_0817.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 2.2.13 on 2020-06-18 08:17
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('instances', '0003_auto_20200615_0637'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='instance',
|
||||
name='name',
|
||||
field=models.CharField(db_index=True, max_length=120, verbose_name='name'),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='instance',
|
||||
name='uuid',
|
||||
field=models.CharField(db_index=True, max_length=36, verbose_name='uuid'),
|
||||
),
|
||||
]
|
23
instances/migrations/0005_flavor.py
Normal file
23
instances/migrations/0005_flavor.py
Normal file
|
@ -0,0 +1,23 @@
|
|||
# Generated by Django 2.2.13 on 2020-06-23 12:12
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('instances', '0004_auto_20200618_0817'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.CreateModel(
|
||||
name='Flavor',
|
||||
fields=[
|
||||
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||
('label', models.CharField(max_length=12, verbose_name='label')),
|
||||
('memory', models.IntegerField(verbose_name='memory')),
|
||||
('vcpu', models.IntegerField(verbose_name='vcpu')),
|
||||
('disk', models.IntegerField(verbose_name='disk')),
|
||||
],
|
||||
),
|
||||
]
|
35
instances/migrations/0006_addFlavors.py
Normal file
35
instances/migrations/0006_addFlavors.py
Normal file
|
@ -0,0 +1,35 @@
|
|||
# Generated by Django 2.2.10 on 2020-01-28 07:01
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
def add_flavors(apps, schema_editor):
|
||||
Flavor = apps.get_model("instances", "Flavor")
|
||||
add_flavor = Flavor(label="micro", vcpu="1", memory="512", disk="20")
|
||||
add_flavor.save()
|
||||
add_flavor = Flavor(label="mini", vcpu="2", memory="1024", disk="30")
|
||||
add_flavor.save()
|
||||
add_flavor = Flavor(label="small", vcpu="2", memory="2048", disk="40")
|
||||
add_flavor.save()
|
||||
add_flavor = Flavor(label="medium", vcpu="2", memory="4096", disk="60")
|
||||
add_flavor.save()
|
||||
add_flavor = Flavor(label="large", vcpu="4", memory="8192", disk="80")
|
||||
add_flavor.save()
|
||||
add_flavor = Flavor(label="xlarge", vcpu="8", memory="16384", disk="160")
|
||||
add_flavor.save()
|
||||
|
||||
|
||||
def del_flavors(apps, schema_editor):
|
||||
Flavor = apps.get_model("instances", "Flavor")
|
||||
Flavor.objects.all().delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('instances', '0005_flavor'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(add_flavors, del_flavors),
|
||||
]
|
18
instances/migrations/0007_auto_20200624_0821.py
Normal file
18
instances/migrations/0007_auto_20200624_0821.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.13 on 2020-06-24 08:21
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('instances', '0006_addFlavors'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='flavor',
|
||||
name='label',
|
||||
field=models.CharField(max_length=12, unique=True, verbose_name='label'),
|
||||
),
|
||||
]
|
18
instances/migrations/0008_auto_20200708_0950.py
Normal file
18
instances/migrations/0008_auto_20200708_0950.py
Normal file
|
@ -0,0 +1,18 @@
|
|||
# Generated by Django 2.2.13 on 2020-07-08 09:50
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('instances', '0007_auto_20200624_0821'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.AlterField(
|
||||
model_name='instance',
|
||||
name='created',
|
||||
field=models.DateTimeField(auto_now_add=True, verbose_name='created'),
|
||||
),
|
||||
]
|
|
@ -1,21 +1,211 @@
|
|||
from django.db.models import (CASCADE, BooleanField, CharField, DateField, ForeignKey, Model)
|
||||
from django.db import models
|
||||
from django.utils.functional import cached_property
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from libvirt import VIR_DOMAIN_XML_SECURE
|
||||
|
||||
from computes.models import Compute
|
||||
from vrtManager.instance import wvmInstance
|
||||
|
||||
|
||||
class Instance(Model):
|
||||
compute = ForeignKey(Compute, on_delete=CASCADE)
|
||||
name = CharField(_('name'), max_length=120)
|
||||
uuid = CharField(_('uuid'), max_length=36)
|
||||
is_template = BooleanField(_('is template'), default=False)
|
||||
created = DateField(_('created'), auto_now_add=True)
|
||||
class Flavor(models.Model):
|
||||
label = models.CharField(_('label'), max_length=12, unique=True)
|
||||
memory = models.IntegerField(_('memory'))
|
||||
vcpu = models.IntegerField(_('vcpu'))
|
||||
disk = models.IntegerField(_('disk'))
|
||||
|
||||
def __str__(self):
|
||||
return self.label
|
||||
|
||||
|
||||
class InstanceManager(models.Manager):
|
||||
def get_queryset(self):
|
||||
return super().get_queryset().select_related('compute')
|
||||
|
||||
|
||||
class Instance(models.Model):
|
||||
compute = models.ForeignKey(Compute, on_delete=models.CASCADE)
|
||||
name = models.CharField(_('name'), max_length=120, db_index=True)
|
||||
uuid = models.CharField(_('uuid'), max_length=36, db_index=True)
|
||||
is_template = models.BooleanField(_('is template'), default=False)
|
||||
created = models.DateTimeField(_('created'), auto_now_add=True)
|
||||
|
||||
objects = InstanceManager()
|
||||
|
||||
def __str__(self):
|
||||
return f'{self.compute}/{self.name}'
|
||||
|
||||
@cached_property
|
||||
def proxy(self):
|
||||
return wvmInstance(
|
||||
self.compute.hostname,
|
||||
self.compute.login,
|
||||
self.compute.password,
|
||||
self.compute.type,
|
||||
self.name,
|
||||
)
|
||||
|
||||
class PermissionSet(Model):
|
||||
@cached_property
|
||||
def media(self):
|
||||
return self.proxy.get_media_devices()
|
||||
|
||||
@cached_property
|
||||
def media_iso(self):
|
||||
return sorted(self.proxy.get_iso_media())
|
||||
|
||||
@cached_property
|
||||
def disks(self):
|
||||
return self.proxy.get_disk_devices()
|
||||
|
||||
@cached_property
|
||||
def status(self):
|
||||
return self.proxy.get_status()
|
||||
|
||||
@cached_property
|
||||
def autostart(self):
|
||||
return self.proxy.get_autostart()
|
||||
|
||||
@cached_property
|
||||
def bootmenu(self):
|
||||
return self.proxy.get_bootmenu()
|
||||
|
||||
@cached_property
|
||||
def boot_order(self):
|
||||
return self.proxy.get_bootorder()
|
||||
|
||||
@cached_property
|
||||
def arch(self):
|
||||
return self.proxy.get_arch()
|
||||
|
||||
@cached_property
|
||||
def machine(self):
|
||||
return self.proxy.get_machine_type()
|
||||
|
||||
@cached_property
|
||||
def firmware(self):
|
||||
return self.proxy.get_loader()
|
||||
|
||||
@cached_property
|
||||
def nvram(self):
|
||||
return self.proxy.get_nvram()
|
||||
|
||||
@cached_property
|
||||
def vcpu(self):
|
||||
return self.proxy.get_vcpu()
|
||||
|
||||
@cached_property
|
||||
def vcpu_range(self):
|
||||
return self.proxy.get_max_cpus()
|
||||
|
||||
@cached_property
|
||||
def cur_vcpu(self):
|
||||
return self.proxy.get_cur_vcpu()
|
||||
|
||||
@cached_property
|
||||
def vcpus(self):
|
||||
return self.proxy.get_vcpus()
|
||||
|
||||
@cached_property
|
||||
def get_uuid(self):
|
||||
return self.proxy.get_uuid()
|
||||
|
||||
@cached_property
|
||||
def memory(self):
|
||||
return self.proxy.get_memory()
|
||||
|
||||
@cached_property
|
||||
def cur_memory(self):
|
||||
return self.proxy.get_cur_memory()
|
||||
|
||||
@cached_property
|
||||
def title(self):
|
||||
return self.proxy.get_title()
|
||||
|
||||
@cached_property
|
||||
def description(self):
|
||||
return self.proxy.get_description()
|
||||
|
||||
@cached_property
|
||||
def networks(self):
|
||||
return self.proxy.get_net_devices()
|
||||
|
||||
@cached_property
|
||||
def qos(self):
|
||||
return self.proxy.get_all_qos()
|
||||
|
||||
@cached_property
|
||||
def telnet_port(self):
|
||||
return self.proxy.get_telnet_port()
|
||||
|
||||
@cached_property
|
||||
def console_type(self):
|
||||
return self.proxy.get_console_type()
|
||||
|
||||
@cached_property
|
||||
def console_port(self):
|
||||
return self.proxy.get_console_port()
|
||||
|
||||
@cached_property
|
||||
def console_keymap(self):
|
||||
return self.proxy.get_console_keymap()
|
||||
|
||||
@cached_property
|
||||
def console_listen_address(self):
|
||||
return self.proxy.get_console_listen_addr()
|
||||
|
||||
@cached_property
|
||||
def guest_agent(self):
|
||||
return False if self.proxy.get_guest_agent() is None else True
|
||||
|
||||
@cached_property
|
||||
def guest_agent_ready(self):
|
||||
return self.proxy.is_agent_ready()
|
||||
|
||||
@cached_property
|
||||
def video_model(self):
|
||||
return self.proxy.get_video_model()
|
||||
|
||||
@cached_property
|
||||
def video_models(self):
|
||||
return self.proxy.get_video_models(self.arch, self.machine)
|
||||
|
||||
@cached_property
|
||||
def snapshots(self):
|
||||
return sorted(self.proxy.get_snapshot(), reverse=True, key=lambda k: k['date'])
|
||||
|
||||
@cached_property
|
||||
def inst_xml(self):
|
||||
return self.proxy._XMLDesc(VIR_DOMAIN_XML_SECURE)
|
||||
|
||||
@cached_property
|
||||
def has_managed_save_image(self):
|
||||
return self.proxy.get_managed_save_image()
|
||||
|
||||
@cached_property
|
||||
def console_passwd(self):
|
||||
return self.proxy.get_console_passwd()
|
||||
|
||||
@cached_property
|
||||
def cache_modes(self):
|
||||
return sorted(self.proxy.get_cache_modes().items())
|
||||
|
||||
@cached_property
|
||||
def io_modes(self):
|
||||
return sorted(self.proxy.get_io_modes().items())
|
||||
|
||||
@cached_property
|
||||
def discard_modes(self):
|
||||
return sorted(self.proxy.get_discard_modes().items())
|
||||
|
||||
@cached_property
|
||||
def detect_zeroes_modes(self):
|
||||
return sorted(self.proxy.get_detect_zeroes_modes().items())
|
||||
|
||||
@cached_property
|
||||
def formats(self):
|
||||
return self.proxy.get_image_formats()
|
||||
|
||||
|
||||
class PermissionSet(models.Model):
|
||||
"""
|
||||
Dummy model for holding set of permissions we need to be automatically added by Django
|
||||
"""
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<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 instance network form">{% csrf_token %}
|
||||
<form action="{% url 'instances:add_network' instance.id %}" method="post" action="" role="form" aria-label="Add instance network form">{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "MAC" %}</label>
|
||||
<div class="col-sm-6">
|
||||
|
|
|
@ -13,7 +13,7 @@
|
|||
<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 instance owner form">{% csrf_token %}
|
||||
<form id="add-owner-form" method="post" action="{% url 'instances:add_owner' instance.id %}" role="form" aria-label="Add instance owner form">{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "User" %}</label>
|
||||
<div class="col-sm-6">
|
||||
|
@ -24,12 +24,12 @@
|
|||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</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_owner">{% trans "Add" %}</button>
|
||||
<button type="submit" class="btn btn-primary" form="add-owner-form">{% trans "Add" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div> <!-- /.modal-content -->
|
||||
</div> <!-- /.modal-dialog -->
|
||||
</div> <!-- /.modal -->
|
||||
|
|
|
@ -23,7 +23,7 @@
|
|||
</ul>
|
||||
<div class="tab-content">
|
||||
<div class="tab-pane active" id="NewDisk">
|
||||
<form method="post" role="form" aria-label="Add new volume to disk form">{% csrf_token %}
|
||||
<form action="{% url 'instances:add_new_vol' instance.id %}" method="post" role="form" aria-label="Add new volume to disk form">{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<p class="font-weight-bold">{% trans "Volume parameters" %}</p>
|
||||
<div class="form-group row">
|
||||
|
@ -46,7 +46,7 @@
|
|||
<label class="col-sm-3 col-form-label">{% trans "Format" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="format" class="custom-select image-format">
|
||||
{% for format in formats %}
|
||||
{% for format in instance.formats %}
|
||||
<option value="{{ format }}" {% if format == default_format %}selected{% endif %}>{% trans format %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
@ -73,7 +73,7 @@
|
|||
<label class="col-sm-3 col-form-label">{% trans "Cache" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="cache" class="custom-select image-format">
|
||||
{% for mode, name in cache_modes %}
|
||||
{% for mode, name in instance.cache_modes %}
|
||||
<option value="{{ mode }}" {% if mode == default_cache %}selected{% endif %}>{% trans name %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
@ -93,7 +93,7 @@
|
|||
</div>
|
||||
</div> <!-- /.modal-body -->
|
||||
<div class="tab-pane" id="ExistingDisk">
|
||||
<form method="post" role="form" aria-label="Add existing volume to instance form">{% csrf_token %}
|
||||
<form action="{% url 'instances:add_existing_vol' instance.id %}" method="post" role="form" aria-label="Add existing volume to instance form">{% csrf_token %}
|
||||
<div class="modal-body">
|
||||
<p class="font-weight-bold">{% trans "Volume parameters" %}</p>
|
||||
<div class="form-group row">
|
||||
|
@ -103,7 +103,7 @@
|
|||
<button id="select_storage" class="btn btn-secondary dropdown-toggle form-control" type="button" data-toggle="dropdown">{% trans 'Select Pool' %}...</button>
|
||||
<div class="dropdown-menu">
|
||||
{% for storage in storages_host %}
|
||||
<a class="dropdown-item" href="#" onclick="get_volumes({{ compute_id }}, '{{ storage }}')">{{ storage }}</a>
|
||||
<a class="dropdown-item" href="#" onclick="get_volumes({{ instance.compute.id }}, '{{ storage }}')">{{ storage }}</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<input id="selected_storage" name="selected_storage" hidden/>
|
||||
|
@ -133,7 +133,7 @@
|
|||
<label class="col-sm-3 col-form-label">{% trans "Cache" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="cache" class="custom-select image-format">
|
||||
{% for mode, name in cache_modes %}
|
||||
{% for mode, name in instance.cache_modes %}
|
||||
<option value="{{ mode }}" {% if mode == default_cache %}selected{% endif %}>{% trans name %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
@ -142,7 +142,7 @@
|
|||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans "Close" %}</button>
|
||||
<button type="submit" class="btn btn-success" name="add_existing_vol">{% trans "Add Volume" %}</button>
|
||||
<button type="submit" class="btn btn-success">{% trans "Add Volume" %}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
|
|
@ -1,131 +1,55 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load icons %}
|
||||
{% load staticfiles %}
|
||||
{% block title %}{% trans "Instances" %}{% endblock %}
|
||||
{% block style %}
|
||||
<link rel="stylesheet" href="{% static "css/sortable-theme-bootstrap.css" %}" />
|
||||
{% endblock %}
|
||||
{% block page_header %}{% trans "Instances" %}{% endblock page_header %}
|
||||
|
||||
{% block page_header_extra %}
|
||||
{% if request.user.is_superuser %}
|
||||
{% include 'create_inst_block.html' %}
|
||||
{% endif %}
|
||||
<div class="float-right search">
|
||||
<input id="filter" class="form-control" type="text" placeholder="{% trans 'Search' %}">
|
||||
</div>
|
||||
{% endblock page_header_extra %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page Heading -->
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
{% if request.user.is_superuser %}
|
||||
{% include 'create_inst_block.html' %}
|
||||
{% endif %}
|
||||
{% if all_host_vms or all_user_vms %}
|
||||
<div class="float-right search">
|
||||
<input id="filter" class="form-control" type="text" placeholder="{% trans 'Search' %}">
|
||||
</div>
|
||||
{% endif %}
|
||||
<h2 class="page-header">{% trans "Instances" %}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
|
||||
{% include 'errors_block.html' %}
|
||||
|
||||
{% for compute in computes %}
|
||||
{% if compute.status is not True %}
|
||||
<div class="alert alert-danger alert-dismissible fade show" role="alert">
|
||||
{% trans 'Problem occurred with host' %} {{ compute.name }}: {{ compute.status }}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-label="Close">
|
||||
<span aria-hidden="true">×</span>
|
||||
</button>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
<div class="col-lg-12">
|
||||
{% if request.user.is_superuser %}
|
||||
{% if not all_host_vms %}
|
||||
<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 "You don't have any Instance" %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{% if view_style == "nongrouped" %}
|
||||
{% include 'allinstances_index_nongrouped.html' %}
|
||||
{% endif %}
|
||||
{% if view_style == "grouped" %}
|
||||
{% include 'allinstances_index_grouped.html' %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if app_settings.VIEW_INSTANCES_LIST_STYLE == 'grouped' and request.user.is_superuser %}
|
||||
{% include 'allinstances_index_grouped.html' %}
|
||||
{% else %}
|
||||
{% if not all_user_vms %}
|
||||
<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 "You don't have any Instance" %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<table class="table table-hover table-striped sortable-theme-bootstrap" data-sortable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans 'Name' %}</th>
|
||||
<th scope="col">{% trans 'Status' %}</th>
|
||||
<th scope="col">{% trans 'VCPU' %}</th>
|
||||
<th scope="col">{% trans 'Memory' %}</th>
|
||||
<th scope="col" data-sortable="false" style="width: 165px;">{% trans 'Actions' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="searchable">
|
||||
{% for inst, vm in all_user_vms.items %}
|
||||
<tr>
|
||||
<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 %}
|
||||
{% if vm.status == 5 %}
|
||||
<span class="text-danger">{% trans "Off" %}</span>
|
||||
{% endif %}
|
||||
{% if vm.status == 3 %}
|
||||
<span class="text-warning">{% trans "Suspend" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ vm.vcpu }}</td>
|
||||
<td>{{ vm.memory }} {% trans "MB" %}</td>
|
||||
<td>
|
||||
{% include "instance_actions.html" %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
{% include 'allinstances_index_nongrouped.html' %}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% endblock content %}
|
||||
{% block script %}
|
||||
<script src="{% static "js/sortable.min.js" %}"></script>
|
||||
<script>
|
||||
function open_console(uuid) {
|
||||
window.open("{% url 'console' %}?token=" + uuid, "", "width=850,height=485");
|
||||
}
|
||||
</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 () {
|
||||
instances_filter_cookie = Cookies.get("instances_filter");
|
||||
if (instances_filter_cookie) {
|
||||
$('#filter').val(instances_filter_cookie);
|
||||
$('#filter').each(filter_table);
|
||||
}
|
||||
(function ($) {
|
||||
$('#filter').keyup(filter_table)
|
||||
}(jQuery));
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
function goto_instance_clone(compute, instance) {
|
||||
window.location = "/instances/" + compute + "/" + instance + "/#clone";
|
||||
}
|
||||
</script>
|
||||
{% if request.user.is_superuser %}
|
||||
<script src="{% static "js/sortable.min.js" %}"></script>
|
||||
<script>
|
||||
function goto_compute() {
|
||||
let compute = $("#compute_select").val();
|
||||
{#window.location.href = "{% url 'create_instance' 1 %}".replace(1, compute);#}
|
||||
window.location.href = "{% url 'create_instance_select_type' 1 %}".replace(1, compute);
|
||||
function open_console(uuid) {
|
||||
window.open("{% url 'console' %}?token=" + uuid, "", "width=850,height=485");
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
<script src="{% static 'js/filter-table.js' %}"></script>
|
||||
{% if request.user.is_superuser %}
|
||||
<script>
|
||||
function goto_compute() {
|
||||
let compute = $("#compute_select").val();
|
||||
window.location.href = "{% url 'instances:create_instance_select_type' 1 %}".replace(1, compute);
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock script %}
|
||||
|
|
|
@ -1,72 +1,77 @@
|
|||
{% load i18n %}
|
||||
{% load icons %}
|
||||
<table class="table table-hover sortable-theme-bootstrap" data-sortable>
|
||||
<thead>
|
||||
<tr style="border: 2px solid transparent; ">
|
||||
<th scope="col" data-sortable="false"><a class="text-secondary" href="#" id="hide_all_instances" onclick="hide_all_host_instances()">#</a></th>
|
||||
<th scope="col">{% trans "Name" %}<br>{% trans "Description" %}</th>
|
||||
<th scope="col" class="d-none d-sm-table-cell">{% trans "User"%}</th>
|
||||
<th scope="col">{% trans "Status" %}</th>
|
||||
<th scope="col" class="d-none d-sm-table-cell">{% trans "VCPU" %}</th>
|
||||
<th scope="col" class="d-none d-sm-table-cell">{% trans "Memory" %}</th>
|
||||
<th scope="col" style="width:200px;" data-sortable="false">{% trans "Actions" %} & {% trans "Mem Usage" %}</th>
|
||||
</tr>
|
||||
<tr style="border: 2px solid transparent; ">
|
||||
<th scope="col" data-sortable="false"><a class="text-secondary" href="#" id="hide_all_instances" onclick="hide_all_host_instances()">#</a></th>
|
||||
<th scope="col">{% trans "Name" %}<br>{% trans "Description" %}</th>
|
||||
<th scope="col" class="d-none d-sm-table-cell">{% trans "User"%}</th>
|
||||
<th scope="col">{% trans "Status" %}</th>
|
||||
<th scope="col" class="d-none d-sm-table-cell">{% trans "VCPU" %}</th>
|
||||
<th scope="col" class="d-none d-sm-table-cell">{% trans "Memory" %}</th>
|
||||
<th scope="col" style="width:200px;" data-sortable="false">{% trans "Actions" %} & {% trans "Mem Usage" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="searchable">
|
||||
{% for host, insts in all_host_vms.items %}
|
||||
<tr class="font-weight-bold active" style="border-bottom: 2px solid darkgray;border-top: 2px solid darkgray;">
|
||||
<td>
|
||||
<span id="collapse_host_instances_{{ host.1 }}" class="fa fa-chevron-up" onclick="hide_host_instances('{{ host.1 }}');"></span>
|
||||
</td>
|
||||
<td>
|
||||
<a class="text-secondary" href="{% url 'overview' host.0 %}">{{ host.1 }}</a>
|
||||
<span id="inst_count_badge_{{ host.1 }}" class="badge badge-secondary d-none">{{ insts.items|length }}</span>
|
||||
</td>
|
||||
<td class="d-none d-sm-table-cell"></td>
|
||||
<td>
|
||||
{% if host.2 == 1 %}<span class="text-success">{% trans "Active" %}</span>{% endif %}
|
||||
{% if host.2 == 2 %}<span class="text-warning">{% trans "Not Active" %}</span>{% endif %}
|
||||
{% if host.2 == 3 %}<span class="text-danger">{% trans "Connection Failed" %}</span>{% endif %}
|
||||
</td>
|
||||
<td class="d-none d-sm-table-cell text-center">{{ host.3 }}</td>
|
||||
<td class="d-none d-sm-table-cell text-right">{{ host.4|filesizeformat }}</td>
|
||||
<td>
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-success" role="progressbar" style="width: {{ host.5 }}%"
|
||||
aria-valuenow="{{ host.5 }}" aria-valuemin="0" aria-valuemax="100">{{ host.5 }}%
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
|
||||
{% for inst, vm in insts.items %}
|
||||
<tr host="{{ host.1 }}">
|
||||
<td class="text-right">{{ forloop.counter }} </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-sm-table-cell">
|
||||
<span class="font-small font-italic">
|
||||
{% if vm.userinstances.count > 0 %} {{ vm.userinstances.first_user.user.username }}
|
||||
{% if vm.userinstances.count > 1 %} (+{{ vm.userinstances.count|add:"-1" }}){% endif %}
|
||||
{% endif %}
|
||||
</span>
|
||||
</td>
|
||||
<td>
|
||||
{% if vm.status == 1 %}<span class="text-success">{% trans "Active" %}</span>{% endif %}
|
||||
{% if vm.status == 5 %}<span class="text-danger">{% trans "Off" %}</span>{% endif %}
|
||||
{% if vm.status == 3 %}<span class="text-warning">{% trans "Suspend" %}</span>{% endif %}
|
||||
</td>
|
||||
<td class="d-none d-sm-table-cell text-center">{{ vm.vcpu }}</td>
|
||||
<td class="d-none d-sm-table-cell text-right">{{ vm.memory |filesizeformat }}</td>
|
||||
<td class="text-nowrap">
|
||||
{% include 'instance_actions.html' %}
|
||||
</td>
|
||||
</tr>
|
||||
{% for compute in computes %}
|
||||
{% if compute.status is True and compute.instance_set.count > 0 %}
|
||||
<tr class="font-weight-bold active" style="border-bottom: 2px solid darkgray;border-top: 2px solid darkgray;">
|
||||
<td>
|
||||
<span id="collapse_host_instances_{{ compute.id }}" class="fa fa-chevron-up" onclick="hide_host_instances('{{ compute.id }}');"></span>
|
||||
</td>
|
||||
<td>
|
||||
<a class="text-secondary" href="{% url 'overview' compute.id %}">{{ compute.name }}</a>
|
||||
<span id="inst_count_badge_{{ compute.id }}" class="badge badge-secondary d-none">{{ compute.instance_set.count }}</span>
|
||||
</td>
|
||||
<td class="d-none d-sm-table-cell"></td>
|
||||
<td>
|
||||
<span class="text-success">{% trans "Connected" %}</span>
|
||||
</td>
|
||||
<td class="d-none d-sm-table-cell text-center">{{ compute.cpu_count }}</td>
|
||||
<td class="d-none d-sm-table-cell text-right">{{ compute.ram_size|filesizeformat }}</td>
|
||||
<td>
|
||||
<div class="progress">
|
||||
<div class="progress-bar bg-success" role="progressbar" style="width: {{ compute.ram_usage }}%"
|
||||
aria-valuenow="{{ compute.ram_usage }}" aria-valuemin="0" aria-valuemax="100">{{ compute.ram_usage }}%
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% for instance in compute.instance_set.all %}
|
||||
<tr host="{{ compute.id }}">
|
||||
<td class="text-right">{{ forloop.counter }} </td>
|
||||
<td>
|
||||
<a class="text-secondary" href="{% url 'instances:instance' instance.id %}">{{ instance.name }}</a><br>
|
||||
</td>
|
||||
<td>
|
||||
<em>
|
||||
{% if instance.userinstance_set.all.count > 0 %}
|
||||
{{ instance.userinstance_set.all.0.user }}
|
||||
{% if instance.userinstance_set.all.count > 1 %}
|
||||
(+{{ instance.userinstance_set.all.count|add:"-1" }})
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
{% if instance.proxy.instance.info.0 == 1 %}<span class="text-success">{% trans "Active" %}</span>{% endif %}
|
||||
{% if instance.proxy.instance.info.0 == 5 %}<span class="text-danger">{% trans "Off" %}</span>{% endif %}
|
||||
{% if instance.proxy.instance.info.0 == 3 %}
|
||||
<span class="text-warning">{% trans "Suspended" %}</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td>{{ instance.proxy.instance.info.3 }}</td>
|
||||
<td>{{ instance.cur_memory }} MB</td>
|
||||
<td class="text-nowrap">
|
||||
{% include 'instance_actions.html' %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
{% block script %}
|
||||
<script>
|
||||
function hide_all_host_instances() {
|
||||
|
|
|
@ -1,33 +1,55 @@
|
|||
{% load i18n %}
|
||||
<table class="table table-hover sortable-theme-bootstrap" data-sortable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans "Name" %}<br>{% trans "Description" %}</th>
|
||||
<th scope="col">{% trans "Host" %}<br>{% trans "User"%}</th>
|
||||
<th scope="col">{% trans "Status" %}</th>
|
||||
<th scope="col">{% trans "VCPU" %}</th>
|
||||
<th scope="col">{% trans "Memory" %}</th>
|
||||
<th scope="col" style="width:200px;" data-sortable="false">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="col">{% trans 'Name' %}<br>{% trans 'Description' %}</th>
|
||||
{% if request.user.is_superuser %}
|
||||
<th scope="col">{% trans 'Host' %}<br>{% trans 'User' %}</th>
|
||||
{% endif %}
|
||||
<th scope="col">{% trans 'Status' %}</th>
|
||||
<th scope="col">{% trans 'VCPU' %}</th>
|
||||
<th scope="col">{% trans 'Memory' %}</th>
|
||||
<th scope="col" data-sortable="false">{% trans 'Actions' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="searchable">
|
||||
{% for host, inst in all_host_vms.items %}
|
||||
{% for inst, vm in inst.items %}
|
||||
<tr>
|
||||
<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 %}
|
||||
{% if vm.status == 5 %}<span class="text-danger">{% trans "Off" %}</span>{% endif %}
|
||||
{% if vm.status == 3 %}<span class="text-warning">{% trans "Suspend" %}</span>{% endif %}
|
||||
</td>
|
||||
<td>{{ vm.vcpu }}</td>
|
||||
<td>{{ vm.memory|filesizeformat }}</td>
|
||||
<td class="text-nowrap">
|
||||
{% include "instance_actions.html" %}
|
||||
</td>
|
||||
</tr>
|
||||
{% for instance in instances %}
|
||||
{% if instance.compute.status is True %}
|
||||
<tr>
|
||||
<td>
|
||||
<a class="text-secondary" href="{% url 'instances:instance' instance.id %}">
|
||||
{{ instance.name }}
|
||||
</a><br>
|
||||
<small><em>{{ instance.title }}</em></small>
|
||||
</td>
|
||||
{% if request.user.is_superuser %}
|
||||
<td>
|
||||
<a href="{% url 'overview' instance.compute.id %}">{{ instance.compute.name }}</a><br>
|
||||
<small><em>
|
||||
{% if instance.userinstance_set.all.count > 0 %}
|
||||
{{ instance.userinstance_set.all.0.user }}
|
||||
{% if instance.userinstance_set.all.count > 1 %}
|
||||
(+{{ instance.userinstance_set.all.count|add:"-1" }})
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</em></small>
|
||||
</td>
|
||||
{% endif %}
|
||||
<td>
|
||||
{% if instance.proxy.instance.info.0 == 1 %}<span
|
||||
class="text-success">{% trans "Active" %}</span>{% endif %}
|
||||
{% if instance.proxy.instance.info.0 == 5 %}<span
|
||||
class="text-danger">{% trans "Off" %}</span>{% endif %}
|
||||
{% if instance.proxy.instance.info.0 == 3 %}<span
|
||||
class="text-warning">{% trans "Suspended" %}</span>{% endif %}
|
||||
</td>
|
||||
<td>{{ instance.proxy.instance.info.3 }}</td>
|
||||
<td>{{ instance.cur_memory }} MB</td>
|
||||
<td class="text-nowrap">
|
||||
{% include 'instance_actions.html' %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
|
|
|
@ -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 'instances:instance' compute.id vname %}"><i class="fa fa-hdd-o"></i> {{ vname }}</a>
|
||||
<a class="nav-link" href="{% url 'instances:instance' instance.id %}"><i class="fa fa-hdd-o"></i> {{ instance.name }}</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
|
34
instances/templates/create_flav_block.html
Normal file
34
instances/templates/create_flav_block.html
Normal file
|
@ -0,0 +1,34 @@
|
|||
{% load i18n %}
|
||||
{% load bootstrap4 %}
|
||||
{% if request.user.is_superuser %}
|
||||
<button href="#addFlavor" type="button" class="btn btn-success btn-header float-right" data-toggle="modal">
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
||||
<!-- Modal Flavor -->
|
||||
<div class="modal fade" id="addFlavor" tabindex="-1" role="dialog" aria-labelledby="addFlavorLabel"
|
||||
aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h4 class="modal-title">{% trans "Add New Flavor" %}</h4>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" role="form" action="{% url 'instances:flavor_create' %}" id="flavor-create-form">
|
||||
{% csrf_token %}
|
||||
{% bootstrap_form flavor_form layout='horizontal' %}
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
{% trans "Close" %}
|
||||
</button>
|
||||
<button form="flavor-create-form" type="submit" class="btn btn-primary">
|
||||
{% trans "Add" %}
|
||||
</button>
|
||||
</div>
|
||||
</div> <!-- /.modal-content -->
|
||||
</div> <!-- /.modal-dialog -->
|
||||
</div> <!-- /.modal -->
|
||||
{% endif %}
|
|
@ -1,49 +1,48 @@
|
|||
{% load i18n %}
|
||||
{% if request.user.is_superuser %}
|
||||
<a href="#AddInstance" type="button" class="btn btn-success btn-header float-right" data-toggle="modal">
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
</a>
|
||||
<a href="#AddInstance" 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="AddInstance" tabindex="-1" role="dialog" aria-labelledby="AddNetPoolLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{% trans "Choose a compute for new instance" %}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" aria-label="Select compute for instance create form"> {% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Compute" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<select class="custom-select" id="compute_select">
|
||||
<option>{% trans "Please select" %}</option>
|
||||
{% for compute in computes_data %}
|
||||
<option {% if compute.status is not True %} class="font-italic text-muted" {% else %} value="{{ compute.id }}" {% endif %}>{{ compute.name }}</option>
|
||||
{% empty %}
|
||||
<option value="None">{% trans "None" %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
<!-- Modal pool -->
|
||||
<div class="modal fade" id="AddInstance" tabindex="-1" role="dialog" aria-labelledby="AddNetPoolLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{% trans "Choose a compute for new instance" %}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" aria-label="Select compute for instance create form">
|
||||
{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Compute" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<select class="custom-select" id="compute_select">
|
||||
<option>{% trans "Please select" %}</option>
|
||||
{% for compute in computes %}
|
||||
<option {% if compute.status is not True %} class="font-italic text-muted" {% else %} value="{{ compute.id }}" {% endif %}>{{ compute.name }}</option>
|
||||
{% empty %}
|
||||
<option value="None">{% trans "None" %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
{% trans "Close" %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">
|
||||
{% trans "Close" %}
|
||||
</button>
|
||||
{% if computes %}
|
||||
<button type="submit" class="btn btn-primary" name="choose" onclick='goto_compute()'>
|
||||
{% trans "Choose" %}
|
||||
</button>
|
||||
{% if computes %}
|
||||
<button type="submit" class="btn btn-primary" name="choose" onclick='goto_compute()'>
|
||||
{% trans "Choose" %}
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="btn btn-primary disabled">
|
||||
{% trans "Choose" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div> <!-- /.modal-content -->
|
||||
</div> <!-- /.modal-dialog -->
|
||||
</div> <!-- /.modal -->
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<button class="btn btn-primary disabled">
|
||||
{% trans "Choose" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div> <!-- /.modal-content -->
|
||||
</div> <!-- /.modal-dialog -->
|
||||
</div> <!-- /.modal -->
|
||||
|
|
153
instances/templates/create_instance_w1.html
Normal file
153
instances/templates/create_instance_w1.html
Normal file
|
@ -0,0 +1,153 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
{% block title %}{% trans "Create new instance" %} - {% trans "Select Type" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<!-- Page Heading -->
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<h3 class="page-header">
|
||||
{% blocktrans with host=compute.name %}New instance on {{ host }} {% endblocktrans %}
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
{% include 'errors_block.html' %}
|
||||
{% include 'pleasewaitdialog.html' %}
|
||||
|
||||
|
||||
{% if form.errors %}
|
||||
{% for field in form %}
|
||||
{% for error in field.errors %}
|
||||
<div class="alert alert-danger">
|
||||
<strong>{{ error|escape }}</strong>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% for error in form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
<strong>{{ error|escape }}</strong>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div class="row" id="max-width-page">
|
||||
<div class="col-lg-12">
|
||||
<div role="tabpanel">
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist" aria-label="Instance create method">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#select_architecture" aria-controls="flavor" role="tab" data-toggle="tab">
|
||||
{% trans "Architecture" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#addFromXML" aria-controls="addFromXML" role="tab" data-toggle="tab">
|
||||
{% trans "XML" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="select_architecture">
|
||||
|
||||
<div class="center-block">
|
||||
<form method="post" role="form" aria-label="Select instance architecture form">{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Architecture" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<select class="custom-select" id="select_archs" name="archs" onchange="get_machine_types({{ compute_id }}, value);">
|
||||
{% for hpv in hypervisors %}
|
||||
<option value="{{ hpv }}" {% if hpv == default_arch %}selected{% endif %}>{{ hpv }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Chipset" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<select class="custom-select" id="select_chipset" name="chipset">
|
||||
<!-- fill with script -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-6 offset-3">
|
||||
<button class="btn btn-block btn-primary" type="button" name="create_instance" onclick="goto_create()">
|
||||
{% trans "Next" %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="addFromXML">
|
||||
<div>
|
||||
<form method="post" role="form" aria-label="Create instance with XML form">{% csrf_token %}
|
||||
<div class="col-sm-12" id="xmlheight">
|
||||
<input type="hidden" name="dom_xml"/>
|
||||
<textarea id="editor"></textarea>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary float-right" name="create_xml" onclick="showPleaseWaitDialog()">
|
||||
{% trans "Create" %}
|
||||
</button>
|
||||
</form>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
let arch = $("#select_archs").val();
|
||||
get_machine_types({{ compute_id }}, arch);
|
||||
});
|
||||
|
||||
function get_machine_types(compute_id, arch) {
|
||||
get_machine_type_url = "/computes/" + compute_id + "/archs/" + arch + "/machines";
|
||||
$.getJSON(get_machine_type_url, function (data) {
|
||||
$("#select_chipset").find('option').remove();
|
||||
$("#select_archs").val(arch);
|
||||
$.each(data['machines'], function(i, item) {
|
||||
if (item == '{{ default_machine }}') {
|
||||
var selected = 'selected';
|
||||
}else{
|
||||
var selected = '';
|
||||
}
|
||||
$("#select_chipset").append('<option value="' + item + '"' + selected +'>' + item + '</option>');
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
<script src="{% static "js/ace/ace.js" %}"></script>
|
||||
<script>
|
||||
var editor = ace.edit("editor");
|
||||
editor.getSession().setMode("ace/mode/xml");
|
||||
|
||||
var input = $('input[name="dom_xml"]');
|
||||
editor.getSession().on("change",function () {
|
||||
input.val(editor.getSession().getValue());
|
||||
})
|
||||
</script>
|
||||
{% if request.user.is_superuser %}
|
||||
<script>
|
||||
function goto_create() {
|
||||
let compute = '{{ compute.id }}';
|
||||
let arch = $("#select_archs").val();
|
||||
let machine = $("#select_chipset").val();
|
||||
//create_machine_url = "/computes/" + compute + "/create/archs/" + arch + "/machines/" + machine;
|
||||
url = "{% url 'instances:create_instance' compute.id 'x86_64' 'pc' %}".replace(/x86_64/, arch).replace(/pc/, machine);
|
||||
window.location.href = url;//create_machine_url;
|
||||
}
|
||||
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
880
instances/templates/create_instance_w2.html
Normal file
880
instances/templates/create_instance_w2.html
Normal file
|
@ -0,0 +1,880 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
{% load icons %}
|
||||
{% block title %}{% trans "Create new instance" %}{% endblock %}
|
||||
{% block style %}
|
||||
<link href="{% static "css/bootstrap-multiselect.css" %}" rel="stylesheet">
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<!-- Page Heading -->
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<h3 class="page-header">
|
||||
{% blocktrans with host=compute.name %}New instance on {{ host }} {% endblocktrans %}</h3>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
{% include 'errors_block.html' %}
|
||||
{% include 'pleasewaitdialog.html' %}
|
||||
|
||||
|
||||
{% if form.errors %}
|
||||
{% for field in form %}
|
||||
{% for error in field.errors %}
|
||||
<div class="alert alert-danger">
|
||||
<strong>{{ error|escape }}</strong>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
{% for error in form.non_field_errors %}
|
||||
<div class="alert alert-danger">
|
||||
<strong>{{ error|escape }}</strong>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
<div class="row" id="max-width-page">
|
||||
<div class="col-lg-12">
|
||||
<div role="tabpanel">
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist" aria-label="Instance create method">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link float-right" href="#" role="tab" data-toggle="tab" onclick="goto_compute()">
|
||||
<span class="fa fa-arrow-left"></span>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#flavor" aria-controls="flavor" role="tab" data-toggle="tab">
|
||||
{% trans "Flavor" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#addCustom" aria-controls="addCustom" role="tab" data-toggle="tab">
|
||||
{% trans "Custom" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#addFromTemp" aria-controls="addFromTemp" role="tab" data-toggle="tab">
|
||||
{% trans "Template" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="flavor">
|
||||
{% if not flavors %}
|
||||
<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 Flavors" %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{% include 'create_flav_block.html' %}
|
||||
<h3 class="page-header">{% trans "Create from flavor" %}</h3>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">#</th>
|
||||
<th scope="col">{% trans "Name" %}</th>
|
||||
<th scope="col">{% trans "VCPU" %}</th>
|
||||
<th scope="col">{% trans "RAM" %}</th>
|
||||
<th scope="col">{% trans "HDD" %}</th>
|
||||
<th scope="colgroup" colspan="2">{% trans "Action" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for flavor in flavors %}
|
||||
<tr>
|
||||
<td>{{ forloop.counter }}</td>
|
||||
<td>{{ flavor.label }}</td>
|
||||
<td>{{ flavor.vcpu }}</td>
|
||||
<td>{{ flavor.memory }} {% trans "MB" %}</td>
|
||||
<td>{{ flavor.disk }} {% trans "GB" %}</td>
|
||||
<td style="width:5px;">
|
||||
<div class="modal fade" id="addVMflavor{{ forloop.counter }}" tabindex="-1" role="dialog" aria-labelledby="addVMFlavorLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{% trans "Create Virtual Machine" %} ({{ flavor.label }})</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<form method="post" role="form" aria-label="Create instance from flavor form">{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Name" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" name="name"
|
||||
placeholder="{% trans "Name" %}" maxlength="64" required
|
||||
pattern="[a-zA-Z0-9\.\-_]+">
|
||||
<input type="hidden" name="vcpu" value="{{ flavor.vcpu }}">
|
||||
<input type="hidden" name="memory" value="{{ flavor.memory }}">
|
||||
<input type="hidden" name="hdd_size" value="{{ flavor.disk }}">
|
||||
</div>
|
||||
</div>
|
||||
{% if firmwares %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Firmware" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select class="form-control" id="select_firmware" name="firmware">
|
||||
{% for frm in firmwares %}
|
||||
<option value="{{ frm }}" {% if frm in default_firmware %}selected{% endif %}>{{ frm }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if dom_caps.cpu_modes %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "VCPU Config" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select class="form-control" id="vcpu_mode" name="vcpu_mode">
|
||||
<option value=""> {% trans 'no-mode' %}</option>
|
||||
{% for mode in dom_caps.cpu_modes %}
|
||||
{% if mode == 'custom' %}
|
||||
<optgroup label="Custom CPU Models">
|
||||
{% for model in dom_caps.cpu_custom_models %}
|
||||
<option value="{{ model }}"> {% trans model %}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% else %}
|
||||
<option value="{{ mode }}" {% if mode == default_cpu_mode %}selected {% endif %}>
|
||||
{% trans mode %}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Storage" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="hidden" name="cache_mode" value="default">
|
||||
<select class="form-control" name="storage">
|
||||
{% if storages %}
|
||||
{% for storage in storages %}
|
||||
<option value="{{ storage }}">{{ storage }}</option>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<option value="">{% trans "None" %}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Network" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select class="form-control" name="networks">
|
||||
{% for network in networks %}
|
||||
<option value="{{ network }}">{{ network }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Advanced" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<button class="btn btn-block btn-outline-light" type="button" data-toggle="collapse" data-target="#flavAddCollapse" aria-expanded="false" aria-controls="collapseExample">
|
||||
<span class="fa fa-caret-down"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapse" id="flavAddCollapse">
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "NWFilter" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select class="form-control" name="nwfilter">
|
||||
<option value="">{% trans "None" %}</option>
|
||||
{% for nwfilter in nwfilters %}
|
||||
<option value="{{ nwfilter }}">{{ nwfilter }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "HDD cache mode" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select class="form-control" id="cache_mode" name="cache_mode">
|
||||
{% for mode, name in cache_modes %}
|
||||
<option value="{{ mode }}" {% if mode == default_cache %}selected {% endif %}>
|
||||
{% trans name %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "MAC" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" name="mac" maxlength="17" value="{{ mac_auto }}" required pattern="[a-zA-Z0-9:]+">
|
||||
</div>
|
||||
</div>
|
||||
{% if dom_caps.graphics_support == 'yes' %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Graphics" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select class="form-control" name="graphics">
|
||||
{% for graphics in dom_caps.graphics_types %}
|
||||
<option value="{{ graphics }}" {% if default_graphics == graphics %}selected{% endif %}>{{ graphics }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Video" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select class="form-control" name="video">
|
||||
{% if not videos %}
|
||||
<option value="vga">vga</option>
|
||||
<option value="cirrus">cirrus</option>
|
||||
{% endif %}
|
||||
{% for video in videos %}
|
||||
<option value="{{ video }}">{{ video }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Console Access" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select class="custom-select" name="listener_addr">
|
||||
{% for addr, label in listener_addr %}
|
||||
<option value="{{ addr }}" {% if addr == "0.0.0.0" %} selected {% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Console Password" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input class="form-control" type="password" name="console_pass" placeholder="{% trans "Console Password" %}" maxlength="64">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Guest Agent" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="checkbox" name="qemu_ga" value="true" checked>
|
||||
</div>
|
||||
</div>
|
||||
{% if virtio_support %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "VirtIO" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="checkbox" name="virtio" value="{{ virtio_support }}" checked>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button type="button" class="btn btn-secondary" data-dismiss="modal">{% trans "Close" %}</button>
|
||||
{% if storages %}
|
||||
<button type="submit" class="btn btn-primary" name="create">{% trans "Create" %}</button>
|
||||
{% else %}
|
||||
<button class="btn btn-primary disabled">{% trans "Create" %}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<a data-toggle="modal" href="#addVMflavor{{ forloop.counter }}" class="btn btn-sm btn-secondary">
|
||||
<span class="fa fa-plus"></span>
|
||||
</a>
|
||||
</td>
|
||||
<td style="width:5px;">
|
||||
<form action="{% url 'instances:flavor_delete' flavor.id %}" method="post" role="form" aria-label="Delete flavor form">
|
||||
{% csrf_token %}
|
||||
<button type="submit" class="btn btn-sm btn-secondary" name="delete_flavor" onclick="return confirm('{% trans "Are you sure?" %}')">
|
||||
{% icon 'trash' %}
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="addCustom">
|
||||
<div>
|
||||
<form method="post" role="form" aria-label="Create custom instance form">{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Name" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" name="name" placeholder="{% trans "Name" %}" maxlength="64" required pattern="[a-zA-Z0-9\.\-_]+">
|
||||
</div>
|
||||
</div>
|
||||
{% if firmwares %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Firmware" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select class="custom-select" id="select_firmware" name="firmware">
|
||||
{% for frm in firmwares %}
|
||||
<option value="{{ frm }}" {% if frm == default_firmware %}selected{% endif %}>{{ frm }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "VCPU" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" name="vcpu" value="1" maxlength="2" required pattern="[0-9]">
|
||||
</div>
|
||||
</div>
|
||||
{% if dom_caps.cpu_modes %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "VCPU Config" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select id="vcpu_mode" name="vcpu_mode" class="custom-select">
|
||||
<option value=""> {% trans 'no-mode' %}</option>
|
||||
{% for mode in dom_caps.cpu_modes %}
|
||||
{% if mode == 'custom' %}
|
||||
<optgroup label="Custom CPU Models">
|
||||
{% for model in dom_caps.cpu_custom_models %}
|
||||
<option value="{{ model }}"> {% trans model %}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% else %}
|
||||
<option value="{{ mode }}" {% if mode == default_cpu_mode %}selected {% endif %}>
|
||||
{% trans mode %}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "RAM" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" name="memory" value="512" maxlength="5" required pattern="[0-9]+">
|
||||
</div>
|
||||
<label class="col-sm-1 col-form-label">{% trans "MB" %}</label>
|
||||
</div>
|
||||
<div id="disk_list_div" class="form-group row d-none">
|
||||
<label id="added_disks" class="col-sm-3 col-form-label">{% trans "Added Disks" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<ul id="img-list" class="form-group row">
|
||||
<!-- populated from javascript -->
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "HDD" %}</label>
|
||||
<input id="images" name="images" type="hidden" value=""/>
|
||||
<div class="col-sm-3">
|
||||
<select id="storage-control" name="storage-control" class="form-control" onchange="get_cust_vols({{ compute_id }}, value);">
|
||||
{% if storages %}
|
||||
<option value disabled selected>{% trans "Select pool" %}...</option>
|
||||
{% for storage in storages %}
|
||||
<option value="{{ storage }}" >{{ storage }}</option>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<option value="">{% trans "None" %}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<select id="image-control" class="form-control" name="image-control" multiple="multiple">
|
||||
<!-- populated from javascript -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Network" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<ul id="net-list">
|
||||
<!-- populated from javascript -->
|
||||
</ul>
|
||||
<input id="networks" type="hidden" name="networks" value=""/>
|
||||
<select id="network-control" name="network-control" class="form-control" multiple="multiple">
|
||||
{% for network in networks %}
|
||||
<option value="{{ network }}">{{ network }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Advanced" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<button class="btn btn-block btn-outline-light" type="button" data-toggle="collapse" data-target="#customAdvCollapse" aria-expanded="false" aria-controls="collapseExample">
|
||||
<span class="fa fa-caret-down"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapse" id="customAdvCollapse">
|
||||
<div class="form-group row meta-prealloc">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Disk Metadata" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="checkbox" name="meta_prealloc" title="{% trans "Metadata preallocation" %}" value="true">
|
||||
</div>
|
||||
<label class="col-lg-1 col-form-label">{% trans "Image" %}</label>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "HDD Cache Mode" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select id="cache_mode" name="cache_mode" class="form-control">
|
||||
{% for mode, name in cache_modes %}
|
||||
<option value="{{ mode }}" {% if mode == default_cache %}selected {% endif %}>
|
||||
{% trans name %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "NWFilter" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select name="nwfilter" class="custom-select">
|
||||
<option value="">{% trans "None" %}</option>
|
||||
{% for nwfilter in nwfilters %}
|
||||
<option value="{{ nwfilter }}">{{ nwfilter }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% if dom_caps.graphics_support == 'yes' %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Graphics" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select name="graphics" class="custom-select">
|
||||
{% for graphics in dom_caps.graphics_types %}
|
||||
<option value="{{ graphics }}" {% if default_graphics == graphics %}selected{% endif %}>{{ graphics }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Video" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select name="video" class="custom-select">
|
||||
{% if not videos %}
|
||||
<option value="vga">vga</option>
|
||||
<option value="cirrus">cirrus</option>
|
||||
{% endif %}
|
||||
{% for video in videos %}
|
||||
<option value="{{ video }}">{{ video }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Console Access" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select name="listener_addr" class="custom-select">
|
||||
{% for addr, label in listener_addr %}
|
||||
<option value="{{ addr }}" {% if addr == "0.0.0.0" %} selected {% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Console Password" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="password" class="form-control" name="console_pass" placeholder="{% trans "Console Password" %}" maxlength="64">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Guest Agent" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="checkbox" name="qemu_ga" value="true" checked>
|
||||
</div>
|
||||
</div>
|
||||
{% if virtio_support %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "VirtIO" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="checkbox" name="virtio" value="{{ virtio_support }}" checked>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-7 offset-3">
|
||||
{% if storages %}
|
||||
<button type="submit" class="btn btn-block btn-primary" name="create" formnovalidate onclick="showPleaseWaitDialog()" value="1">
|
||||
{% trans "Create" %}
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="btn btn-block btn-primary disabled">
|
||||
{% trans "Create" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="addFromTemp">
|
||||
<div>
|
||||
<form method="post" role="form" aria-label="Create instance from template form">{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Name" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" name="name" placeholder="{% trans "Name" %}" maxlength="64" required pattern="[a-zA-Z0-9\.\-_]+">
|
||||
</div>
|
||||
</div>
|
||||
{% if firmwares %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Firmware" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select class="custom-select" id="select_firmware" name="firmware">
|
||||
{% for frm in firmwares %}
|
||||
<option value="{{ frm }}" {% if frm == default_firmware %}selected{% endif %}>{{ frm }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "VCPU" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" name="vcpu" value="1" maxlength="2" required pattern="[0-9]">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "VCPU Config" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select id="vcpu_mode" name="vcpu_mode" class="custom-select">
|
||||
<option value=""> {% trans 'no-mode' %}</option>
|
||||
{% for mode in dom_caps.cpu_modes %}
|
||||
{% if mode == 'custom' %}
|
||||
<optgroup label="Custom CPU Models">
|
||||
{% for model in dom_caps.cpu_custom_models %}
|
||||
<option value="{{ model }}"> {% trans model %}</option>
|
||||
{% endfor %}
|
||||
</optgroup>
|
||||
{% else %}
|
||||
<option value="{{ mode }}" {% if mode == default_cpu_mode %}selected{% endif %}>
|
||||
{% trans mode %}
|
||||
</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "RAM" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="text" class="form-control" name="memory" value="512" maxlength="5" required pattern="[0-9]+">
|
||||
</div>
|
||||
<label class="col-sm-1 col-form-label">{% trans "MB" %}</label>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Template Disk" %}</label>
|
||||
<input id="images" name="images" type="hidden" value=""/>
|
||||
<div class="col-sm-3">
|
||||
<select class="custom-select" onchange="get_template_vols({{ compute_id }}, value);">
|
||||
{% if storages %}
|
||||
<option value disabled selected>{% trans "Select pool" %}...</option>
|
||||
{% for storage in storages %}
|
||||
<option value="{{ storage }}" >{{ storage }}</option>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<option value="">{% trans "None" %}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-sm-4">
|
||||
<select id="template" class="form-control" name="template" disabled>
|
||||
<!-- populated from javascript -->
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Storage" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select id="storage" name="storage" class="form-control" disabled>
|
||||
{% if storages %}
|
||||
{% for storage in storages %}
|
||||
<option value="{{ storage }}" >{{ storage }}</option>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<option value="">{% trans "None" %}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Network" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select name="networks" class="custom-select">
|
||||
{% for network in networks %}
|
||||
<option value="{{ network }}">{{ network }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Advanced" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<button class="btn btn-block btn-outline-light" type="button" data-toggle="collapse" data-target="#tempAdvCollapse" aria-expanded="false" aria-controls="collapseExample">
|
||||
<span class="fa fa-caret-down"></span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="collapse" id="tempAdvCollapse">
|
||||
<div class="form-group row meta-prealloc">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Disk Metadata" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="checkbox" name="meta_prealloc" title="{% trans "Metadata preallocation" %}" value="true">
|
||||
</div>
|
||||
<label class="col-lg-1 col-form-label">{% trans "Image" %}</label>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "HDD cache mode" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select id="cache_mode" name="cache_mode" class="custom-select">
|
||||
{% for mode, name in cache_modes %}
|
||||
<option value="{{ mode }}" {% if mode == default_cache %}selected{% endif %}>
|
||||
{% trans name %}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "NWFilter" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select name="nwfilter" class="custom-select">
|
||||
<option value="">{% trans "None" %}</option>
|
||||
{% for nwfilter in nwfilters %}
|
||||
<option value="{{ nwfilter }}">{{ nwfilter }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% if dom_caps.graphics_support == 'yes' %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Graphics" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select name="graphics" class="custom-select">
|
||||
{% for graphics in dom_caps.graphics_types %}
|
||||
<option value="{{ graphics }}" {% if default_graphics == graphics %}selected{% endif %}>{{ graphics }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Video" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select name="video" class="custom-select">
|
||||
{% if not videos %}
|
||||
<option value="vga">vga</option>
|
||||
<option value="cirrus">cirrus</option>
|
||||
{% endif %}
|
||||
{% for video in videos %}
|
||||
<option value="{{ video }}">{{ video }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Console Access" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<select name="listener_addr" class="custom-select">
|
||||
{% for addr, label in listener_addr %}
|
||||
<option value="{{ addr }}" {% if addr == "0.0.0.0" %} selected {% endif %}>{{ label }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Console Password" %}</label>
|
||||
<div class="col-sm-7">
|
||||
<input type="password" class="form-control" name="console_pass" placeholder="{% trans "Console Password" %}" maxlength="64">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Guest Agent" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="checkbox" name="qemu_ga" value="true" checked>
|
||||
</div>
|
||||
</div>
|
||||
{% if virtio_support %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "VirtIO" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="checkbox" name="virtio" value="{{ virtio_support }}" checked>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-7 offset-3">
|
||||
{% if storages %}
|
||||
<button type="submit" class="btn btn-block btn-primary" name="create" value="1" formnovalidate onclick="showPleaseWaitDialog()">
|
||||
{% trans "Create" %}
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="btn btn-primary disabled">
|
||||
{% trans "Create" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div><!-- /Tab panes -->
|
||||
</div>
|
||||
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
<script src="{% static "js/bootstrap-multiselect.js" %}"></script>
|
||||
<script>
|
||||
function toggleValue(string, updated_value, checked) {
|
||||
let result = '';
|
||||
if (checked) {
|
||||
result = string;
|
||||
if (result != '') result += ',';
|
||||
result += updated_value;
|
||||
} else {
|
||||
$.each(string.split(','), function (index, value) {
|
||||
if (value == updated_value) return;
|
||||
if (result != '') result += ',';
|
||||
result += value;
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
$(document).ready(function () {
|
||||
$('#image-control').multiselect({
|
||||
disableIfEmpty: true,
|
||||
enableCaseInsensitiveFiltering: true,
|
||||
maxHeight: 400,
|
||||
inheritClass: true,
|
||||
buttonWidth:function (options, select) {
|
||||
return '100%';
|
||||
},
|
||||
buttonText: function (options, select) {
|
||||
return 'Add image...';
|
||||
},
|
||||
buttonTitle: function (options, select) {
|
||||
return '';
|
||||
},
|
||||
onChange: function (element, checked) {
|
||||
let input_value = toggleValue($('#images').val(), element.val(), checked);
|
||||
$('#images').val(input_value);
|
||||
|
||||
let selected_list_html = '';
|
||||
let counter = 0;
|
||||
if (input_value != '') {
|
||||
$('#disk_list_div').removeClass('d-none');
|
||||
$.each(input_value.split(','), function (index, value) {
|
||||
var li = '<li>hdd' + counter + ' - ' +
|
||||
'<select name="device' + counter + '" class="image-format" onchange="get_disk_bus_choices({{ compute_id }},' + counter + ', value);">' +
|
||||
'{% for dev in disk_devices %}' +
|
||||
'<option value=' + '"{{ dev }}">' + '{% trans dev %}</option>' +
|
||||
'{% endfor %}' +
|
||||
'</select>' +
|
||||
'<select id="bus' + counter + '" name="bus' + counter + '" class="image-format">' +
|
||||
'{% for bus in disk_buses %}' +
|
||||
'<option value=' + '"{{ bus }}"' +
|
||||
'{% if bus == default_bus %}selected{% endif %}>' +
|
||||
'{% trans bus %}</option>' +
|
||||
'{% endfor %}' +
|
||||
'</select>' +
|
||||
' -> ' + value + ' ' +
|
||||
'<a class="btn-link float-right" onclick="javascript:$(\'#image-control\').multiselect(\'deselect\', \'' + value + '\', true)"><i class="fa fa-remove"></i></a></li>';
|
||||
selected_list_html += li;
|
||||
counter++;
|
||||
});
|
||||
}else {
|
||||
$('#disk_list_div').addClass('d-none');
|
||||
}
|
||||
$('#img-list').html(selected_list_html);
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
$('#network-control').multiselect({
|
||||
inheritClass: true,
|
||||
buttonText: function (options, select) {
|
||||
return 'Add network';
|
||||
},
|
||||
buttonTitle: function (options, select) {
|
||||
return '';
|
||||
},
|
||||
buttonWidth:function (options, select) {
|
||||
return '100%';
|
||||
},
|
||||
onChange: function (element, checked) {
|
||||
let input_value = toggleValue($('#networks').val(), element.val(), checked);
|
||||
$('#networks').val(input_value);
|
||||
let selected_list_html = '';
|
||||
let counter = 0;
|
||||
if (input_value != '') {
|
||||
$.each(input_value.split(','), function (index, value) {
|
||||
let li = '<li>eth' + counter +
|
||||
' -> ' + value + ' ' +
|
||||
'<a class="btn-link float-right" onclick="$(\'#network-control\').multiselect(\'deselect\', \'' + value + '\', true)"><i class="fa fa-remove"></i></a></li>';
|
||||
selected_list_html += li;
|
||||
counter++;
|
||||
});
|
||||
}
|
||||
$('#net-list').html(selected_list_html);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
$("id[vcpu_mode]").multiselect();
|
||||
|
||||
function get_cust_vols(compute_id, pool) {
|
||||
get_vol_url = "/computes/" + compute_id + "/storage/" + pool + "/volumes";
|
||||
$.getJSON(get_vol_url, function (data) {
|
||||
$("#image-control").find('option').remove();
|
||||
$.each(data['vols'], function(i, item) {
|
||||
$("#image-control").append('<option value=' + item +'>' + item + '</option>');
|
||||
});
|
||||
$('#image-control').multiselect('rebuild');
|
||||
});
|
||||
}
|
||||
|
||||
function get_template_vols(compute_id, pool) {
|
||||
get_vol_url = "/computes/" + compute_id + "/storage/" + pool + "/volumes";
|
||||
$.getJSON(get_vol_url, function (data) {
|
||||
$("#template").find('option').remove();
|
||||
$.each(data['vols'], function(i, item) {
|
||||
$("#template").append('<option value=' + item +'>' + item + '</option>');
|
||||
});
|
||||
});
|
||||
$("#template").removeAttr("disabled");
|
||||
$("#storage").val(pool).change();
|
||||
$("#storage").removeAttr("disabled");
|
||||
}
|
||||
|
||||
function get_disk_bus_choices(compute_id, dev_idx, disk_type){
|
||||
let arch = $('select[name="arch"]').val();
|
||||
let machine = $("select[id='machine-control']").val();
|
||||
get_diskBus_url = "/computes/" + compute_id + "/archs/" + arch + "/machines/" + machine + "/disks/" + disk_type + "/buses";
|
||||
$.getJSON(get_diskBus_url, function (data) {
|
||||
$("#bus" + dev_idx).find('option').remove();
|
||||
$.each(data['bus'], function(i, item) {
|
||||
$("#bus" + dev_idx).append('<option value=' + item +'>' + item + '</option>');
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% if request.user.is_superuser %}
|
||||
<script>
|
||||
function goto_compute() {
|
||||
let compute = {{ compute.id }}
|
||||
window.location.href = "{% url 'instances:create_instance_select_type' 1 %}".replace(1, compute);
|
||||
}
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endblock %}
|
|
@ -52,7 +52,7 @@
|
|||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans 'Bus' %}</label>
|
||||
<div class="col-sm-8">
|
||||
<select class="custom-select" name="vol_bus" {% if status != 5 %} disabled {% endif %}>
|
||||
<select class="custom-select" name="vol_bus" {% if instance.status != 5 %} disabled {% endif %}>
|
||||
{% for bus in bus_host %}
|
||||
<option value="{{ bus }}" {% if bus == disk.bus %}selected{% endif %}>{{ bus }}</option>
|
||||
{% endfor %}
|
||||
|
@ -78,7 +78,7 @@
|
|||
<label class="col-sm-4 col-form-label">{% trans 'Cache mode' %}</label>
|
||||
<div class="col-sm-8">
|
||||
<select class="custom-select" name="vol_cache">
|
||||
{% for key, val in cache_modes %}
|
||||
{% for key, val in instance.cache_modes %}
|
||||
<option value="{{ key }}" {% if key == disk.cache %}selected{% endif %}>{{ val }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
@ -88,7 +88,7 @@
|
|||
<label class="col-sm-4 col-form-label">{% trans 'IO mode' %}</label>
|
||||
<div class="col-sm-8">
|
||||
<select class="custom-select" name="vol_io_mode">
|
||||
{% for key, val in io_modes %}
|
||||
{% for key, val in instance.io_modes %}
|
||||
<option value="{{ key }}" {% if key == disk.io %}selected{% endif %}>{{ val }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
@ -98,7 +98,7 @@
|
|||
<label class="col-sm-4 col-form-label">{% trans 'Discard mode' %}</label>
|
||||
<div class="col-sm-8">
|
||||
<select class="custom-select" name="vol_discard_mode">
|
||||
{% for key, val in discard_modes %}
|
||||
{% for key, val in instance.discard_modes %}
|
||||
<option value="{{ key }}" {% if key == disk.discard %}selected{% endif %}>{{ val }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
@ -108,7 +108,7 @@
|
|||
<label class="col-sm-4 col-form-label">{% trans 'Detect zeroes' %}</label>
|
||||
<div class="col-sm-8">
|
||||
<select class="custom-select" name="vol_detect_zeroes">
|
||||
{% for key, val in detect_zeroes_modes %}
|
||||
{% for key, val in instance.detect_zeroes_modes %}
|
||||
<option value="{{ key }}" {% if key == disk.detect_zeroes %}selected{% endif %}>{{ val }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -1,61 +1,44 @@
|
|||
{% load i18n %}
|
||||
{% load icons %}
|
||||
<form action="" method="post" role="form" aria-label="Shortcut instance action form">{% csrf_token %}
|
||||
<input type="hidden" name="name" value="{{ inst }}"/>
|
||||
<input type="hidden" name="compute_id" value="{{ host.0 }}"/>
|
||||
{% if vm.status == 5 %}
|
||||
{% if vm.is_template %}
|
||||
<button class="btn btn-sm btn-secondary" type="button" name="clone" title="{% trans "Clone" %}" onclick="goto_instance_clone({{ host.0 }}, '{{ inst }}');">
|
||||
<span class="fa fa-clone"></span>
|
||||
</button>
|
||||
{% if instance.proxy.instance.info.0 == 5 %}
|
||||
{% if instance.is_template %}
|
||||
<a href="{% url 'instances:instance' instance.id %}#clone" class="btn btn-sm btn-secondary" title="{% trans "Clone" %}">
|
||||
{% icon 'clone' %}
|
||||
</a>
|
||||
{% else %}
|
||||
<button class="btn btn-sm btn-secondary" type="submit" name="poweron" title="{% trans "Power On" %}">
|
||||
<span class="fa fa-play"></span>
|
||||
</button>
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'instances:poweron' instance.id %}" title="{% trans "Power On" %}">
|
||||
{% icon 'play' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<button class="btn btn-sm btn-secondary disabled" title="{% trans "Suspend" %}" disabled>
|
||||
<span class="fa fa-pause"></span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary disabled" title="{% trans "Power Off" %}" disabled>
|
||||
<span class="fa fa-power-off"></span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary disabled" title="{% trans "Power Cycle" %}" disabled>
|
||||
<span class="fa fa-refresh"></span>
|
||||
</button>
|
||||
<a class="btn btn-sm btn-secondary disabled" title="{% trans "Suspend" %}">{% icon 'pause' %}</a>
|
||||
<a class="btn btn-sm btn-secondary disabled" title="{% trans "Power Off" %}">{% icon 'power-off' %}</a>
|
||||
<a class="btn btn-sm btn-secondary disabled" title="{% trans "Power Cycle" %}">{% icon 'refresh' %}</a>
|
||||
<button class="btn btn-sm btn-secondary disabled" title="{% trans "VNC Console" %}" disabled>
|
||||
<span class="fa fa-eye"></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if vm.status == 3 %}
|
||||
<button class="btn btn-sm btn-secondary" type="submit" name="resume" title="{% trans "Resume" %}">
|
||||
<span class="fa fa-play"></span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary disabled" title="{% trans "Suspend" %}" disabled>
|
||||
<span class="fa fa-pause"></span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary" type="submit" name="powerforce" title="{% trans "Force Off" %}" onclick="return confirm('Are you sure to force it down?')">
|
||||
<span class="fa fa-power-off"></span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary disabled" title="{% trans "Power Cycle" %}" disabled>
|
||||
<span class="fa fa-refresh"></span>
|
||||
</button>
|
||||
{% if instance.proxy.instance.info.0 == 3 %}
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'instances:resume' instance.id %}" title="{% trans "Resume" %}">
|
||||
{% icon 'play' %}
|
||||
</a>
|
||||
<a class="btn btn-sm btn-secondary disabled" title="{% trans "Suspend" %}">{% icon 'pause' %}</a>
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'instances:force_off' instance.id %}" title="{% trans "Force Off" %}">
|
||||
{% icon 'power-off' %}
|
||||
</a>
|
||||
<a class="btn btn-sm btn-secondary disabled" title="{% trans "Power Cycle" %}">{% icon 'refresh' %}</a>
|
||||
<button class="btn btn-sm btn-secondary disabled" title="{% trans "VNC Console" %}" disabled>
|
||||
<span class="fa fa-eye"></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
{% if vm.status == 1 %}
|
||||
<button class="btn btn-sm btn-secondary disabled" title="{% trans "Power On" %}" disabled>
|
||||
<span class="fa fa-play"></span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary" type="submit" name="suspend" title="{% trans "Suspend" %}">
|
||||
<span class="fa fa-pause"></span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary" type="submit" name="poweroff" title="{% trans "Power Off" %}" onclick="return confirm('Are you sure?')">
|
||||
<span class="fa fa-power-off"></span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary" type="submit" name="powercycle" title="{% trans "Power Cycle" %}" onclick="return confirm('Are you sure?')">
|
||||
<span class="fa fa-refresh"></span>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-secondary" type="button" onclick='open_console("{{ host.0 }}-{{ vm.uuid }}")' title="{% trans "Console" %}">
|
||||
{% if instance.proxy.instance.info.0 == 1 %}
|
||||
<a class="btn btn-sm btn-secondary disabled" title="{% trans "Power On" %}">{% icon 'play' %}</a>
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'instances:suspend' instance.id %}"
|
||||
title="{% trans "Suspend" %}">{% icon 'pause' %}</a>
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'instances:poweroff' instance.id %}">{% icon 'power-off' %}</a>
|
||||
<a class="btn btn-sm btn-secondary" href="{% url 'instances:powercycle' instance.id %}">{% icon 'refresh' %}</a>
|
||||
<button class="btn btn-sm btn-secondary" type="button" onclick='open_console("{{ instance.compute.id }}-{{ instance.get_uuid }}")'
|
||||
title="{% trans "Console" %}">
|
||||
<span class="fa fa-eye"></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
|
|
|
@ -1,135 +0,0 @@
|
|||
{% extends "base.html" %}
|
||||
{% load i18n %}
|
||||
{% load staticfiles %}
|
||||
{% block title %}{% trans "Instances" %} - {{ compute.name }}{% endblock %}
|
||||
{% block style %}
|
||||
<link rel="stylesheet" href="{% static "css/sortable-theme-bootstrap.css" %}" />
|
||||
{% endblock %}
|
||||
{% block content %}
|
||||
<!-- Page Heading -->
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
{% if request.user.is_superuser %}
|
||||
<a href="{% url 'create_instance_select_type' compute.id %}" type="button" class="btn btn-success btn-header float-right">
|
||||
<span class="fa fa-plus" aria-hidden="true"></span>
|
||||
</a>
|
||||
{% endif %}
|
||||
{% if all_host_vms or all_user_vms %}
|
||||
<div class="float-right search">
|
||||
<input id="filter" class="form-control" type="text" placeholder="{% trans 'Search' %}">
|
||||
</div>
|
||||
{% endif %}
|
||||
<h2 class="page-header">{{ compute.name }}</h2>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<nav aria-label="breadcrumb">
|
||||
<ol class="breadcrumb bg-light shadow-sm">
|
||||
<li class="breadcrumb-item active">
|
||||
<a href="{% url 'overview' compute.id %}"><i class="fa fa-dashboard"></i> {% trans "Overview" %}</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item">
|
||||
<span class="font-weight-bold"><i class="fa fa-server"></i> {% trans "Instances" %}</span>
|
||||
</li>
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'storages' compute.id %}"><i class="fa fa-hdd-o"></i> {% trans "Storages" %}</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'networks' compute.id %}"><i class="fa fa-sitemap"></i> {% trans "Networks" %}</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'interfaces' compute.id %}"><i class="fa fa-wifi"></i> {% trans "Interfaces" %}</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'nwfilters' compute.id %}"><i class="fa fa-filter"></i> {% trans "NWFilters" %}</a>
|
||||
</li>
|
||||
<li class="breadcrumb-item">
|
||||
<a href="{% url 'secrets' compute.id %}"><i class="fa fa-key"></i> {% trans "Secrets" %}</a>
|
||||
</li>
|
||||
</ol>
|
||||
</nav>
|
||||
</div>
|
||||
</div>
|
||||
<!-- /.row -->
|
||||
{% include 'errors_block.html' %}
|
||||
{% include 'messages_block.html' %}
|
||||
<div class="row">
|
||||
{% if not all_host_vms %}
|
||||
<div class="col-lg-12">
|
||||
<div class="alert alert-warning alert-dismissable fade show">
|
||||
<i class="fa fa-exclamation-triangle"></i> <strong>{% trans "Warning" %}:</strong> {% trans "Hypervisor doesn't have any Instances" %}
|
||||
<button type="button" class="close" data-dismiss="alert" aria-hidden="true">×</button>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-lg-12">
|
||||
<table class="table table-hover sortable-theme-bootstrap" data-sortable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans 'Name' %}<br>{% trans 'Description' %}</th>
|
||||
<th scope="col" class="d-none d-md-table-cell">{% trans 'User' %}</th>
|
||||
<th scope="col">{% trans 'Status' %}</th>
|
||||
<th scope="col">{% trans 'VCPU' %}</th>
|
||||
<th scope="col">{% trans 'Memory' %}</th>
|
||||
<th scope="col" style="width:200px;" data-sortable="false">{% trans 'Actions' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="searchable">
|
||||
{% for host, insts in all_host_vms.items %}
|
||||
{% for inst, vm in insts.items %}
|
||||
<tr>
|
||||
<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 %}
|
||||
{% if vm.status == 5 %}<span class="text-danger">{% trans "Off" %}</span>{% endif %}
|
||||
{% if vm.status == 3 %}<span class="text-warning">{% trans "Suspend" %}</span>{% endif %}
|
||||
</td>
|
||||
<td>{{ vm.vcpu }}</td>
|
||||
<td>{{ vm.memory|filesizeformat }}</td>
|
||||
<td class="text-nowrap">
|
||||
{% include 'instance_actions.html' %}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
{% block script %}
|
||||
<script src="{% static "js/sortable.min.js" %}"></script>
|
||||
<script>
|
||||
function open_console(uuid) {
|
||||
window.open("{% url 'console' %}?token=" + uuid, "", "width=850,height=685");
|
||||
}
|
||||
</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 () {
|
||||
instances_filter_cookie = Cookies.get("instances_filter");
|
||||
if (instances_filter_cookie) {
|
||||
$('#filter').val(instances_filter_cookie);
|
||||
$('#filter').each(filter_table);
|
||||
}
|
||||
(function ($) {
|
||||
$('#filter').keyup(filter_table)
|
||||
}(jQuery));
|
||||
});
|
||||
</script>
|
||||
<script>
|
||||
function goto_instance_clone(compute, instance) {
|
||||
window.location = "/instances/" + compute + "/" + instance + "/#clone";
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
114
instances/templates/instances/access_tab.html
Normal file
114
instances/templates/instances/access_tab.html
Normal file
|
@ -0,0 +1,114 @@
|
|||
{% load i18n %}
|
||||
<div role="tabpanel" class="tab-pane" id="access" aria-label="Instance access options">
|
||||
<div role="tabpanel">
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary active" href="#vnconsole" aria-controls="vnconsole" role="tab" data-toggle="tab">
|
||||
{% trans "Console" %}
|
||||
</a>
|
||||
</li>
|
||||
{% if app_settings.SHOW_ACCESS_ROOT_PASSWORD == 'True' %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#rootpasswd" aria-controls="rootpasswd" role="tab" data-toggle="tab">
|
||||
{% trans "Root Password" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if app_settings.SHOW_ACCESS_SSH_KEYS == 'True' %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#sshkeys" aria-controls="sshkeys" role="tab" data-toggle="tab">
|
||||
{% trans "SSH Keys" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if instance.status == 1 %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#vdiconsole" aria-controls="vdiconsole" role="tab" data-toggle="tab">
|
||||
{% trans "VDI" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="vnconsole">
|
||||
<p>{% trans "This action opens a new window with a VNC connection to the console of the instance." %}</p>
|
||||
{% if instance.status == 1 %}
|
||||
<!-- Split button -->
|
||||
<div class="btn-group float-right">
|
||||
<button type="button" id="consoleBtnGroup" class="btn btn-lg btn-success" onclick="open_console('lite')">{% trans 'Console' %}</button>
|
||||
<button type="button" class="btn btn-success dropdown-toggle dropdown-toggle-split" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">
|
||||
<span class="sr-only">{% trans 'Toggle Dropdown' %}</span>
|
||||
</button>
|
||||
<div class="dropdown-menu">
|
||||
<a class="dropdown-item" href="#" title="{% trans "Console port" %}: {{ instance.console_port }}" onclick="open_console('lite')">{% trans "Console" %} - {% trans "Lite" %}</a>
|
||||
<a class="dropdown-item" href="#" title="{% trans "Console port" %}: {{ instance.console_port }}" onclick="open_console('full')">{% trans "Console" %} - {% trans "Full" %}</a>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<button class="btn btn-lg btn-success float-right disabled">{% trans "Console" %}</button>
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% if app_settings.SHOW_ACCESS_SSH_KEYS == 'True' %}
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="rootpasswd">
|
||||
<p>{% trans "You need shut down your instance and enter a new root password." %}</p>
|
||||
<form action="{% url 'instances:rootpasswd' instance.id %}" class="form-inline" method="post" role="form" aria-label="Add root password to instance form">
|
||||
{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<input type="text" class="form-control-lg" name="passwd" placeholder="{% trans "Enter Password" %}" maxlength="24">
|
||||
</div>
|
||||
</div>
|
||||
{% if instance.status == 5 %}
|
||||
<input type="submit" class="btn btn-lg btn-success float-right" name="rootpasswd" value="{% trans "Reset Root Password" %}">
|
||||
{% else %}
|
||||
<button class="btn btn-lg btn-success float-right disabled">{% trans "Reset Root Password" %}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if app_settings.SHOW_ACCESS_SSH_KEYS == 'True' %}
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="sshkeys">
|
||||
<p>{% trans "You need shut down your instance and choose your public key." %}</p>
|
||||
<form action="{% url 'instances:add_public_key' instance.id %}" class="form-inline" method="post" role="form" aria-label="Add public key to instance form">
|
||||
{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<select name="sshkeyid" class="form-control-lg keyselect">
|
||||
{% if publickeys %}
|
||||
{% for key in publickeys %}
|
||||
<option value="{{ key.id }}">{{ key.keyname }}</option>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<option value="None">{% trans "None" %}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
{% if instance.status == 5 %}
|
||||
<input type="submit" class="btn btn-lg btn-success float-right" name="addpublickey" value="{% trans "Add Public Key" %}">
|
||||
{% else %}
|
||||
<button class="btn btn-lg btn-success float-right disabled">{% trans "Add Public Key" %}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if instance.status == 1 %}
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="vdiconsole">
|
||||
<p>{% trans "This action opens a remote viewer with a connection to the console of the instance." %}</p>
|
||||
<div class="input-group">
|
||||
<input type="text" class="input-lg disabled form-control" disabled id="vdi_url_input"/>
|
||||
<span class="input-group-append">
|
||||
<a href="#" class="btn btn-success" id="vdi_url" >{% trans "VDI" %}</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
49
instances/templates/instances/destroy_instance_form.html
Normal file
49
instances/templates/instances/destroy_instance_form.html
Normal file
|
@ -0,0 +1,49 @@
|
|||
{% extends 'base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}
|
||||
{% trans "Confirm Destroy" %}
|
||||
{% endblock title %}
|
||||
|
||||
{% block page_header %}
|
||||
{% trans "Destroy instance" %} {{ instance }}
|
||||
{% endblock page_header %}
|
||||
|
||||
{% block content %}
|
||||
{% if request.user.is_superuser or userinstance.is_delete %}
|
||||
{% if instance.status == 3 %}
|
||||
<div class="alert alert-danger">
|
||||
{% trans "Instance is suspended, cannot destroy!" %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="alert alert-danger">
|
||||
{% trans "This action is irreversible!" %}
|
||||
</div>
|
||||
<form action="{% url 'instances:destroy' instance.id %}" class="form" method="post" role="form" id="delete_form">
|
||||
{% csrf_token %}
|
||||
<div class="ml-3 form-row">
|
||||
<div class="custom-control custom-switch">
|
||||
<input class="custom-control-input" type="checkbox" name="delete_disk" value="true" checked id="delete_disk">
|
||||
<label class="custom-control-label font-weight-bold" for="delete_disk">{% trans "Remove Instance's data" %}</label>
|
||||
</div>
|
||||
</div>
|
||||
{% if instance.nvram %}
|
||||
<div class="ml-3 form-row">
|
||||
<div class="custom-control custom-switch">
|
||||
<input class="custom-control-input" type="checkbox" name="delete_nvram" value="true" id="delete_nvram" checked>
|
||||
<label class="custom-control-label font-weight-bold" for="delete_nvram">
|
||||
{% trans "Remove Instance's NVRAM" %}
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<button type="submit" class="btn btn-lg btn-success float-right" name="delete">
|
||||
{% trans "Destroy" %}
|
||||
</button>
|
||||
</form>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<div class="alert alert-danger">
|
||||
{% trans "You cannot destroy instance!" %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock content %}
|
28
instances/templates/instances/destroy_tab.html
Normal file
28
instances/templates/instances/destroy_tab.html
Normal file
|
@ -0,0 +1,28 @@
|
|||
{% load i18n %}
|
||||
<div role="tabpanel" class="tab-pane" id="undefine">
|
||||
<div role="tabpanel">
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist" aria-label="Instance destroy menu">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#destroy" aria-controls="destroy" role="tab" data-toggle="tab">
|
||||
{% trans "Destroy Instance" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="destroy">
|
||||
{% if request.user.is_superuser or userinstance.is_delete %}
|
||||
{% if instance.status == 3 %}
|
||||
<a class="btn btn-lg btn-success disabled float-right">{% trans "Destroy" %}</a>
|
||||
{% else %}
|
||||
<a href="{% url 'instances:destroy' instance.id %}" class="btn btn-lg btn-success float-right">{% trans "Destroy" %}</a>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<button class="btn btn-lg btn-success disabled float-right" name="delete">{% trans "Destroy" %}</button>
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
131
instances/templates/instances/power_tab.html
Normal file
131
instances/templates/instances/power_tab.html
Normal file
|
@ -0,0 +1,131 @@
|
|||
{% load i18n %}
|
||||
<div role="tabpanel" class="tab-pane active" id="power">
|
||||
<div role="tabpanel">
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist" aria-label="Instance power actions">
|
||||
{% if instance.status == 1 %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary active" href="#poweroff" aria-controls="poweroff" role="tab" data-toggle="tab">
|
||||
{% trans "Power Off" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#powercycle" aria-controls="powercycle" role="tab" data-toggle="tab">
|
||||
{% trans "Power Cycle" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#powerforce" aria-controls="powerforce" role="tab" data-toggle="tab">
|
||||
{% trans "Force Off" %}
|
||||
</a>
|
||||
</li>
|
||||
{% if request.user.is_superuser %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#suspend" aria-controls="suspend" role="tab" data-toggle="tab">
|
||||
{% trans "Suspend" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if instance.status == 3 %}
|
||||
{% if request.user.is_superuser %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#resume" aria-controls="resume" role="tab" data-toggle="tab">
|
||||
{% trans "Resume" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#powerforce" aria-controls="powerforce" role="tab" data-toggle="tab">
|
||||
{% trans "Force Off" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if instance.status == 5 %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary active" href="#boot" aria-controls="boot" role="tab" data-toggle="tab">
|
||||
{% trans "Power On" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
{% if instance.status == 1 %}
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="poweroff">
|
||||
<p>{% trans "This action sends an ACPI shutdown signal to the instance." %}</p>
|
||||
<form action="{% url 'instances:poweroff' instance.id %}" method="post" role="form" aria-label0="Power off instance form">
|
||||
{% csrf_token %}
|
||||
<input type="submit" name="poweroff" class="btn btn-lg btn-success float-right" value="{% trans "Power Off" %}">
|
||||
<div class="clearfix"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="powercycle">
|
||||
<p>{% trans "This action forcibly powers off and start the instance and may cause data corruption." %}</p>
|
||||
<form action="{% url 'instances:powercycle' instance.id %}" method="post" role="form" aria-label="Power cycle instance form">{% csrf_token %}
|
||||
<input type="submit" name="powercycle" class="btn btn-lg btn-success float-right" value="{% trans "Power Cycle" %}">
|
||||
<div class="clearfix"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="powerforce">
|
||||
<p>{% trans "This action forcibly powers off the instance and may cause data corruption." %}</p>
|
||||
<form action="{% url 'instances:force_off' instance.id %}" method="post" role="form" aria-label="Force to shotdown instance form">
|
||||
{% csrf_token %}
|
||||
<input type="submit" name="powerforce" class="btn btn-lg btn-success float-right" value="{% trans "Force Off" %}">
|
||||
<div class="clearfix"></div>
|
||||
</form>
|
||||
</div>
|
||||
{% if request.user.is_superuser %}
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="suspend">
|
||||
<p>{% trans "This action suspends the instance." %}</p>
|
||||
<form action="{% url 'instances:suspend' instance.id %}" method="post" role="form" aria-label="Suspend instance form">{% csrf_token %}
|
||||
<input type="submit" name="suspend" class="btn btn-lg btn-success float-right" value="{% trans "Suspend" %}">
|
||||
<div class="clearfix"></div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if instance.status == 3 %}
|
||||
{% if request.user.is_superuser %}
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="resume">
|
||||
<p>{% trans "This action restore the instance after suspend." %}</p>
|
||||
<form action="{% url 'instances:resume' instance.id %}" method="post" role="form" aria-label="Resume instance from suspension form">{% csrf_token %}
|
||||
<input type="submit" name="resume" class="btn btn-lg btn-success float-right" value="{% trans "Resume" %}">
|
||||
<div class="clearfix"></div>
|
||||
</form>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="powerforce">
|
||||
<p>{% trans "This action forcibly powers off the instance and may cause data corruption." %}</p>
|
||||
<form action="{% url 'instances:force_off' instance.id %}" method="post" role="form" aria-label="Force to shutdown form">{% csrf_token %}
|
||||
<input type="submit" name="powerforce" class="btn btn-lg btn-success float-right" value="{% trans "Force Off" %}">
|
||||
<div class="clearfix"></div>
|
||||
</form>
|
||||
</div>
|
||||
{% else %}
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="resume">
|
||||
<p>{% trans "Administrator blocked your instance." %}</p>
|
||||
<form action="{% url 'instances:resume' instance.id %}" method="post" role="form" aria-label="Resume instance form">{% csrf_token %}
|
||||
<button class="btn btn-lg btn-success disabled float-right">{% trans "Resume" %}</button>
|
||||
<div class="clearfix"></div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
{% if instance.status == 5 %}
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="boot">
|
||||
<p>{% trans "Click on Power On button to start this instance." %}</p>
|
||||
<form action="{% url 'instances:poweron' instance.id %}" method="post" role="form" aria-label="Start instance form">
|
||||
{% csrf_token %}
|
||||
{% if instance.is_template %}
|
||||
<p>{% trans "Template instance cannot be started." %}</p>
|
||||
<input type="submit" name="poweron" class="btn btn-lg btn-success float-right disabled" value="{% trans "Power On" %}">
|
||||
{% else %}
|
||||
<input type="submit" name="poweron" class="btn btn-lg btn-success float-right" value="{% trans "Power On" %}">
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
</form>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
164
instances/templates/instances/resize_tab.html
Normal file
164
instances/templates/instances/resize_tab.html
Normal file
|
@ -0,0 +1,164 @@
|
|||
{% load i18n %}
|
||||
<div role="tabpanel" class="tab-pane" id="resize">
|
||||
<div role="tabpanel">
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist" aria-label="Instance resize options">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary active" href="#resizevm_cpu" aria-controls="resizevm_cpu" role="tab" data-toggle="tab">
|
||||
{% trans "CPU" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#resizevm_mem" aria-controls="resizevm_mem" role="tab" data-toggle="tab">
|
||||
{% trans "Memory" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#resizevm_disk" aria-controls="resizevm_disk" role="tab" data-toggle="tab">
|
||||
{% trans "Disk" %}
|
||||
</a>
|
||||
</li>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="resizevm_cpu">
|
||||
{% if request.user.is_superuser or request.user.is_staff or userinstance.is_change %}
|
||||
{% if instance.status == 5 or not instance.vcpus %}
|
||||
<form action="{% url 'instances:resizevm_cpu' instance.id %}" method="post" role="form" aria-label="Resize instance cpu form">{% csrf_token %}
|
||||
<p class="font-weight-bold">{% trans "Logical host CPUs" %} : {{ vcpu_host }}</p>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label"> {% trans "Current Allocation" %}</label>
|
||||
<div class="col-sm-4">
|
||||
<select name="cur_vcpu" class="custom-select">
|
||||
{% for cpu in instance.vcpu_range %}
|
||||
{% if instance.cur_vcpu %}
|
||||
<option value="{{ cpu }}" {% if cpu == instance.cur_vcpu %}selected{% endif %}>{{ cpu }}</option>
|
||||
{% else %}
|
||||
<option value="{{ cpu }}" {% if cpu == instance.vcpu %}selected{% endif %}>{{ cpu }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Maximum Allocation" %}</label>
|
||||
<div class="col-sm-4">
|
||||
<select name="vcpu" class="custom-select">
|
||||
{% for cpu in instance.vcpu_range %}
|
||||
<option value="{{ cpu }}" {% if cpu == instance.vcpu %}selected{% endif %}>{{ cpu }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if instance.status == 5 %}
|
||||
<button type="submit" class="btn btn-lg btn-success float-right" name="resizevm_cpu">{% trans "Resize" %}</button>
|
||||
{% else %}
|
||||
<button class="btn btn-lg btn-success float-right disabled">{% trans "Resize" %}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
{% else %}
|
||||
<p class="font-weight-bold">{% trans "Logical Instance Active/Maximum CPUs" %} : {{ instance.cur_vcpu }} / {{ instance.vcpu }} </p>
|
||||
<div class="col-sm-3"></div>
|
||||
<div class="col-sm-6">
|
||||
{% for id, vcpu in instance.vcpus.items %}
|
||||
<form action="{% url 'instances:set_vcpu' instance.id %}" method="post" role="form" aria-label="Resize instance cpu form">{% csrf_token %}
|
||||
<div class="col-sm-3">
|
||||
<input name="id" value="{{ id }}" hidden/>
|
||||
{% if vcpu.enabled == 'yes' and vcpu.hotpluggable == "yes" %}
|
||||
<button type="submit" class="btn btn-block btn-success" value="False" name="set_vcpu" title="{% trans "Disable" %}">{{ id }}</button>
|
||||
{% elif vcpu.enabled == 'yes' and vcpu.hotpluggable == "no" %}
|
||||
<button type="button" class="btn btn btn-block btn-info" title="{% trans "Constant" %}">{{ id }}</button>
|
||||
{% else %}
|
||||
<button type="submit" class="btn btn btn-block btn-secondary" value="True" name="set_vcpu" title="{% trans "Enable" %}">{{ id }}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div class="col-sm-3"></div>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% trans "You don't have permission for resizing instance" %}
|
||||
<button class="btn btn-lg btn-success float-right disabled">{% trans "Resize" %}</button>
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="resizevm_mem">
|
||||
{% if request.user.is_superuser or request.user.is_staff or userinstance.is_change %}
|
||||
<form action="{% url 'instances:resize_memory' instance.id %}" method="post" role="form" aria-label="Resize instance memory form">
|
||||
{% csrf_token %}
|
||||
<p class="font-weight-bold">{% trans "Total host memory" %}: {{ memory_host|filesizeformat }}</p>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Current Allocation" %} ({% trans "MB" %})</label>
|
||||
<div class="col-sm-4 js-custom__container">
|
||||
<select name="cur_memory" class="custom-select js-custom__toggle">
|
||||
{% for mem in memory_range %}
|
||||
<option value="{{ mem }}" {% if mem == instance.cur_memory %}selected{% endif %}>{{ mem }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="text" name="cur_memory_custom" class="custom-select js-custom__toggle" style="display: none" />
|
||||
<small><input type="checkbox" class="js-custom__checkbox" /> {% trans "Custom value" %}</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">
|
||||
{% trans "Maximum Allocation" %} ({% trans "MB" %})
|
||||
</label>
|
||||
<div class="col-sm-4 js-custom__container">
|
||||
<select name="memory" class="form-control js-custom__toggle">
|
||||
{% for mem in memory_range %}
|
||||
<option value="{{ mem }}"
|
||||
{% if mem == instance.memory %}selected{% endif %}>{{ mem }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<input type="text" name="memory_custom" class="form-control js-custom__toggle" style="display: none" />
|
||||
<small><input type="checkbox" class="js-custom__checkbox" /> {% trans "Custom value" %}</small>
|
||||
</div>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-lg btn-success float-right" name="resizevm_mem">{% trans "Resize" %}</button>
|
||||
</form>
|
||||
{% else %}
|
||||
{% trans "You don't have permission for resizing instance" %}
|
||||
<button class="btn btn-lg btn-success float-right disabled">{% trans "Resize" %}</button>
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="resizevm_disk">
|
||||
{% if request.user.is_superuser or request.user.is_staff or userinstance.is_change %}
|
||||
<form action="{% url 'instances:resize_disk' instance.id %}" method="post" role="form" aria-label="Resize instance disk form">
|
||||
{% csrf_token %}
|
||||
<p class="font-weight-bold">{% trans "Disk allocation (GB)" %}:</p>
|
||||
{% for disk in instance.disks %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-4 col-form-label">{% trans "Current Allocation" %} ({{ disk.dev }})</label>
|
||||
{% if disk.storage is None %}
|
||||
<div class="col-sm-4 js-custom__container">
|
||||
<div class="alert alert-danger">
|
||||
{% trans "Error getting disk info" %}
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="col-sm-4 js-custom__container">
|
||||
<input type="number" name="disk_size_{{ disk.dev }}" class="form-control" value="{% widthratio disk.size 1073741824 1 %}" />
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% if instance.status == 5 %}
|
||||
<button type="submit" class="btn btn-lg btn-success float-right" name="resizevm_disk">{% trans "Resize" %}</button>
|
||||
{% else %}
|
||||
<button class="btn btn-lg btn-success float-right disabled">{% trans "Resize" %}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
{% else %}
|
||||
{% trans "You don't have permission for resizing instance" %}
|
||||
<button class="btn btn-lg btn-success float-right disabled">{% trans "Resize" %}</button>
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
904
instances/templates/instances/settings_tab.html
Normal file
904
instances/templates/instances/settings_tab.html
Normal file
|
@ -0,0 +1,904 @@
|
|||
{% load i18n %}
|
||||
{% load bootstrap4 %}
|
||||
{% load icons %}
|
||||
<div role="tabpanel" class="tab-pane" id="settings">
|
||||
<div role="tabpanel">
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist" aria-label="Instance settings">
|
||||
{% if request.user.is_superuser %}
|
||||
<li class="nav-item ">
|
||||
<a class="nav-link text-secondary active" href="#boot_opt" aria-controls="boot" role="tab" data-toggle="tab">
|
||||
{% trans "Boot" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#disks" aria-controls="disks" role="tab" data-toggle="tab">
|
||||
{% trans "Disk" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if request.user.is_superuser or request.user.is_staff or userinstance.is_vnc %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#vncsettings" aria-controls="vncsettings" role="tab" data-toggle="tab">
|
||||
{% trans "Console" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if request.user.is_superuser %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#network" aria-controls="network" role="tab" data-toggle="tab">
|
||||
{% trans "Network" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.instances.clone_instances %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#clone" aria-controls="clone" role="tab" data-toggle="tab">
|
||||
{% trans "Clone" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if request.user.is_superuser %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#migrate" aria-controls="migrate" role="tab" data-toggle="tab">
|
||||
{% trans "Migrate" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#xmledit" aria-controls="xmledit" role="tab" data-toggle="tab">
|
||||
{% trans "XML" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if perms.instances.clone_instances %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#options" aria-controls="options" role="tab" data-toggle="tab">
|
||||
{% trans "Options" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
{% if request.user.is_superuser %}
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#users" aria-controls="users" role="tab" data-toggle="tab">
|
||||
{% trans "Users" %}
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
{% if request.user.is_superuser %}
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="boot_opt">
|
||||
<p class="font-weight-bold">{% trans 'Autostart' %}</p>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12 text-center">
|
||||
<p>{% trans "Autostart your instance when host server is power on " %}
|
||||
{% if instance.autostart == 0 %}
|
||||
<a class="btn btn-success" href="{% url 'instances:set_autostart' instance.id %}">{% trans "Enable" %}</a>
|
||||
{% else %}
|
||||
<a class="btn btn-danger" href="{% url 'instances:unset_autostart' instance.id %}">{% trans "Disable" %}</a>
|
||||
{% endif %}
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<p class="font-weight-bold">{% trans 'Boot Order' %}</p>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12 text-center">
|
||||
{% if instance.status == 5 %}
|
||||
<p>{% trans "Enable Boot Menu for your instance when it starts up " %}
|
||||
{% if instance.bootmenu == 0 %}
|
||||
<form action="{% url 'instances:set_bootmenu' instance.id %}" method="post" role="form" aria-label="Enable/disable instance boot order form">{% csrf_token %}
|
||||
<input type="submit" class="btn btn-success" name="set_bootmenu" title="{% trans 'Show boot menu' %}" value="{% trans "Enable" %}">
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="{% url 'instances:unset_bootmenu' instance.id %}" method="post" role="form" aria-label="Enable/disable instance boot order form">{% csrf_token %}
|
||||
<input type="submit" class="btn btn-danger" name="unset_bootmenu" title="{% trans 'Hide boot menu' %}" value="{% trans "Disable" %}">
|
||||
</form>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{% if instance.bootmenu == 0 %}
|
||||
<p>**** {% trans "Please shutdown instance to modify boot menu" %} ****</p>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if instance.bootmenu == 1 %}
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="col-sm-6 bg-light rounded shadow-sm">
|
||||
{% for idx, val in instance.boot_order.items %}
|
||||
<label>{{ idx|add:1 }}:{{ val.target }}, </label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
<form action="{% url 'instances:set_bootorder' instance.id %}" method="post" role="form" aria-label="Boot order edit form">{% csrf_token %}
|
||||
<input id="bootorder" name="bootorder" hidden>
|
||||
<div class="d-flex justify-content-center">
|
||||
<div id="b_order" class="multipleselect border-0">
|
||||
{% for disk in instance.disks %}
|
||||
<label><input type="checkbox" name="disk:{{ disk.dev }}" value="disk:{{ disk.dev }}" onclick="set_orderlist($('#bootorder'))" />{{ disk.dev }} - {{ disk.image }}</label>
|
||||
{% endfor %}
|
||||
{% for cd in instance.media %}
|
||||
<label><input type="checkbox" name="cdrom:{{ cd.dev }}" value="cdrom:{{ cd.dev }}" onclick="set_orderlist($('#bootorder'))"/>{{ cd.dev }} - {{ cd.image }}</label>
|
||||
{% endfor %}
|
||||
{% for net in instance.networks %}
|
||||
<label><input type="checkbox" name="network:{{ net.mac }}" value="network:{{ net.mac }}" onclick="set_orderlist($('#bootorder'))"/>NIC - {{ net.mac|slice:"9:" }}</label>
|
||||
{% endfor %}
|
||||
</div>
|
||||
<div>
|
||||
<div class="row mt-4">
|
||||
<a href="#" id="boot_order_up" class="btn btn-light shadow-sm"><span class="fa fa-arrow-up" title="{% trans 'up: move selected devices' %}"></span></a>
|
||||
</div>
|
||||
<div class="row mt-2">
|
||||
<a href="#" id="boot_order_down" class="btn btn-light shadow-sm"><span class="fa fa-arrow-down" title="{% trans 'down: move selected devices' %}"></span></a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="d-flex justify-content-center">
|
||||
<div class="col-sm-6">
|
||||
<input type="submit" class="btn btn-success btn-block" name="set_bootorder" value="{% trans "Apply" %}">
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="disks">
|
||||
<form action="{% url 'instances:add_cdrom' instance.id %}" method="post" role="form" aria-label="Add CD-ROM form">{% csrf_token %}
|
||||
<p class="font-weight-bold">
|
||||
{% trans "Instance Media" %}
|
||||
<button class="btn btn-success float-right"
|
||||
type="submit" type="button"
|
||||
title="{% trans 'Add CD-ROM' %}"
|
||||
{% if instance.status != 5 %} disabled {% endif %}>
|
||||
<span class="fa fa-plus"></span>
|
||||
</button>
|
||||
</p>
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
{% for cd in instance.media %}
|
||||
<div class="row mt-2">
|
||||
<a class="col-sm-3 col-form-label"
|
||||
name="details"
|
||||
title="{% trans "Details" %}"
|
||||
tabindex="0"
|
||||
data-trigger="focus"
|
||||
data-toggle="popover"
|
||||
data-html="true"
|
||||
data-content="<strong>{% trans 'Bus' %}:</strong> {{ cd.bus }} <br/>
|
||||
<strong>{% trans 'Device' %}:</strong> {{ cd.dev }}">
|
||||
{% trans "CD-ROM" %} {{ forloop.counter }}
|
||||
</a>
|
||||
<div class="col-sm-9">
|
||||
{% if not cd.image %}
|
||||
<form action="{% url 'instances:mount_iso' instance.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="input-group">
|
||||
<select name="media" class="form-control">
|
||||
{% if instance.media_iso %}
|
||||
{% for iso in instance.media_iso %}
|
||||
<option value="{{ iso }}">{{ iso }}</option>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
<option value="none">{% trans "None" %}</option>
|
||||
{% endif %}
|
||||
</select>
|
||||
<div class="input-group-append">
|
||||
{% if instance.media_iso and allow_admin_or_not_template %}
|
||||
<button type="submit" class="btn btn-sm btn-success float-left" name="mount_iso" value="{{ cd.dev }}">{% trans "Mount" %}</button>
|
||||
{% else %}
|
||||
<button class="btn btn-sm btn-success float-left disabled">{% trans "Mount" %}</button>
|
||||
{% endif %}
|
||||
{% if instance.status == 5 and allow_admin_or_not_template %}
|
||||
<a href="{% url 'instances:detach_cdrom' instance.id cd.dev %}" class="btn btn-sm btn-danger float-right" title="{% trans "Detach CD-ROM (remove device)" %}">
|
||||
{% icon 'remove' %}
|
||||
</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% else %}
|
||||
<form action="{% url 'instances:unmount_iso' instance.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" value="{{ cd.image }}" disabled/>
|
||||
<div class="input-group-append">
|
||||
<input type="hidden" name="path" value="{{ cd.path }}">
|
||||
{% if allow_admin_or_not_template %}
|
||||
<button type="submit" class="btn btn-sm btn-success float-left" value="{{ cd.dev }}" name="umount_iso">{% trans "Unmount" %}</button>
|
||||
{% else %}
|
||||
<button class="btn btn-sm btn-success float-left disabled" value="{{ cd.dev }}" name="umount_iso">{% trans "Unmount" %}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% empty %}
|
||||
<div class="offset-3 col-sm-6">
|
||||
<div class="bg-light rounded shadow-sm">{% trans 'There is not any CD-ROM device.' %}</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="clearfix"></div>
|
||||
<p class="font-weight-bold">
|
||||
{% trans "Instance Volume" %}
|
||||
{% include 'add_instance_volume.html' %}
|
||||
</p>
|
||||
|
||||
<div class="col-12 col-sm-12">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-hover mt-3">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans "Device" %}</th>
|
||||
<th scope="col">{% trans "Used" %}</th>
|
||||
<th scope="col">{% trans "Capacity" %}</th>
|
||||
<th scope="col">{% trans "Storage" %}</th>
|
||||
<th scope="col">{% trans "Source" %}</th>
|
||||
<th scope="col">{% trans "Action" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for disk in instance.disks %}
|
||||
<tr>
|
||||
<td>
|
||||
<button type="submit" class="btn btn-sm btn-secondary"
|
||||
name="details{{ forloop.counter0 }}"
|
||||
title="{% trans "Details" %}"
|
||||
tabindex="0"
|
||||
data-trigger="focus"
|
||||
data-toggle="popover"
|
||||
data-html="true"
|
||||
data-content="<strong>Bus:</strong> {{ disk.bus }} <br/>
|
||||
<strong>Format:</strong> {{ disk.format }} <br/>
|
||||
<strong>Cache:</strong> {{ disk.cache }} <br/>
|
||||
<strong>Serial:</strong> {{ disk.serial }} <br/>
|
||||
<strong>Readonly:</strong> {{ disk.readonly }} <br/>
|
||||
<strong>Shareable:</strong> {{ disk.shareable }}</br>
|
||||
<strong>IO Mode:</strong> {{ disk.io }} <br/>
|
||||
<strong>Discard:</strong> {{ disk.discard }} <br/>
|
||||
<strong>Detect Zeroes:</strong> {{ disk.detect_zeroes }}">
|
||||
<i class="fa fa-info"></i>
|
||||
</button>
|
||||
{{ disk.dev }}
|
||||
</td>
|
||||
{% if disk.storage is None %}
|
||||
<td colspan="4">
|
||||
<div class="alert alert-danger">
|
||||
{% trans "Error getting disk info" %}
|
||||
</div>
|
||||
</td>
|
||||
{% else %}
|
||||
<td>{{ disk.used | filesizeformat}}</td>
|
||||
<td>{{ disk.size | filesizeformat }}</td>
|
||||
<td>{{ disk.storage }}</td>
|
||||
<td>{{ disk.path }}</td>
|
||||
{% endif %}
|
||||
<td class="text-nowrap">
|
||||
<form class="d-inline" action="{% url 'instances:edit_volume' instance.id %}" method="post" role="form" aria-label="Edit instance volume form">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="path" value="{{ disk.path }}">
|
||||
<input type="hidden" name="dev" value="{{ disk.dev }}">
|
||||
<input type="hidden" name="storage" value="{{ disk.storage }}">
|
||||
<input type="hidden" name="name" value="{{ disk.image }}">
|
||||
{% include 'edit_instance_volume.html' with id=forloop.counter0 %}
|
||||
</form>
|
||||
<form class="d-inline" action="{% url 'instances:detach_vol' instance.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="path" value="{{ disk.path }}">
|
||||
<input type="hidden" name="dev" value="{{ disk.dev }}">
|
||||
<input type="hidden" name="storage" value="{{ disk.storage }}">
|
||||
<input type="hidden" name="name" value="{{ disk.image }}">
|
||||
{% if instance.status == 5 %}
|
||||
<button type="submit" class="btn btn-sm btn-secondary" value="{{ disk.dev }}" title="{% trans "Detach" %}" onclick="return confirm('{% trans "Are you sure to detach volume?" %}')">
|
||||
{% icon 'eject' %}
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="btn btn-sm btn-secondary disabled" value="{{ disk.dev }}" title="{% trans "Detach" %}" onclick="return confirm('{% trans "Are you sure? This may lead data corruption!" %}')">
|
||||
{% icon 'eject' %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
<form class="d-inline" action="{% url 'instances:delete_vol' instance.id %}" method="post">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="path" value="{{ disk.path }}">
|
||||
<input type="hidden" name="dev" value="{{ disk.dev }}">
|
||||
<input type="hidden" name="storage" value="{{ disk.storage }}">
|
||||
<input type="hidden" name="name" value="{{ disk.image }}">
|
||||
{% if instance.status == 5 %}
|
||||
<button type="submit" class="btn btn-sm btn-secondary" title="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure to delete volume?" %}')">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="btn btn-sm btn-secondary disabled" title="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure? This may lead data corruption!" %}')">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="network">
|
||||
<p>
|
||||
{% trans "Add a network device" %}
|
||||
{% include 'add_instance_network_block.html' %}
|
||||
</p>
|
||||
|
||||
<div class="row mt-3">
|
||||
<div class="col-lg-12 mt-3">
|
||||
<h5 class="font-weight-bold">{% trans "Network Devices" %}</h5>
|
||||
<table class="table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans 'Name' %}</th>
|
||||
<th scope="col" class="d-none d-table-cell d-sm-table-cell" colspan="6">{% trans 'Info' %}</th>
|
||||
<th scope="colgroup" class="d-none" colspan="2">{% trans 'Info' %}</th>
|
||||
<th scope="colgroup" colspan="2">{% trans 'Actions' %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for network in instance.networks %}
|
||||
<tr>
|
||||
<td rowspan="2">eth{{ forloop.counter0 }}({{ network.target|default:"no target" }})
|
||||
<form action="{% url 'instances:set_link_state' instance.id %}" method="post" aria-label="set instance link state form">
|
||||
{% csrf_token %}
|
||||
<input name="mac" value="{{ network.mac }}" hidden/>
|
||||
<input name="set_link_state" value="{{ network.state }}" hidden/>
|
||||
<input type="checkbox" {% if network.state == 'up' %} checked{% endif %} onclick='submit();' />
|
||||
<strong>{% trans 'active' %}</strong>
|
||||
</form>
|
||||
</td>
|
||||
<th class="d-none d-table-cell d-sm-table-cell">{% trans 'MAC' %}</th>
|
||||
<td>{{ network.mac }}</td>
|
||||
<th scope="row" class="d-none d-table-cell d-sm-table-cell">{% trans 'Filter' %}</th>
|
||||
<td class="d-none d-table-cell">{{ network.filterref|default:"None" }}</td>
|
||||
<th scope="row" class="d-none d-table-cell d-sm-table-cell">{% trans 'Source' %}</th>
|
||||
<td>{{ network.nic }}</td>
|
||||
<td>
|
||||
<form action="{% url 'instances:change_network' instance.id %}" method="post" name="edit_network{{ forloop.counter0 }}" role="form">{% csrf_token %}
|
||||
<button data-target="#editInstanceNetwork{{ forloop.counter0 }}" type="button" class="btn btn-sm btn-primary"
|
||||
title="{% trans "Edit NIC" %}" data-toggle="modal">
|
||||
<span class="fa fa-edit" aria-hidden="true"></span>
|
||||
</button>
|
||||
|
||||
<div class="modal fade" id="editInstanceNetwork{{ forloop.counter0 }}" role="dialog" aria-labelledby="editInstanceNetworkLabel" aria-hidden="true">
|
||||
<div class="modal-dialog">
|
||||
<div class="modal-content">
|
||||
<div class="modal-header">
|
||||
<h5 class="modal-title">{% trans "Edit Instance Network" %}</h5>
|
||||
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">×</button>
|
||||
</div>
|
||||
<div class="modal-body">
|
||||
<div class="container">
|
||||
<div class="form-group row">
|
||||
<label class="col-form-label">{% trans "MAC" %}</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="text" value="{{ network.mac }}" readonly/>
|
||||
<input class="form-control" type="text" name="net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-form-label">{% trans "Net Source" %}</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="text" value="{{ network.nic }}" readonly/>
|
||||
<select class="form-control" name="net-source-{{ forloop.counter0 }}">
|
||||
{% for c_net in networks_host %}
|
||||
<option value="net:{{ c_net }}" {% if c_net == network.nic %} selected {% endif %}>{% trans 'Network' %} {{ c_net }}</option>
|
||||
{% endfor %}
|
||||
{% for c_iface in interfaces_host %}
|
||||
<option value="iface:{{ c_iface }}" {% if c_iface == network.nic %} selected {% endif %}>{% trans 'Interface' %} {{ c_iface }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-form-label">{% trans "NWFilter" %}</label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="text" value="{{ network.filterref }}" readonly/>
|
||||
<select class="form-control" name="net-nwfilter-{{ forloop.counter0 }}">
|
||||
<option value="">{% trans "None" %}</option>
|
||||
{% for c_filters in nwfilters_host %}
|
||||
<option value="{{ c_filters }}" {% if c_filters == network.filterref %} selected {% endif %}>{{ c_filters }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-form-label">{% trans "Model" %} </label>
|
||||
<div class="input-group">
|
||||
<input class="form-control" type="text" value="{{ network.model }}" readonly/>
|
||||
<select class="form-control" name="net-model-{{ forloop.counter0 }}">
|
||||
{% for model in net_models_host %}
|
||||
<option value="{{ model }}" {% if model == network.model %} selected {% endif %}>{{ model }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="modal-footer">
|
||||
<button class="btn btn-secondary" data-dismiss="modal">{% trans 'Close' %}</button>
|
||||
<button class="btn btn-success" name="change_network" title="{% trans "Apply network changes" %}">{% trans "Apply" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</td>
|
||||
<td align="right">
|
||||
<form action="{% url 'instances:delete_network' instance.id %}" method="post" name="delete_network" role="form">{% csrf_token %}
|
||||
<button class="btn btn-sm btn-danger" value="{{ network.mac }}" name="delete_network" title="{% trans "Delete Device" %}"
|
||||
onclick="return confirm('{% trans "Are you sure?" %}')">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th scope="row">{% trans 'IPv4' %}</th>
|
||||
<td>
|
||||
{% for ipv4 in network.ipv4|default:"unknown" %}{{ ipv4 }}{% endfor %}
|
||||
</td>
|
||||
<th scope="row">{% trans 'IPv6' %}</th>
|
||||
<td>
|
||||
{% for ipv6 in network.ipv6|default:"unknown" %}{{ ipv6 }}{% endfor %}
|
||||
</td>
|
||||
<th scope="row">{% trans 'Model' %}</th>
|
||||
<td>{{ network.model }}</td>
|
||||
<th>{% trans 'QoS' %}</th>
|
||||
<td class="d-flex justify-content-end">
|
||||
<form action="{% url 'instances:set_qos' instance.id %}" method="post" name="add_qos{{ forloop.counter0 }}" role="form" aria-label="Add network qos form">
|
||||
{% csrf_token %}
|
||||
<input type="text" name="net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}" hidden/>
|
||||
{% include 'add_network_qos.html' with id=forloop.counter0 %}
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="bg-primary" colspan="9"></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if instance.qos %}
|
||||
<div class="row mt-3">
|
||||
<div class="col-sm-10">
|
||||
<p><strong>{% trans "QoS Configuration" %}</strong></p>
|
||||
</div>
|
||||
<div class="col-12 col-sm-12">
|
||||
<table class="table table-hover">
|
||||
<thead>
|
||||
<tr class="d-flex">
|
||||
<th scope="col" class="col-2">{% trans "MAC" %}/{% trans "Direction" %}</th>
|
||||
<th scope="col" class="col-2">{% trans "Average" %}</th>
|
||||
<th scope="col" class="col-3">{% trans "Peak" %}</th>
|
||||
<th scope="col" class="col-3">{% trans "Burst" %}</th>
|
||||
<th scope="col" class="col-2">{% trans "Actions" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for q, attrs in instance.qos.items %}
|
||||
{% for att in attrs %}
|
||||
<tr class="d-flex">
|
||||
<form action="{% url 'instances:set_qos' instance.id %}" method="post" role="form" aria-label="Instance QoS configuration form">
|
||||
{% csrf_token %}
|
||||
<td class="col-2"><label class="col-form-label">{{ q }} {{ att.direction | capfirst }}</label></td>
|
||||
<td class="col-2"><input id="qos_average" class="form-control" name="qos_average"
|
||||
value="{{ att.average|default:'' }}"/>
|
||||
</td>
|
||||
<td class="col-3"><input id="qos_peak" class="form-control" name="qos_peak"
|
||||
value="{{ att.peak|default:'' }}"/>
|
||||
</td>
|
||||
<td class="col-3"><input id="qos_burst" class="form-control" name="qos_burst"
|
||||
value="{{ att.burst|default:'' }}"/>
|
||||
</td>
|
||||
<td class="col-sm-2">
|
||||
<input name="qos_direction" value="{{ att.direction }}" hidden/>
|
||||
<input name="net-mac" value="{{ q }}" hidden/>
|
||||
<button type="submit" class="btn btn-sm btn-primary"
|
||||
name="set_qos" data-toggle="modal"
|
||||
title="{% trans "Edit QoS" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
|
||||
<i class="fa fa-save"></i>
|
||||
</button>
|
||||
</form>
|
||||
<form action="{% url 'instances:unset_qos' instance.id %}" method="post" role="form" aria-label="Instance QoS configuration form">
|
||||
{% csrf_token %}
|
||||
<input name="qos_direction" value="{{ att.direction }}" hidden/>
|
||||
<input name="net-mac" value="{{ q }}" hidden/>
|
||||
<button type="submit" class="btn btn-sm btn-danger"
|
||||
name="unset_qos"
|
||||
title="{% trans "Delete QoS" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<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>
|
||||
<form action="{% url 'instances:migrate' instance.id %}" class="ml-3 form" method="post" role="form" aria-label="Migrate instance form">
|
||||
{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Original host" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<label class="form-control" readonly="readonly">{{ compute.name }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Host migration" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<select name="compute_id" class="custom-select">
|
||||
{% if computes_count != 1 %}
|
||||
{% for comp in computes %}
|
||||
{% if comp.id != compute.id %}
|
||||
<option value="{{ comp.id }}">{{ comp.name }}</option>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-6 offset-3">
|
||||
<div class="custom-control custom-switch">
|
||||
<input class="custom-control-input" type="checkbox" name="live_migrate" value="true" id="vm_live_migrate" {% if instance.status != 5 %}checked{% else %}disabled{% endif %}>
|
||||
<label class="custom-control-label" for="vm_live_migrate">{% trans "Live migration" %}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-6 offset-3">
|
||||
<div class="custom-control custom-switch">
|
||||
<input class="custom-control-input" type="checkbox" name="unsafe_migrate" value="true" id="vm_unsafe_migrate">
|
||||
<label class="custom-control-label" for="vm_unsafe_migrate">{% trans "Unsafe migration" %}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-6 offset-3">
|
||||
<div class="custom-control custom-switch">
|
||||
<input class="custom-control-input" type="checkbox" name="xml_delete" value="true" id="xml_delete" checked>
|
||||
<label class="custom-control-label" for="xml_delete">{% trans "Delete original" %}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-6 offset-3">
|
||||
<div class="custom-control custom-switch">
|
||||
<input class="custom-control-input" type="checkbox" name="offline_migrate" value="true" id="offline_migrate" {% if instance.status == 5 %}checked{% else %}disabled{% endif %}>
|
||||
<label class="custom-control-label" for="offline_migrate">{% trans "Offline migration" %}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-6 offset-3">
|
||||
<div class="custom-control custom-switch">
|
||||
<input class="custom-control-input" type="checkbox" name="postcopy" value="true" id="postcopy" {% if instance.status != 1 %}disabled{% endif %}>
|
||||
<label class="custom-control-label" for="postcopy">{% trans "Post copy" %}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-6 offset-3">
|
||||
<div class="custom-control custom-switch">
|
||||
<input class="custom-control-input" type="checkbox" name="autoconverge" value="true" id="autoconverge" {% if instance.status != 1 %}disabled{% endif %}>
|
||||
<label class="custom-control-label" for="autoconverge" title="{% trans 'Forces CPU convergence during live migration' %}">{% trans "Auto converge" %}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-6 offset-3">
|
||||
<div class="custom-control custom-switch">
|
||||
<input class="custom-control-input" type="checkbox" name="compress" value="true" id="compress" {% if instance.status != 1 %}disabled{% endif %}>
|
||||
<label class="custom-control-label" for="compress" title="{% trans 'Compress instance memory for fast migration' %}">{% trans "Compressed" %}</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if computes_count != 1 %}
|
||||
<button type="submit" class="btn btn-lg btn-success float-right" name="migrate" onclick="showPleaseWaitDialog();">{% trans "Migrate" %}</button>
|
||||
{% else %}
|
||||
<button class="btn btn-lg btn-success float-right disabled">{% trans "Migrate" %}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="xmledit">
|
||||
<p>{% trans "If you need to edit XML please Power Off the instance" %}</p>
|
||||
<form action="{% url 'instances:change_xml' instance.id %}" method="post" role="form" aria-label="Edit instance XML form">
|
||||
{% csrf_token %}
|
||||
<div class="col-sm-12" id="xmlheight">
|
||||
<textarea id="editor">{{ instance.inst_xml }}</textarea>
|
||||
</div>
|
||||
{% if instance.status == 5 %}
|
||||
<input type="hidden" name="inst_xml">
|
||||
<button type="submit" class="btn btn-lg btn-success float-right" name="change_xml">
|
||||
{% trans "Change" %}
|
||||
</button>
|
||||
{% else %}
|
||||
<button class="btn btn-lg btn-success float-right disabled">
|
||||
{% trans "Change" %}
|
||||
</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="users">
|
||||
<div>
|
||||
<p class="font-weight-bold">
|
||||
{% trans "Instance owners" %}
|
||||
{% include 'add_instance_owner_block.html' %}
|
||||
</p>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped sortable-theme-bootstrap mt-3" data-sortable>
|
||||
<tbody class="searchable">
|
||||
{% for userinstance in userinstances %}
|
||||
<tr>
|
||||
<td><a href="{% url 'account' userinstance.user.id %}">{{ userinstance.user }}</a></td>
|
||||
<td style="width:30px;">
|
||||
<a href="{% url 'user_instance_delete' userinstance.id %}?next={% url 'instances:instance' instance.id %}#users">
|
||||
{% icon 'trash' %}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if request.user.is_superuser or request.user.is_staff or userinstance.is_vnc %}
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="vncsettings">
|
||||
<form method="post" action="{% url 'instances:update_console' instance.id %}">
|
||||
{% csrf_token %}
|
||||
{% if instance.status != 5 %}
|
||||
<div class="alert alert-warning">
|
||||
{% trans "To change console settings, shutdown the instance." %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% bootstrap_form console_form layout='horizontal' %}
|
||||
<div class="float-right">
|
||||
{% if instance.status != 5 %}
|
||||
<button class="btn btn-success" name="set_console_type" disabled>{% trans "Update" %}</button>
|
||||
{% else %}
|
||||
<button type="submit" class="btn btn-success ">{% trans "Update" %}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% if perms.instances.clone_instances %}
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="clone">
|
||||
<p class="font-weight-bold">{% trans "Create a clone" %}</p>
|
||||
<form class="form" action="{% url 'instances:clone' instance.id %}" method="post" role="form" aria-label="Create clone form">{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label font-weight-normal">{% trans "Clone Name" %}</label>
|
||||
{% if request.user.is_superuser %}
|
||||
<div class="col-sm-6">
|
||||
<div class="input-group">
|
||||
<input id="clone_name" type="text" class="form-control" name="name" value="{{ instance.name }}-clone"/>
|
||||
<div class="input-group-append">
|
||||
<button type="button" class="btn btn-success" name="guess-clone-name"
|
||||
onclick="guess_clone_name()">{% trans "Guess" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% elif app_settings.CLONE_INSTANCE_AUTO_NAME == 'True'%}
|
||||
<div class="col-sm-4">
|
||||
<input id="clone_instance_auto_name" type="text" class="form-control" name="auto_name" value="Automatic" disabled/>
|
||||
</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 %}
|
||||
<label>{% trans "Network devices" %}:</label>
|
||||
{% for network in instance.networks %}
|
||||
<p>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label offset-1">eth{{ forloop.counter0 }} ({{ network.nic }})</label>
|
||||
<div class="col-sm-6">
|
||||
<div class="input-group">
|
||||
<input type="text" class="form-control" name="clone-net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
|
||||
<div class="input-group-append">
|
||||
<button type="button" class="btn btn-success" name="random-mac-{{ forloop.counter0 }}"
|
||||
onclick="random_mac('clone-net-mac-{{ forloop.counter0 }}')">{% trans "Random" %}</button>
|
||||
<button type="button" class="btn btn-success" name="guess-mac-{{ forloop.counter0 }}"
|
||||
onclick="guess_mac_address('#clone_name', {{ forloop.counter0 }})">{% trans "Guess" %}</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</p>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for network in instance.networks %}
|
||||
<input type="hidden" class="form-control" name="clone-net-mac-{{ forloop.counter0 }}" value="{{ network.mac }}"/>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% if request.user.is_superuser %}
|
||||
<label>{% trans "Storage devices" %}:</label>
|
||||
{% for disk in instance.disks %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-2 col-form-label offset-1">{{ disk.dev }} ({{ disk.storage }})</label>
|
||||
<div class="col-sm-6">
|
||||
<div class="input-group">
|
||||
<input id="disk_name-{{ disk.dev }}" type="text" class="form-control" name="disk-{{ disk.dev }}" value="{{ disk.image }}"/>
|
||||
{% if disk.format == 'qcow2' %}
|
||||
<div class="input-group-append">
|
||||
<span class="input-group-text" >{% trans 'Metadata' %}</span>
|
||||
<div class="input-group-text">
|
||||
<input type="checkbox" name="meta-{{ disk.dev }}" value="true">
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% for disk in instance.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 row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Title" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="clone-title" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Description" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<textarea name="clone-description" class="form-control"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
{% if instance.status == 5 %}
|
||||
<button type="submit" class="btn btn-lg btn-success float-right" name="clone" onclick="showPleaseWaitDialog();">{% trans "Clone" %}</button>
|
||||
{% else %}
|
||||
<button class="btn btn-lg btn-success float-right disabled" name="clone">{% trans "Clone" %}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="options">
|
||||
<p>{% trans "To set instance template name description, shutdown the instance." %}</p>
|
||||
<form action="{% url 'instances:change_options' instance.id %}" method="post" role="form" aria-label="Set instance template name description form">{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Title" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<input type="text" name="title" class="form-control" value="{{ instance.title }}">
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-label">{% trans "Description" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<textarea name="description" class="form-control">{{ instance.description }}</textarea>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<label class="col-sm-3 col-form-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 %}
|
||||
{% if not request.user.is_superuser and not request.user.is_staff %}disabled{% endif %}>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group row">
|
||||
<div class="offset-3 col-sm-6">
|
||||
{% if instance.status == 5 %}
|
||||
<button type="submit" class="btn btn-block btn-success" name="change_options">{% trans "Change" %}</button>
|
||||
{% else %}
|
||||
<button class="btn btn-block btn-success disabled" name="change_options">{% trans "Change" %}</button>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class="font-weight-bold">{% trans "To set instance video model, shutdown the instance." %}</p>
|
||||
<form action="{% url 'instances:set_video_model' instance.id %}" method="post" role="form" aria-label="Set instance video model form">{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<label for="video_model_select" class="col-sm-3 col-form-label">{% trans "Primary Video Model" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<div class="input-group">
|
||||
<select id="video_model_select" class="custom-select" name="video_model">
|
||||
<option class="font-weight-bold" value="">{% trans "please choose" %}</option>
|
||||
{% for vmodel in instance.video_models %}
|
||||
<option value="{{ vmodel }}"{% if vmodel == instance.video_model %} selected{% endif %}>{{ vmodel }}</option>
|
||||
{% endfor %}
|
||||
</select>
|
||||
<span class="input-group-btn">
|
||||
{% if instance.status == 5 %}
|
||||
<button type="submit" class="btn btn-success" name="set_video_model">{% trans "Set" %}</button>
|
||||
{% else %}
|
||||
<button class="btn btn-success disabled" name="set_video_model">{% trans "Set" %}</button>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class="font-weight-bold">{% trans "To set instance vCPUs hotpluggable" %}</p>
|
||||
<form action="{% url 'instances:set_vcpu_hotplug' instance.id %}" method="post" role="form" aria-label="Set instance vCPUs hotpluggable form">{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<label for="vcpu_hotplug" class="col-sm-3 col-form-label">{% trans "vCPU Hot Plug" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<div class="input-group">
|
||||
<select id="vcpu_hotplug" class="form-control" name="vcpu_hotplug">
|
||||
<option value="True" {% if instance.vcpus %} selected {% endif %}>{% trans 'Enabled' %}</option>
|
||||
<option value="False" {% if not instance.vcpus %} selected {% endif %}>{% trans 'Disabled' %}</option>
|
||||
</select>
|
||||
<span class="input-group-btn">
|
||||
{% if instance.status == 5 %}
|
||||
<button type="submit" class="btn btn-success" name="set_vcpu_hotplug">{% trans "Set" %}</button>
|
||||
{% else %}
|
||||
<button class="btn btn-success" name="set_vcpu_hotplug" disabled>{% trans "Set" %}</button>
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<p class="font-weight-bold">{% trans "To Enable/Disable Qemu Guest Agent. Status" %}:
|
||||
{% if instance.status == 1 %}
|
||||
{% if instance.guest_agent_ready %}
|
||||
<label class="badge badge-success">{% trans 'Connected' %}</label>
|
||||
{% else %}
|
||||
<label class="badge badge-danger">{% trans 'Disconnected' %}</label>
|
||||
{% endif %}</p>
|
||||
{% else %}
|
||||
<label class="badge badge-default">{% trans 'Unknown' %}</label>
|
||||
{% endif %}
|
||||
<form action="{% url 'instances:set_guest_agent' instance.id %}" method="post" role="form" aria-label="Enable/Disable Qemu Guest Agent form">{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<label for="guest_agent" class="col-sm-3 col-form-label">{% trans "Qemu Guest Agent" %}</label>
|
||||
<div class="col-sm-6">
|
||||
<div class="input-group">
|
||||
<select id="guest_agent" class="custom-select" name="guest_agent">
|
||||
<option value="True" {% if instance.guest_agent %} selected {% endif %}>{% trans 'Enabled' %}</option>
|
||||
<option value="False" {% if not instance.guest_agent %} selected {% endif %}>{% trans 'Disabled' %}</option>
|
||||
</select>
|
||||
<span class="input-group-btn">
|
||||
<button type="submit" class="btn btn-success" name="set_guest_agent">{% trans "Set" %}</button>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
91
instances/templates/instances/snapshots_tab.html
Normal file
91
instances/templates/instances/snapshots_tab.html
Normal file
|
@ -0,0 +1,91 @@
|
|||
{% load i18n %}
|
||||
{% load icons %}
|
||||
<div role="tabpanel" class="tab-pane" id="snapshots">
|
||||
<div role="tabpanel">
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist" aria-label="Instance snapshot menu">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary active" href="#takesnapshot" aria-controls="takesnapshot" role="tab" data-toggle="tab">
|
||||
{% trans "Take Snapshot" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#managesnapshot" aria-controls="managesnapshot" role="tab" data-toggle="tab">
|
||||
{% trans "Manage Snapshots" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="takesnapshot">
|
||||
{% if instance.status == 5 %}
|
||||
<p>{% trans "This may take more than an hour, depending on how much content is on your droplet and how large the disk is." %}</p>
|
||||
<form action="{% url 'instances:snapshot' instance.id %}" class="form-inline" method="post" role="form" aria-label="Create snapshot form">
|
||||
{% csrf_token %}
|
||||
<div class="form-group row">
|
||||
<div class="col-sm-12">
|
||||
<input type="text" class="form-control form-control-lg" name="name" placeholder="{% trans "Enter Snapshot Name" %}" maxlength="14">
|
||||
</div>
|
||||
</div>
|
||||
{% if instance.status == 5 %}
|
||||
<input type="submit" class="btn btn-lg btn-success float-right" name="snapshot" value="{% trans "Take Snapshot" %}">
|
||||
{% else %}
|
||||
<button class="btn btn-lg btn-success float-right disabled">{% trans "Take Snapshot" %}</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
<div class="clearfix"></div>
|
||||
{% else %}
|
||||
<p>{% trans "To take a snapshot please Power Off the instance." %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="managesnapshot">
|
||||
{% if instance.snapshots %}
|
||||
<p>{% trans "Choose a snapshot for restore/delete" %}</p>
|
||||
<div class="table-responsive">
|
||||
<table class="table">
|
||||
<thead>
|
||||
<th scope="col">{% trans "Name" %}</th>
|
||||
<th scope="col">{% trans "Date" %}</th>
|
||||
<th scope="colgroup" colspan="2">{% trans "Action" %}</th>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for snap in instance.snapshots %}
|
||||
<tr>
|
||||
<td><strong>{{ snap.name }}</strong></td>
|
||||
<td>{{ snap.date|date:"M d H:i:s" }}</td>
|
||||
<td style="width:30px;">
|
||||
<form action="{% url 'instances:revert_snapshot' instance.id %}" method="post" role="form" aria-label="Restore snapshot form">
|
||||
{% csrf_token %}
|
||||
<input type="hidden" name="name" value="{{ snap.name }}">
|
||||
{% if instance.status == 5 %}
|
||||
<button type="submit" class="btn btn-sm btn-secondary" name="revert_snapshot" title="{% trans 'Revert to this Snapshot' %}" onclick="return confirm('Are you sure?')">
|
||||
<span class="fa fa-download"></span>
|
||||
</button>
|
||||
{% else %}
|
||||
<button type="button" class="btn btn-sm btn-secondary disabled"
|
||||
title="{% trans "To restore snapshots you need Power Off the instance." %}">
|
||||
<span class="fa fa-download"></span>
|
||||
</button>
|
||||
{% endif %}
|
||||
</form>
|
||||
</td>
|
||||
<td style="width:30px;">
|
||||
<form action="{% url 'instances:delete_snapshot' instance.id %}" method="post" role="form" aria-label="Delete snapshot form">{% csrf_token %}
|
||||
<input type="hidden" name="name" value="{{ snap.name }}">
|
||||
<button type="submit" class="btn btn-sm btn-danger" title="{% trans 'Delete Snapshot' %}" onclick="return confirm('{% trans "Are you sure?" %}')">
|
||||
{% icon 'trash' %}
|
||||
</button>
|
||||
</form>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% else %}
|
||||
<p>{% trans "You do not have any snapshots" %}</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
116
instances/templates/instances/stats_tab.html
Normal file
116
instances/templates/instances/stats_tab.html
Normal file
|
@ -0,0 +1,116 @@
|
|||
{% load i18n %}
|
||||
<div role="tabpanel" class="tab-pane" id="graphics">
|
||||
<div role="tabpanel">
|
||||
<!-- Nav tabs -->
|
||||
<ul class="nav nav-tabs" role="tablist" aria-label="Instance graphs and logs menu">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary active" href="#graphs" id="graphtab" aria-controls="graphs" role="tab" data-toggle="tab" aria-controls="graphs" aria-selected="true">
|
||||
{% trans "Real Time" %}
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link text-secondary" href="#logs" id="logtab" aria-controls="logs" role="tab" data-toggle="tab" aria-controls="logs" onclick='update_logs_table("{{ instance.name }}");'>
|
||||
{% trans "Logs" %}
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
<!-- Tab panes -->
|
||||
<div class="tab-content">
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered active" id="graphs">
|
||||
<div class="mb-1 card border-success">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title"><i class="fa fa-long-arrow-right"></i>
|
||||
{% trans "CPU Usage" %}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="flot-chart">
|
||||
<div class="flot-chart-content" id="flot-moving-line-chart">
|
||||
<canvas id="cpuChart" width="735" height="160"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-1 card border-danger">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title"><i class="fa fa-long-arrow-right"></i>
|
||||
{% trans "Memory Usage" %}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="flot-chart">
|
||||
<div class="flot-chart-content" id="flot-moving-line-chart">
|
||||
<canvas id="memChart" width="735" height="160"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% for net in instance.networks %}
|
||||
<div class="mb-1 card border-info">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title"><i class="fa fa-long-arrow-right"></i>
|
||||
{% trans "Bandwidth Device" %}: eth{{ forloop.counter0 }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="flot-chart">
|
||||
<div class="flot-chart-content" id="flot-moving-line-chart">
|
||||
<canvas id="netEth{{ forloop.counter0 }}Chart" width="735" height="160"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% for disk in instance.disks %}
|
||||
<div class="mb-1 card border-warning">
|
||||
<div class="card-header">
|
||||
<h5 class="card-title"><i class="fa fa-long-arrow-right"></i>
|
||||
{% trans "Disk I/O device" %}: {{ disk.dev }}</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="flot-chart">
|
||||
<div class="flot-chart-content" id="flot-moving-line-chart">
|
||||
<canvas id="blk{{ disk.dev }}Chart" width="735" height="160"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="logs">
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped sortable-theme-bootstrap" id="logs_table" data-sortable>
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">{% trans "Date" %}</th>
|
||||
<th scope="col">{% trans "User" %}</th>
|
||||
<th scope="col">{% trans "Message" %}</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="searchable">
|
||||
<tr>
|
||||
<td colspan="3"><i>{% trans 'None' %}...</i></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="clearfix"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
function update_logs_table(vname) {
|
||||
// TODO
|
||||
logurl = "{% url 'vm_logs' 1 %}".replace(1, vname);
|
||||
$.getJSON(logurl, function(data) {
|
||||
var logs = "";
|
||||
$.each(data, function(id) {
|
||||
row = data[id];
|
||||
// console.log(row);
|
||||
logs += '<tr><td style="width:150px">'+row['date']+'</td>';
|
||||
logs += '<td>'+row['user']+'</td>';
|
||||
logs += '<td>'+row['message']+'</td></tr>';
|
||||
});
|
||||
$("#logs_table > tbody").html(logs);
|
||||
});
|
||||
}
|
||||
</script>
|
|
@ -1,3 +1,478 @@
|
|||
import tempfile
|
||||
|
||||
from django.shortcuts import reverse
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
from computes.models import Compute
|
||||
|
||||
from .models import Instance
|
||||
|
||||
|
||||
class InstancesTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.client.login(username='admin', password='admin')
|
||||
Compute(
|
||||
name='local',
|
||||
hostname='localhost',
|
||||
login='',
|
||||
password='',
|
||||
details='local',
|
||||
type=4,
|
||||
).save()
|
||||
|
||||
def test_index(self):
|
||||
response = self.client.get(reverse('instances:index'))
|
||||
# with open('index.html', 'wb') as f:
|
||||
# f.write(response.content)
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_create_select_type(self):
|
||||
response = self.client.get(reverse('instances:create_instance_select_type', args=[1]))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
def test_instance(self):
|
||||
compute = Compute.objects.get(pk=1)
|
||||
|
||||
# create volume
|
||||
response = self.client.post(
|
||||
reverse('create_volume', args=[compute.id, 'default']),
|
||||
{
|
||||
'name': 'test',
|
||||
'format': 'qcow2',
|
||||
'size': '1',
|
||||
'meta_prealloc': False,
|
||||
},
|
||||
)
|
||||
self.assertRedirects(response, reverse('storage', args=[compute.id, 'default']))
|
||||
|
||||
# create instance
|
||||
response = self.client.get(reverse('instances:create_instance', args=[compute.id, 'x86_64', 'q35']))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('instances:create_instance', args=[compute.id, 'x86_64', 'q35']),
|
||||
{
|
||||
'name': 'test',
|
||||
'firmware': 'BIOS',
|
||||
'vcpu': 1,
|
||||
'vcpu_mode': 'host-model',
|
||||
'memory': 512,
|
||||
'device0': 'disk',
|
||||
'bus0': 'virtio',
|
||||
'images': 'test.qcow2',
|
||||
'storage-control': 'default',
|
||||
'image-control': 'test.qcow2',
|
||||
'networks': 'default',
|
||||
'network-control': 'default',
|
||||
'cache_mode': 'directsync',
|
||||
'nwfilter': '',
|
||||
'graphics': 'spice',
|
||||
'video': 'vga',
|
||||
'listener_addr': '0.0.0.0',
|
||||
'console_pass': '',
|
||||
'qemu_ga': False,
|
||||
'virtio': True,
|
||||
'create': True,
|
||||
},
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
instance: Instance = Instance.objects.get(pk=1)
|
||||
self.assertEqual(instance.name, 'test')
|
||||
|
||||
# get instance page
|
||||
response = self.client.get(reverse('instances:instance', args=[instance.id]))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# resize cpu
|
||||
self.assertEqual(instance.vcpu, 1)
|
||||
self.assertEqual(instance.cur_vcpu, 1)
|
||||
|
||||
response = self.client.post(reverse('instances:resizevm_cpu', args=[instance.id]), {'vcpu': 4, 'cur_vcpu': 2})
|
||||
self.assertRedirects(response, reverse('instances:instance', args=[instance.id]) + '#resize')
|
||||
|
||||
# reset cached properties
|
||||
del instance.vcpu
|
||||
del instance.cur_vcpu
|
||||
self.assertEqual(instance.vcpu, 4)
|
||||
self.assertEqual(instance.cur_vcpu, 2)
|
||||
|
||||
# resize memory
|
||||
self.assertEqual(instance.memory, 512)
|
||||
self.assertEqual(instance.cur_memory, 512)
|
||||
|
||||
response = self.client.post(reverse('instances:resize_memory', args=[instance.id]), {
|
||||
'memory': 2048,
|
||||
'cur_memory': 1024
|
||||
})
|
||||
self.assertRedirects(response, reverse('instances:instance', args=[instance.id]) + '#resize')
|
||||
|
||||
del instance.memory
|
||||
del instance.cur_memory
|
||||
self.assertEqual(instance.memory, 2048)
|
||||
self.assertEqual(instance.cur_memory, 1024)
|
||||
|
||||
# resize disk
|
||||
self.assertEqual(instance.disks[0]['size'], 1024**3)
|
||||
|
||||
response = self.client.post(reverse('instances:resize_disk', args=[instance.id]), {
|
||||
'disk_size_vda': '2.0 GB',
|
||||
})
|
||||
self.assertRedirects(response, reverse('instances:instance', args=[instance.id]) + '#resize')
|
||||
|
||||
del instance.disks
|
||||
self.assertEqual(instance.disks[0]['size'], 2 * 1024**3)
|
||||
|
||||
# add new volume
|
||||
self.assertEqual(len(instance.disks), 1)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('instances:add_new_vol', args=[instance.id]),
|
||||
{
|
||||
'storage': 'default',
|
||||
'name': 'test2',
|
||||
'size': 1,
|
||||
},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.disks
|
||||
self.assertEqual(len(instance.disks), 2)
|
||||
|
||||
# delete volume from instance
|
||||
response = self.client.post(
|
||||
reverse('instances:delete_vol', args=[instance.id]),
|
||||
{
|
||||
'storage': 'default',
|
||||
'dev': 'vdb',
|
||||
'name': 'test2.qcow2',
|
||||
},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.disks
|
||||
self.assertEqual(len(instance.disks), 1)
|
||||
|
||||
# detach volume
|
||||
response = self.client.post(
|
||||
reverse('instances:detach_vol', args=[instance.id]),
|
||||
{
|
||||
'dev': 'vda',
|
||||
},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.disks
|
||||
self.assertEqual(len(instance.disks), 0)
|
||||
|
||||
# add existing volume
|
||||
response = self.client.post(
|
||||
reverse('instances:add_existing_vol', args=[instance.id]),
|
||||
{
|
||||
'selected_storage': 'default',
|
||||
'vols': 'test.qcow2',
|
||||
},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.disks
|
||||
self.assertEqual(len(instance.disks), 1)
|
||||
|
||||
# edit volume
|
||||
response = self.client.post(
|
||||
reverse('instances:edit_volume', args=[instance.id]),
|
||||
{
|
||||
'vol_path': '/var/lib/libvirt/images/test.qcow2',
|
||||
# 'vol_shareable': False,
|
||||
# 'vol_readonly': False,
|
||||
'vol_bus': 'virtio',
|
||||
'vol_bus_old': 'virtio',
|
||||
'vol_format': 'qcow2',
|
||||
'dev': 'vda',
|
||||
'edit_volume': True
|
||||
},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
# add media device
|
||||
self.assertEqual(len(instance.media), 1)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('instances:add_cdrom', args=[instance.id]),
|
||||
{
|
||||
'bus': 'sata',
|
||||
},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.media
|
||||
self.assertEqual(len(instance.media), 2)
|
||||
|
||||
# create dummy iso
|
||||
# with tempfile.NamedTemporaryFile() as f:
|
||||
# f.write(b'\x00' * 1024**2)
|
||||
|
||||
# response = self.client.post(
|
||||
# reverse('storage', args=[instance.compute.id, 'default']),
|
||||
# {
|
||||
# 'iso_upload': True,
|
||||
# 'file': f
|
||||
# },
|
||||
# )
|
||||
|
||||
# remove media device
|
||||
response = self.client.post(
|
||||
reverse('instances:detach_cdrom', args=[instance.id, 'sda']),
|
||||
{},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.media
|
||||
self.assertEqual(len(instance.media), 1)
|
||||
|
||||
# snapshots
|
||||
self.assertEqual(len(instance.snapshots), 0)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('instances:snapshot', args=[instance.id]),
|
||||
{
|
||||
'name': 'test',
|
||||
},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.snapshots
|
||||
self.assertEqual(len(instance.snapshots), 1)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('instances:revert_snapshot', args=[instance.id]),
|
||||
{
|
||||
'name': 'test',
|
||||
},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('instances:delete_snapshot', args=[instance.id]),
|
||||
{
|
||||
'name': 'test',
|
||||
},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.snapshots
|
||||
self.assertEqual(len(instance.snapshots), 0)
|
||||
|
||||
# autostart
|
||||
self.assertEqual(instance.autostart, 0)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('instances:set_autostart', args=[instance.id]),
|
||||
{},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.autostart
|
||||
self.assertEqual(instance.autostart, 1)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('instances:unset_autostart', args=[instance.id]),
|
||||
{},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.autostart
|
||||
self.assertEqual(instance.autostart, 0)
|
||||
|
||||
# bootmenu
|
||||
self.assertEqual(instance.bootmenu, True)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('instances:unset_bootmenu', args=[instance.id]),
|
||||
{},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.bootmenu
|
||||
self.assertEqual(instance.bootmenu, False)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('instances:set_bootmenu', args=[instance.id]),
|
||||
{},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.bootmenu
|
||||
self.assertEqual(instance.bootmenu, True)
|
||||
|
||||
# guest agent
|
||||
|
||||
self.assertEqual(instance.guest_agent, False)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('instances:set_guest_agent', args=[instance.id]),
|
||||
{'guest_agent': True},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.guest_agent
|
||||
self.assertEqual(instance.guest_agent, True)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('instances:set_guest_agent', args=[instance.id]),
|
||||
{'guest_agent': False},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.guest_agent
|
||||
self.assertEqual(instance.guest_agent, False)
|
||||
|
||||
# video model
|
||||
self.assertEqual(instance.video_model, 'vga')
|
||||
|
||||
response = self.client.post(
|
||||
reverse('instances:set_video_model', args=[instance.id]),
|
||||
{'video_model': 'virtio'},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.video_model
|
||||
self.assertEqual(instance.video_model, 'virtio')
|
||||
|
||||
# console
|
||||
response = self.client.post(
|
||||
reverse('instances:update_console', args=[instance.id]),
|
||||
{
|
||||
'type': 'spice',
|
||||
'listen_on': '0.0.0.0',
|
||||
'password': '',
|
||||
'keymap': 'auto'
|
||||
},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
# poweron
|
||||
self.assertEqual(instance.status, 5)
|
||||
|
||||
response = self.client.get(reverse('instances:poweron', args=[instance.id]), HTTP_REFERER=reverse('index'))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.status
|
||||
|
||||
self.assertEqual(instance.status, 1)
|
||||
|
||||
# status
|
||||
response = self.client.get(reverse('instances:status', args=[instance.id]))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# stats
|
||||
response = self.client.get(reverse('instances:stats', args=[instance.id]))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# guess_mac_address
|
||||
response = self.client.get(reverse('instances:guess_mac_address', args=[instance.name]))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# random_mac_address
|
||||
response = self.client.get(reverse('instances:random_mac_address'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# random_mac_address
|
||||
response = self.client.get(reverse('instances:guess_clone_name'))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# guess_mac_address
|
||||
response = self.client.get(reverse('instances:check_instance', args=[instance.name]))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# sshkeys
|
||||
response = self.client.get(reverse('instances:sshkeys', args=[instance.name]))
|
||||
self.assertEqual(response.status_code, 200)
|
||||
|
||||
# suspend
|
||||
response = self.client.get(reverse('instances:suspend', args=[instance.id]), HTTP_REFERER=reverse('index'))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.status
|
||||
self.assertEqual(instance.status, 3)
|
||||
|
||||
# resume
|
||||
response = self.client.get(reverse('instances:resume', args=[instance.id]), HTTP_REFERER=reverse('index'))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.status
|
||||
self.assertEqual(instance.status, 1)
|
||||
|
||||
# poweroff
|
||||
response = self.client.get(reverse('instances:poweroff', args=[instance.id]), HTTP_REFERER=reverse('index'))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
# as no OS is installed ACPI won't work
|
||||
del instance.status
|
||||
self.assertEqual(instance.status, 1)
|
||||
|
||||
# powercycle
|
||||
response = self.client.get(reverse('instances:powercycle', args=[instance.id]), HTTP_REFERER=reverse('index'))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
self.assertEqual(instance.status, 1)
|
||||
|
||||
# force_off
|
||||
response = self.client.get(reverse('instances:force_off', args=[instance.id]), HTTP_REFERER=reverse('index'))
|
||||
self.assertEqual(response.status_code, 302)
|
||||
|
||||
del instance.status
|
||||
self.assertEqual(instance.status, 5)
|
||||
|
||||
# delete started instance with disks
|
||||
instance.proxy.start()
|
||||
del instance.status
|
||||
self.assertEqual(instance.status, 1)
|
||||
|
||||
response = self.client.post(
|
||||
reverse('instances:destroy', args=[instance.id]),
|
||||
{'delete_disk': True},
|
||||
HTTP_REFERER=reverse('index'),
|
||||
)
|
||||
self.assertRedirects(response, reverse('instances', args=[compute.id]))
|
||||
|
||||
# # create volume
|
||||
# response = self.client.post(
|
||||
# reverse('create_volume', args=[compute.id, 'default']),
|
||||
# {
|
||||
# 'name': 'test3',
|
||||
# 'format': 'qcow2',
|
||||
# 'size': '1',
|
||||
# 'meta_prealloc': False,
|
||||
# },
|
||||
# )
|
||||
# self.assertRedirects(response, reverse('storage', args=[compute.id, 'default']))
|
||||
|
||||
# # delete volume
|
||||
# response = self.client.post(
|
||||
# reverse('instances:delete_vol', args=[instance.id]),
|
||||
# {
|
||||
# 'storage': 'default',
|
||||
# 'dev': 'vdb',
|
||||
# 'name': 'test3.qcow2',
|
||||
# },
|
||||
# HTTP_REFERER=reverse('index'),
|
||||
# )
|
||||
# self.assertEqual(response.status_code, 302)
|
||||
|
|
|
@ -5,10 +5,62 @@ from . import views
|
|||
app_name = 'instances'
|
||||
|
||||
urlpatterns = [
|
||||
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'),
|
||||
path('', views.index, name='index'),
|
||||
path('flavor/create/', views.flavor_create, name='flavor_create'),
|
||||
path('flavor/<int:pk>/update/', views.flavor_update, name='flavor_update'),
|
||||
path('flavor/<int:pk>/delete/', views.flavor_delete, name='flavor_delete'),
|
||||
path('<int:pk>/', views.instance, name='instance'),
|
||||
path('<int:pk>/poweron/', views.poweron, name='poweron'),
|
||||
path('<int:pk>/powercycle/', views.powercycle, name='powercycle'),
|
||||
path('<int:pk>/poweroff/', views.poweroff, name='poweroff'),
|
||||
path('<int:pk>/suspend/', views.suspend, name='suspend'),
|
||||
path('<int:pk>/resume/', views.resume, name='resume'),
|
||||
path('<int:pk>/force_off/', views.force_off, name='force_off'),
|
||||
path('<int:pk>/destroy/', views.destroy, name='destroy'),
|
||||
path('<int:pk>/migrate/', views.migrate, name='migrate'),
|
||||
path('<int:pk>/status/', views.status, name='status'),
|
||||
path('<int:pk>/stats/', views.stats, name='stats'),
|
||||
path('<int:pk>/rootpasswd/', views.set_root_pass, name='rootpasswd'),
|
||||
path('<int:pk>/add_public_key/', views.add_public_key, name='add_public_key'),
|
||||
path('<int:pk>/resizevm_cpu/', views.resizevm_cpu, name='resizevm_cpu'),
|
||||
path('<int:pk>/resize_memory/', views.resize_memory, name='resize_memory'),
|
||||
path('<int:pk>/resize_disk/', views.resize_disk, name='resize_disk'),
|
||||
path('<int:pk>/add_new_vol/', views.add_new_vol, name='add_new_vol'),
|
||||
path('<int:pk>/delete_vol/', views.delete_vol, name='delete_vol'),
|
||||
path('<int:pk>/add_owner/', views.add_owner, name='add_owner'),
|
||||
path('<int:pk>/add_existing_vol/', views.add_existing_vol, name='add_existing_vol'),
|
||||
path('<int:pk>/edit_volume/', views.edit_volume, name='edit_volume'),
|
||||
path('<int:pk>/detach_vol/', views.detach_vol, name='detach_vol'),
|
||||
path('<int:pk>/add_cdrom/', views.add_cdrom, name='add_cdrom'),
|
||||
path('<int:pk>/detach_cdrom/<str:dev>/', views.detach_cdrom, name='detach_cdrom'),
|
||||
path('<int:pk>/unmount_iso/', views.unmount_iso, name='unmount_iso'),
|
||||
path('<int:pk>/mount_iso/', views.mount_iso, name='mount_iso'),
|
||||
path('<int:pk>/snapshot/', views.snapshot, name='snapshot'),
|
||||
path('<int:pk>/delete_snapshot/', views.delete_snapshot, name='delete_snapshot'),
|
||||
path('<int:pk>/revert_snapshot/', views.revert_snapshot, name='revert_snapshot'),
|
||||
path('<int:pk>/set_vcpu/', views.set_vcpu, name='set_vcpu'),
|
||||
path('<int:pk>/set_vcpu_hotplug/', views.set_vcpu_hotplug, name='set_vcpu_hotplug'),
|
||||
path('<int:pk>/set_autostart/', views.set_autostart, name='set_autostart'),
|
||||
path('<int:pk>/unset_autostart/', views.unset_autostart, name='unset_autostart'),
|
||||
path('<int:pk>/set_bootmenu/', views.set_bootmenu, name='set_bootmenu'),
|
||||
path('<int:pk>/unset_bootmenu/', views.unset_bootmenu, name='unset_bootmenu'),
|
||||
path('<int:pk>/set_bootorder/', views.set_bootorder, name='set_bootorder'),
|
||||
path('<int:pk>/change_xml/', views.change_xml, name='change_xml'),
|
||||
path('<int:pk>/set_guest_agent/', views.set_guest_agent, name='set_guest_agent'),
|
||||
path('<int:pk>/set_video_model/', views.set_video_model, name='set_video_model'),
|
||||
path('<int:pk>/change_network/', views.change_network, name='change_network'),
|
||||
path('<int:pk>/add_network/', views.add_network, name='add_network'),
|
||||
path('<int:pk>/delete_network/', views.delete_network, name='delete_network'),
|
||||
path('<int:pk>/set_link_state/', views.set_link_state, name='set_link_state'),
|
||||
path('<int:pk>/set_qos/', views.set_qos, name='set_qos'),
|
||||
path('<int:pk>/unset_qos/', views.unset_qos, name='unset_qos'),
|
||||
path('<int:pk>/del_owner/', views.del_owner, name='del_owner'), # no links to this one???
|
||||
path('<int:pk>/clone/', views.clone, name='clone'),
|
||||
path('<int:pk>/update_console/', views.update_console, name='update_console'),
|
||||
path('<int:pk>/change_options/', views.change_options, name='change_options'),
|
||||
path('<int:pk>/getvvfile/', views.getvvfile, name='getvvfile'), # no links to this one???
|
||||
path('create/<int:compute_id>/', views.create_instance_select_type, name='create_instance_select_type'),
|
||||
path('create/<int:compute_id>/<str:arch>/<str:machine>/', views.create_instance, name='create_instance'),
|
||||
path('guess_mac_address/<vname>/', views.guess_mac_address, name='guess_mac_address'),
|
||||
path('guess_clone_name/', views.guess_clone_name, name='guess_clone_name'),
|
||||
path('random_mac_address/', views.random_mac_address, name='random_mac_address'),
|
||||
|
|
361
instances/utils.py
Normal file
361
instances/utils.py
Normal file
|
@ -0,0 +1,361 @@
|
|||
import os
|
||||
import random
|
||||
import string
|
||||
from collections import OrderedDict
|
||||
|
||||
from django.conf import settings
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from accounts.models import UserInstance
|
||||
from appsettings.settings import app_settings
|
||||
from logs.views import addlogmsg
|
||||
from vrtManager.connection import connection_manager
|
||||
from vrtManager.hostdetails import wvmHostDetails
|
||||
from vrtManager.instance import wvmInstance, wvmInstances
|
||||
|
||||
from .models import Instance
|
||||
|
||||
|
||||
def filesizefstr(size_str):
|
||||
if size_str == '':
|
||||
return 0
|
||||
size_str = size_str.upper().replace("B", "")
|
||||
if size_str[-1] == 'K':
|
||||
return int(float(size_str[:-1])) << 10
|
||||
elif size_str[-1] == 'M':
|
||||
return int(float(size_str[:-1])) << 20
|
||||
elif size_str[-1] == 'G':
|
||||
return int(float(size_str[:-1])) << 30
|
||||
elif size_str[-1] == 'T':
|
||||
return int(float(size_str[:-1])) << 40
|
||||
elif size_str[-1] == 'P':
|
||||
return int(float(size_str[:-1])) << 50
|
||||
else:
|
||||
return int(float(size_str))
|
||||
|
||||
|
||||
def get_clone_free_names(size=10):
|
||||
prefix = app_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(user, instance, cpu, memory, disk_size):
|
||||
ua = user.userattributes
|
||||
msg = ""
|
||||
|
||||
if user.is_superuser:
|
||||
return msg
|
||||
|
||||
quota_debug = app_settings.QUOTA_DEBUG
|
||||
|
||||
user_instances = UserInstance.objects.filter(user=user, instance__is_template=False)
|
||||
instance += user_instances.count()
|
||||
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.hostname,
|
||||
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_devices():
|
||||
if disk['size']:
|
||||
disk_size += int(disk['size']) >> 30
|
||||
|
||||
if ua.max_instances > 0 and instance > ua.max_instances:
|
||||
msg = "instance"
|
||||
if quota_debug:
|
||||
msg += f" ({instance} > {ua.max_instances})"
|
||||
if ua.max_cpus > 0 and cpu > ua.max_cpus:
|
||||
msg = "cpu"
|
||||
if quota_debug:
|
||||
msg += f" ({cpu} > {ua.max_cpus})"
|
||||
if ua.max_memory > 0 and memory > ua.max_memory:
|
||||
msg = "memory"
|
||||
if quota_debug:
|
||||
msg += f" ({memory} > {ua.max_memory})"
|
||||
if ua.max_disk_size > 0 and disk_size > ua.max_disk_size:
|
||||
msg = "disk"
|
||||
if quota_debug:
|
||||
msg += f" ({disk_size} > {ua.max_disk_size})"
|
||||
return msg
|
||||
|
||||
|
||||
def get_new_disk_dev(media, disks, bus):
|
||||
existing_disk_devs = []
|
||||
existing_media_devs = []
|
||||
if bus == "virtio":
|
||||
dev_base = "vd"
|
||||
elif bus == "ide":
|
||||
dev_base = "hd"
|
||||
elif bus == "fdc":
|
||||
dev_base = "fd"
|
||||
else:
|
||||
dev_base = "sd"
|
||||
|
||||
if disks:
|
||||
existing_disk_devs = [disk['dev'] for disk in disks]
|
||||
|
||||
# cd-rom bus could be virtio/sata, because of that we should check it also
|
||||
if media:
|
||||
existing_media_devs = [m['dev'] for m in media]
|
||||
|
||||
for al in string.ascii_lowercase:
|
||||
dev = dev_base + al
|
||||
if dev not in existing_disk_devs and dev not in existing_media_devs:
|
||||
return dev
|
||||
raise Exception(_('None available device name'))
|
||||
|
||||
|
||||
def get_network_tuple(network_source_str):
|
||||
network_source_pack = network_source_str.split(":", 1)
|
||||
if len(network_source_pack) > 1:
|
||||
return network_source_pack[1], network_source_pack[0]
|
||||
else:
|
||||
return network_source_pack[0], 'net'
|
||||
|
||||
|
||||
def migrate_instance(
|
||||
new_compute,
|
||||
instance,
|
||||
user,
|
||||
live=False,
|
||||
unsafe=False,
|
||||
xml_del=False,
|
||||
offline=False,
|
||||
autoconverge=False,
|
||||
compress=False,
|
||||
postcopy=False,
|
||||
):
|
||||
status = connection_manager.host_is_up(new_compute.type, new_compute.hostname)
|
||||
if not status:
|
||||
return
|
||||
if new_compute == instance.compute:
|
||||
return
|
||||
try:
|
||||
conn_migrate = wvmInstances(
|
||||
new_compute.hostname,
|
||||
new_compute.login,
|
||||
new_compute.password,
|
||||
new_compute.type,
|
||||
)
|
||||
|
||||
autostart = instance.autostart
|
||||
conn_migrate.moveto(
|
||||
instance.proxy,
|
||||
instance.name,
|
||||
live,
|
||||
unsafe,
|
||||
xml_del,
|
||||
offline,
|
||||
autoconverge,
|
||||
compress,
|
||||
postcopy,
|
||||
)
|
||||
|
||||
conn_new = wvmInstance(
|
||||
new_compute.hostname,
|
||||
new_compute.login,
|
||||
new_compute.password,
|
||||
new_compute.type,
|
||||
instance.name,
|
||||
)
|
||||
|
||||
if autostart:
|
||||
conn_new.set_autostart(1)
|
||||
finally:
|
||||
conn_migrate.close()
|
||||
conn_new.close()
|
||||
|
||||
instance.compute = new_compute
|
||||
instance.save()
|
||||
|
||||
|
||||
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
|
||||
|
||||
|
||||
def get_userinstances_info(instance):
|
||||
info = {}
|
||||
uis = UserInstance.objects.filter(instance=instance)
|
||||
info['count'] = uis.count()
|
||||
if info['count'] > 0:
|
||||
info['first_user'] = uis[0]
|
||||
else:
|
||||
info['first_user'] = None
|
||||
return info
|
||||
|
||||
|
||||
def refr(compute):
|
||||
if compute.status is True:
|
||||
domains = compute.proxy.wvm.listAllDomains()
|
||||
domain_names = [d.name() for d in domains]
|
||||
# Delete instances that're not on host
|
||||
Instance.objects.filter(compute=compute).exclude(name__in=domain_names).delete()
|
||||
# Create instances that're not in DB
|
||||
names = Instance.objects.filter(compute=compute).values_list('name', flat=True)
|
||||
for domain in domains:
|
||||
if domain.name() not in names:
|
||||
Instance(compute=compute, name=domain.name(), uuid=domain.UUIDString()).save()
|
||||
|
||||
|
||||
def refresh_instance_database(comp, inst_name, info, all_host_vms, user):
|
||||
# Multiple Instance Name Check
|
||||
instances = Instance.objects.filter(name=inst_name)
|
||||
if instances.count() > 1:
|
||||
for i in instances:
|
||||
user_instances_count = UserInstance.objects.filter(instance=i).count()
|
||||
if user_instances_count == 0:
|
||||
addlogmsg(user.username, i.name, _("Deleting due to multiple(Instance Name) records."))
|
||||
i.delete()
|
||||
|
||||
# Multiple UUID Check
|
||||
instances = Instance.objects.filter(uuid=info['uuid'])
|
||||
if instances.count() > 1:
|
||||
for i in instances:
|
||||
if i.name != inst_name:
|
||||
addlogmsg(user.username, i.name, _("Deleting due to multiple(UUID) records."))
|
||||
i.delete()
|
||||
|
||||
try:
|
||||
inst_on_db = Instance.objects.get(compute_id=comp["id"], name=inst_name)
|
||||
if inst_on_db.uuid != info['uuid']:
|
||||
inst_on_db.save()
|
||||
|
||||
all_host_vms[comp["id"], comp["name"], comp["status"], comp["cpu"], comp["mem_size"],
|
||||
comp["mem_perc"]][inst_name]['is_template'] = inst_on_db.is_template
|
||||
all_host_vms[comp["id"], comp["name"], comp["status"], comp["cpu"], comp["mem_size"],
|
||||
comp["mem_perc"]][inst_name]['userinstances'] = get_userinstances_info(inst_on_db)
|
||||
except Instance.DoesNotExist:
|
||||
inst_on_db = Instance(compute_id=comp["id"], name=inst_name, uuid=info['uuid'])
|
||||
inst_on_db.save()
|
||||
|
||||
|
||||
def get_user_instances(user):
|
||||
all_user_vms = {}
|
||||
user_instances = UserInstance.objects.filter(user=user)
|
||||
for usr_inst in user_instances:
|
||||
if connection_manager.host_is_up(usr_inst.instance.compute.type, usr_inst.instance.compute.hostname):
|
||||
conn = wvmHostDetails(
|
||||
usr_inst.instance.compute.hostname,
|
||||
usr_inst.instance.compute.login,
|
||||
usr_inst.instance.compute.password,
|
||||
usr_inst.instance.compute.type,
|
||||
)
|
||||
all_user_vms[usr_inst] = conn.get_user_instances(usr_inst.instance.name)
|
||||
all_user_vms[usr_inst].update({'compute_id': usr_inst.instance.compute.id})
|
||||
return all_user_vms
|
||||
|
||||
|
||||
def get_host_instances(compute):
|
||||
all_host_vms = OrderedDict()
|
||||
|
||||
# if compute.status:
|
||||
comp_node_info = compute.proxy.get_node_info()
|
||||
comp_mem = compute.proxy.get_memory_usage()
|
||||
comp_instances = compute.proxy.get_host_instances(True)
|
||||
|
||||
# if comp_instances:
|
||||
comp_info = {
|
||||
"id": compute.id,
|
||||
"name": compute.name,
|
||||
"status": compute.status,
|
||||
"cpu": comp_node_info[3],
|
||||
"mem_size": comp_node_info[2],
|
||||
"mem_perc": comp_mem['percent'],
|
||||
}
|
||||
# refr(compute)
|
||||
all_host_vms[comp_info["id"], comp_info["name"], comp_info["status"], comp_info["cpu"], comp_info["mem_size"],
|
||||
comp_info["mem_perc"]] = comp_instances
|
||||
for vm, info in comp_instances.items():
|
||||
# TODO: Delete this function completely
|
||||
refresh_instance_database(comp_info, vm, info, all_host_vms, User.objects.get(pk=1))
|
||||
|
||||
# else:
|
||||
# raise libvirtError(_(f"Problem occurred with host: {compute.name} - {status}"))
|
||||
return all_host_vms
|
||||
|
||||
|
||||
def get_dhcp_mac_address(vname):
|
||||
dhcp_file = settings.BASE_DIR + '/dhcpd.conf'
|
||||
mac = ''
|
||||
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:
|
||||
mac = line.split(' ')[-1].strip().strip(';')
|
||||
break
|
||||
return mac
|
||||
|
||||
|
||||
def get_random_mac_address():
|
||||
mac = '52:54:00:%02x:%02x:%02x' % (
|
||||
random.randint(0x00, 0xff),
|
||||
random.randint(0x00, 0xff),
|
||||
random.randint(0x00, 0xff),
|
||||
)
|
||||
return mac
|
||||
|
||||
|
||||
def get_clone_disk_name(disk, prefix, clone_name=''):
|
||||
if not disk['image']:
|
||||
return None
|
||||
if disk['image'].startswith(prefix) and clone_name:
|
||||
suffix = disk['image'][len(prefix):]
|
||||
image = f"{clone_name}{suffix}"
|
||||
elif "." in disk['image'] and len(disk['image'].rsplit(".", 1)[1]) <= 7:
|
||||
name, suffix = disk['image'].rsplit(".", 1)
|
||||
image = f"{name}-clone.{suffix}"
|
||||
else:
|
||||
image = f"{disk['image']}-clone"
|
||||
return image
|
||||
|
||||
|
||||
# TODO: this function is not used
|
||||
def _get_clone_disks(disks, vname=''):
|
||||
clone_disks = []
|
||||
for disk in disks:
|
||||
new_image = get_clone_disk_name(disk, vname)
|
||||
if not new_image:
|
||||
continue
|
||||
new_disk = {
|
||||
'dev': disk['dev'],
|
||||
'storage': disk['storage'],
|
||||
'image': new_image,
|
||||
'format': disk['format'],
|
||||
}
|
||||
clone_disks.append(new_disk)
|
||||
return clone_disks
|
2665
instances/views.py
2665
instances/views.py
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue