1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2025-07-31 12:41:08 +00:00

Compare commits

...

11 commits

Author SHA1 Message Date
catborise
6e76eb2cd9 reorganize otp_login and login pages 2023-05-12 10:51:18 +03:00
catborise
c099cd06ef update requirements 2023-05-12 10:51:18 +03:00
catborise
beb13cdea1
Merge pull request #585 from catborise/master
various addings and fixes
2023-05-12 10:44:47 +03:00
cserma
2941840f0d 'create new instance' screen improvements 2023-05-12 10:15:01 +03:00
catborise
5880b91c7a
Merge pull request #29 from cserma/master
various fixes - new create instance view
2023-05-11 11:42:43 +03:00
cserma
3cd4212cdd modified 'create new instance' screen with compute details 2023-05-10 13:59:13 +03:00
cserma
96ea999926 redirect after login fix 2023-05-10 13:57:01 +03:00
cserma
3403d21fea log i18n fixes 2023-05-10 13:57:01 +03:00
cserma
907d47ab78 snapshots tab improvements 2023-05-10 13:57:01 +03:00
cserma
da9fbeaff4 refresh instance pools implementation 2023-04-27 16:04:43 +03:00
cserma
407605761f new login screen 2023-04-27 16:02:29 +03:00
25 changed files with 8705 additions and 11594 deletions

View file

@ -1,30 +1,37 @@
{% extends 'base.html' %}
{% extends "base.html" %}
{% load i18n %}
{% load static %}
{% load django_bootstrap5 %}
{% block title %}WebVirtCloud{% endblock title %}
{% block title %}{% trans "WebVirtCloud" %} - {% trans "Sign In with OTP" %}{% endblock title %}
{% block page_heading %}WebVirtCloud{% endblock page_heading %}
{% block style %}
<link href="{% static "css/signin.css" %}" rel="stylesheet">
{% endblock style %}
{% block content %}
<div class="row">
<div class="col-6 offset-3" role="main">
<div class="card">
<div class="card-body">
{% if form.errors %}
{% bootstrap_form_errors form %}
{% endif %}
<form class="form-signin" method="post" role="form" aria-label="Login form">
{% csrf_token %}
{% bootstrap_field form.username layout='horizontal' %}
{% bootstrap_field form.password layout='horizontal' %}
{% bootstrap_field form.otp_token layout='horizontal' %}
<a href="{% url 'accounts:email_otp' %}" class="float-end mb-2">{% trans "I do not have/lost my OTP!" %}</a>
<button class="btn btn-lg btn-success btn-block" type="submit">{% trans "Sign In" %}</button>
</form>
<div class="login-box">
<div class="page-header">
<a href="/"><h1>WebVirtCloud</h1></a>
</div>
<hr>
<div class="col-12" role="main">
{% if form.errors %}
<div class="alert alert-danger" role="alert">
{% bootstrap_form_errors form %}
</div>
</div>
{% endif %}
<form class="form-signin" method="post" role="form" aria-label="Login form">{% csrf_token %}
<h2 class="form-signin-heading">{% trans "Sign In" %}</h2>
{% bootstrap_field form.username layout='inline' %}
{% bootstrap_field form.password layout='inline' %}
{% bootstrap_field form.otp_token layout='inline'%}
<a href="{% url 'accounts:email_otp' %}" class="float-end">{% trans "I do not have/lost my OTP!" %}</a>
<br>
<div class="d-grid">
<button class="btn btn-lg btn-success" type="submit">{% trans "Sign In" %}</button>
</div>
</form>
</div>
</div>
{% endblock content %}

View file

@ -2,31 +2,35 @@
{% load i18n %}
{% load static %}
{% block title %}{% trans "WebVirtCloud" %} - {% trans "Sign In" %}{% endblock %}
{% block title %}{% trans "WebVirtCloud" %} - {% trans "Sign In" %}{% endblock title %}
{% block style %}
<link href="{% static "css/signin.css" %}" rel="stylesheet">
{% endblock style %}
{% block content %}
<div class="page-header">
<a class="" href="/"><h1>WebVirtCloud</h1></a>
</div>
<div class="col-12" role="main">
{% if form.errors %}
<div class="alert alert-danger">
<button type="button" class="btn-close" data-dismiss="alert" aria-label="Close"></button>
{% trans "Incorrect username or password." %}
</div>
{% endif %}
<form class="form-signin" method="post" role="form" aria-label="Login form">{% csrf_token %}
<h2 class="form-signin-heading">{% trans "Sign In" %}</h2>
<input type="text" class="form-control" name="username" placeholder="{% trans "User" %}" autocapitalize="none" autocorrect="off" autofocus>
<input type="password" class="form-control" name="password" placeholder="{% trans "Password" %}">
<input type="hidden" name="next" value="{{ next }}">
<div class="d-grid">
<button class="btn btn-lg btn-success" type="submit">{% trans "Sign In" %}</button>
</div>
</form>
<div class="login-box">
<div class="page-header">
<a href="/"><h1>WebVirtCloud</h1></a>
</div>
<hr>
<div class="col-12" role="main">
{% if form.errors %}
<div class="alert alert-danger" role="alert">
<button type="button" class="btn-close" data-bs-dismiss="alert" aria-label="Close"></button>
{% trans "Incorrect username or password." %}
</div>
{% endif %}
<form class="form-signin" method="post" role="form" aria-label="Login form">{% csrf_token %}
<h2 class="form-signin-heading">{% trans "Sign In" %}</h2>
<input type="text" class="form-control" name="username" placeholder="{% trans 'User' %}" autocapitalize="none" autocorrect="off" autofocus required>
<input type="password" class="form-control" name="password" placeholder="{% trans 'Password' %}" required>
<input type="hidden" name="next" value="{{ next }}">
<div class="d-grid">
<button class="btn btn-lg btn-success" type="submit">{% trans "Sign In" %}</button>
</div>
</form>
</div>
</div>
{% endblock %}

View file

@ -5,7 +5,7 @@ from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponseRedirect
from django.shortcuts import render
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_noop as _
from logs.views import addlogmsg
from appsettings.models import AppSettings

View file

@ -48,6 +48,10 @@ class Compute(Model):
@cached_property
def cpu_count(self):
return self.proxy.get_node_info()[3]
@cached_property
def cpu_usage(self):
return round(self.proxy.get_cpu_usage().get('usage'))
@cached_property
def ram_size(self):

View file

@ -1,12 +1,12 @@
Django==3.2.18
Django==3.2.19
django_bootstrap5==23.1
django-login-required-middleware==0.9.0
django-otp==1.1.6
django-icons==23.1
django-icons==23.2
django-qr-code==3.1.1
gunicorn==20.1.0
libsass==0.22.0
libvirt-python==9.2.0
libvirt-python==9.3.0
lxml==4.9.2
qrcode==7.4.2
rwlock==0.0.7

View file

@ -1,8 +1,8 @@
-r ../conf/requirements.txt
coverage==7.2.3
coverage==7.2.5
django-debug-toolbar==4.0.0
pycodestyle==2.10.0
pyflakes==3.0.1
pylint==2.17.3
pylint==2.17.4
yapf==0.33.0
black==23.3.0

View file

@ -39,8 +39,8 @@
<script src="{% static 'js/filter-table.js' %}"></script>
{% if request.user.is_superuser %}
<script>
function goto_compute() {
let compute = $("#compute_select").val();
function goto_compute(compute) {
//let compute = $("#compute_select").val();
window.location.href = "{% url 'instances:create_instance_select_type' 1 %}".replace(1, compute);
}
</script>

View file

@ -5,7 +5,7 @@
<!-- Modal pool -->
<div class="modal fade" id="AddInstance" tabindex="-1" role="dialog" aria-labelledby="AddInstanceModal" aria-hidden="true">
<div class="modal-dialog">
<div class="modal-dialog modal-dialog-scrollable modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">{% trans "Choose a compute for new instance" %}</h5>
@ -15,33 +15,50 @@
<form method="post" aria-label="Select compute for instance create form">
{% csrf_token %}
<div class="row">
<label class="col-sm-4 col-form-label">{% trans "Compute" %}</label>
<div class="col-sm-6">
<select class="form-select" id="compute_select">
<option>{% trans "Please select" %}</option>
<table class="table">
<thead>
<tr style="cursor:default;pointer-events:none">
<th>{% trans "Name" %}</th>
<th>{% trans "VCPU" %}
<th>{% trans "Cpu Usage" %}</th>
<th>{% trans "Memory" %}</th>
<th>{% trans "Mem Usage" %}</th>
<th>{% trans "Action" %}</th>
</tr>
</thead>
<tbody>
{% 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>
{% if compute.status is True %}
<tr style="text-decoration: none">
<td>{{ compute.name }}</td>
<td>{{ compute.cpu_count }}</td>
<td>
<div class="progress">
<div class="progress-bar bg-success" role="progressbar" style="width: {{ compute.cpu_usage }}%"
aria-valuenow="{{ compute.cpu_usage }}" aria-valuemin="0" aria-valuemax="100">{{ compute.cpu_usage }}%
</div>
</div>
</td>
<td>{{ 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>
<td class="col-1">
<button class="btn btn-success btn-sm" type="button" onclick="goto_compute('{{ compute.id }}');">
{% trans "Choose" %}
</button>
</td>
</tr>
{% endif %}
{% endfor %}
</select>
</div>
</tbody>
</table>
</div>
</form>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
{% trans "Close" %}
</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 %}
</form>
</div>
</div> <!-- /.modal-content -->
</div> <!-- /.modal-dialog -->

View file

@ -46,11 +46,10 @@
<div role="tabpanel" class="tab-pane tab-pane-bordered" id="takeextsnapshot">
{% if instance.status != 5 %}
<p>{% trans "You can get external snapshots within this tab." %}</p>
<p class="text-primary">{% trans "External snapshots are experimental in this stage, use it if you know what you are doing. It may require manual intervention." %}</p>
{% else %}
<p>{% trans "Create an external snapshot" %}</p>
{% endif %}
<p class="text-danger">{% trans "Give your External Snapshot a <b>distinctive description</b> so it wouldn't get mixed with other snapshots." %}</p>
<p class="fst-italic">{% trans "External snapshots are experimental in this stage, use it if you know what you are doing. 'Revert Snapshot' may require manual operation with CLI." %}</p>
<form action="{% url 'instances:create_external_snapshot' instance.id %}" method="post" role="form" aria-label="Create snapshot form">
{% csrf_token %}
<div class="input-group mb-3">
@ -63,7 +62,7 @@
<input type="submit" class="btn btn-lg btn-success float-end" name="snapshot" value="{% trans "Take Snapshot" %}" onclick="showPleaseWaitDialog();">
{% endif %}
</div>
<p class="text-danger">{% trans "WebVirtCloud supports only one external snapshot at the moment." %}</p>
<p class="text-danger font-monospace small">{% trans "WebVirtCloud supports only one external snapshot at the moment." %}</p>
</form>
<div class="clearfix"></div>
</div> <!--tab pane takeextsnapshot-->

View file

@ -8,7 +8,7 @@
</button>
</li>
<li class="nav-item" role="presentation">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#logs" type="button" role="tab" aria-controls="logs" aria-selected="false">
<button class="nav-link" data-bs-toggle="tab" data-bs-target="#logs" type="button" role="tab" aria-controls="logs" aria-selected="false" onclick="update_logs_table(vname);">
{% trans "Logs" %}
</button>
</li>

View file

@ -19,7 +19,7 @@ from django.contrib.auth.models import User
from django.http import Http404, HttpResponse, JsonResponse
from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_noop as _
from libvirt import (VIR_DOMAIN_UNDEFINE_KEEP_NVRAM,
VIR_DOMAIN_UNDEFINE_NVRAM,
VIR_DOMAIN_START_PAUSED,

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -3,7 +3,7 @@ from computes.models import Compute
from django.contrib import messages
from django.http import HttpResponseRedirect
from django.shortcuts import get_object_or_404, render
from django.utils.translation import gettext_lazy as _
from django.utils.translation import gettext_noop as _
from libvirt import libvirtError
from logs.views import addlogmsg
from vrtManager import util

View file

@ -1,3 +1,23 @@
/* body {
background: rgba(203,203,203,1)
} */
.login-box {
margin-top: 25%;
display: block;
align-content: center;
align-items: center;
box-shadow: 0px 0px 20px black;
border-radius: 5px;
padding-top: 5%;
padding-bottom: 1%;
}
h1 {
color:#212529;
}
h2 {
color:#212529;
}
.form-signin {
max-width: 330px;
padding: 15px;
@ -29,15 +49,22 @@
}
.form-signin input[type="text"] {
margin-bottom: -1px;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
margin-bottom: 10px;
border-radius: 5px;
}
.form-signin input[type="password"] {
margin-bottom: 10px;
border-top-left-radius: 0;
border-top-right-radius: 0;
border-radius: 5px;
}
.btn-success {
border-width: 2px;
border-style: solid;
}
.btn-success:hover {
border-width: 2px;
border-style: solid;
}
.logout {

View file

@ -2,7 +2,6 @@ import contextlib
import json
import os.path
import time
import subprocess
try:
from libvirt import (
@ -1255,6 +1254,7 @@ class wvmInstance(wvmConnect):
return iso
def delete_all_disks(self):
self.refresh_instance_pools()
disks = self.get_disk_devices()
for disk in disks:
vol = self.get_volume_by_path(disk.get("path"))
@ -1318,8 +1318,8 @@ class wvmInstance(wvmConnect):
</domainsnapshot>"""
self._snapshotCreateXML(xml, VIR_DOMAIN_SNAPSHOT_CREATE_DISK_ONLY)
self.refresh_instance_pools()
def get_external_snapshots(self):
return self.get_snapshot(VIR_DOMAIN_SNAPSHOT_LIST_EXTERNAL)
@ -1350,29 +1350,23 @@ class wvmInstance(wvmConnect):
def revert_external_snapshot(self, name, date, desc):
pool = None
snap = self.instance.snapshotLookupByName(name, 0)
snap_xml = snap.getXMLDesc(0)
snapXML = ElementTree.fromstring(snap_xml)
disks = snapXML.findall('inactiveDomain/devices/disk')
if not disks: disks = snapXML.findall('domain/devices/disk')
self.start(flags=VIR_DOMAIN_START_PAUSED) if self.get_status() == 5 else None
disk_info = self.get_disk_devices()
for disk in disk_info:
vol_snap = self.get_volume_by_path(disk["path"])
pool = vol_snap.storagePoolLookupByVolume()
pool.refresh(0)
vol_snap.delete(0)
self.delete_all_disks()
self.force_shutdown()
snap.delete(VIR_DOMAIN_SNAPSHOT_DELETE_METADATA_ONLY)
disks = snapXML.findall('inactiveDomain/devices/disk')
if not disks: disks = snapXML.findall('domain/devices/disk')
for disk in disks:
self.instance.updateDeviceFlags(ElementTree.tostring(disk).decode("UTF-8"))
name = name.replace("s1", "s2")
self.create_external_snapshot(name, date, desc)
pool.refresh() if pool else None
def get_snapshot(self, flag=VIR_DOMAIN_SNAPSHOT_LIST_INTERNAL):
snapshots = []
@ -1413,6 +1407,15 @@ class wvmInstance(wvmConnect):
def get_wvmStorages(self):
return wvmStorages(self.host, self.login, self.passwd, self.conn)
def refresh_instance_pools(self):
disks = self.get_disk_devices()
target_paths = set()
for disk in disks:
disk_path = disk.get("path")
target_paths.add(os.path.dirname(disk_path))
for target_path in target_paths:
self.get_wvmStorages().get_pool_by_target(target_path).refresh(0)
def fix_mac(self, mac):
if ":" in mac:
return mac

View file

@ -28,6 +28,14 @@ class wvmStorages(wvmConnect):
def define_storage(self, xml, flag):
self.wvm.storagePoolDefineXML(xml, flag)
def get_pool_by_target(self, target):
pool_names = self.get_storages()
for pool_name in pool_names:
stg = wvmStorage(self.host, self.login, self.passwd, self.conn, pool_name)
if stg.get_target_path() == target:
return self.get_storage(pool_name)
return None
def create_storage(self, stg_type, name, source, target):
xml = f"""<pool type='{stg_type}'>
<name>{name}</name>"""

View file

@ -120,6 +120,8 @@ LOGIN_URL = "/accounts/login/"
LOGOUT_REDIRECT_URL = "/accounts/login/"
LOGIN_REDIRECT_URL="/instances/"
LANGUAGE_CODE = "en-us"
TIME_ZONE = "UTC"