1
0
Fork 0
mirror of https://github.com/retspen/webvirtcloud synced 2026-07-14 23:45:41 +00:00

Compare commits

..

No commits in common. "af774ebcc85767e8139876c142dff892739c3427" and "c0f31909bd0e73d25920468f9a78f9ae5f11446c" have entirely different histories.

4 changed files with 49 additions and 210 deletions

View file

@ -15,7 +15,6 @@ libvirt-python==11.4.0
lxml==6.0.0 lxml==6.0.0
ldap3==2.9.1 ldap3==2.9.1
markdown==3.8.2 markdown==3.8.2
paramiko==3.4.0
#psycopg2-binary #psycopg2-binary
python-engineio==4.12.0 python-engineio==4.12.0
python-socketio==5.13.0 python-socketio==5.13.0

View file

@ -16,27 +16,20 @@
<h5 class="modal-title">{% trans "Upload ISO Image" %}</h5> <h5 class="modal-title">{% trans "Upload ISO Image" %}</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button> <button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div> </div>
<form id="isoUploadForm" enctype="multipart/form-data" method="post" role="form" aria-label="Upload iso form">{% csrf_token %} <div class="modal-body">
<div class="modal-body"> <form enctype="multipart/form-data" method="post" role="form" aria-label="Upload iso form">{% csrf_token %}
<div class="row mb-3"> <div class="row">
<label for="id_file" class="col-sm-3 col-form-label">{% trans "Name" %}</label> <label class="col-sm-3 col-form-label">{% trans "Name" %}</label>
<div class="col-sm-9"> <div class="col-sm-6">
<input type="file" name="file" id="id_file" class="form-control" required> <input type="file" name="file" id="id_file">
</div> </div>
</div> </div>
<div id="upload-progress-container" class="mt-3" style="display: none;"> </div>
<div class="progress"> <div class="modal-footer">
<div id="upload-progress-bar" class="progress-bar" role="progressbar" style="width: 0%;" aria-valuenow="0" aria-valuemin="0" aria-valuemax="100">0%</div> <button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
</div> <button type="submit" class="btn btn-primary" name="iso_upload">{% trans "Upload" %}</button>
</div> </div>
<div id="upload-error-message" class="alert alert-danger mt-3" style="display: none;"></div> </form>
</div>
<div class="modal-footer">
<input type="hidden" name="iso_upload" value="true">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">{% trans "Close" %}</button>
<button type="submit" class="btn btn-primary">{% trans "Upload" %}</button>
</div>
</form>
</div> <!-- /.modal-content --> </div> <!-- /.modal-content -->
</div> <!-- /.modal-dialog --> </div> <!-- /.modal-dialog -->
@ -69,4 +62,4 @@
</div> <!-- /.modal --> </div> <!-- /.modal -->
{% endif %} {% endif %}
{% endif %} {% endif %}
{% endif %} {% endif %}

View file

@ -58,31 +58,32 @@
<dd class="col-6">{{ used|filesizeformat }}</dd> <dd class="col-6">{{ used|filesizeformat }}</dd>
<dt class="col-6">{% trans "State" %}</dt> <dt class="col-6">{% trans "State" %}</dt>
<dd class="col-6"> <dd class="col-6">
<form action="" method="post" role="form" aria-label="Storage start/stop form" class="confirm-form">{% csrf_token %} <form action="" method="post" role="form" aria-label="Storage start/stop form">{% csrf_token %}
{% if state == 0 %} {% if state == 0 %}
<input type="submit" class="btn btn-sm btn-secondary" name="start" value="{% trans "Start" %}"> <input type="submit" class="btn btn-sm btn-secondary" name="start" value="{% trans "Start" %}">
<input type="submit" class="btn btn-sm btn-danger" name="delete" value="{% trans "Delete" %}"> <input type="submit" class="btn btn-sm btn-danger" name="delete" value="{% trans "Delete" %}"
onclick="return confirm('{% trans "Are you sure?" %}')">
{% else %} {% else %}
<input type="submit" class="btn btn-sm btn-secondary" name="stop" value="{% trans "Stop" %}"> <input type="submit" class="btn btn-sm btn-secondary" name="stop" value="{% trans "Stop" %}"
onclick="return confirm('{% trans "Are you sure?" %}')">
{% endif %} {% endif %}
</form> </form>
</dd> </dd>
<dt class="col-6">{% trans "Autostart" %}</dt> <dt class="col-6">{% trans "Autostart" %}</dt>
<dd class="col-6"> <dd class="col-6">
<form action="" method="post" role="form" aria-label="Storage disable/enable autostart form" class="confirm-form">{% csrf_token %} <form action="" method="post" role="form" aria-label="Storage disable/enable autostart form">{% csrf_token %}
{% if autostart == 0 %} {% if autostart == 0 %}
<input type="submit" class="btn btn-sm btn-secondary" name="set_autostart" <input type="submit" class="btn btn-sm btn-secondary" name="set_autostart"
value="{% trans "Enable" %}"> value="{% trans "Enable" %}">
{% else %} {% else %}
<input type="submit" class="btn btn-sm btn-secondary" name="unset_autostart" <input type="submit" class="btn btn-sm btn-secondary" name="unset_autostart"
value="{% trans "Disable" %}"> onclick="return confirm('{% trans "Are you sure?" %}')" value="{% trans "Disable" %}">
{% endif %} {% endif %}
</form> </form>
</dd> </dd>
</dl> </dl>
</div> </div>
</div> </div>
{% if state %} {% if state %}
<p> <p>
@ -170,9 +171,9 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
<form action="" method="post" role="form" aria-label="Delete volume form" class="confirm-form">{% csrf_token %} <form action="" method="post" role="form" aria-label="Delete volume form">{% csrf_token %}
<input type="hidden" name="volname" value="{{ volume.name }}"> <input type="hidden" name="volname" value="{{ volume.name }}">
<button type="submit" class="btn btn-sm btn-secondary" name="del_volume" title="{% trans "Delete" %}"> <button type="submit" class="btn btn-sm btn-secondary" name="del_volume" title="{% trans "Delete" %}" onclick="return confirm('{% trans "Are you sure?" %}')">
{% bs_icon 'trash' %} {% bs_icon 'trash' %}
</button> </button>
</form> </form>
@ -215,80 +216,5 @@
$('.meta-prealloc').hide(); $('.meta-prealloc').hide();
} }
}); });
$(document).ready(function() {
$('.confirm-form').on('submit', function(e) {
if (!confirm('{% trans "Are you sure?"|escapejs %}')) {
e.preventDefault();
}
});
$('#isoUploadForm').on('submit', function(e) {
e.preventDefault();
var fileInput = $('#id_file')[0];
if (fileInput.files.length === 0) {
alert("{% trans 'Please select a file to upload.'|escapejs %}");
return;
}
var file = fileInput.files[0];
var fileName = file.name;
var chunkSize = 10 * 1024 * 1024; // 10MB
var totalChunks = Math.ceil(file.size / chunkSize);
var chunkIndex = 0;
var progressBar = $('#upload-progress-bar');
var progressContainer = $('#upload-progress-container');
var errorMessage = $('#upload-error-message');
progressContainer.show();
progressBar.width('0%').attr('aria-valuenow', 0).text('0%');
errorMessage.hide();
function uploadChunk() {
if (chunkIndex >= totalChunks) {
return;
}
var start = chunkIndex * chunkSize;
var end = Math.min(start + chunkSize, file.size);
var chunk = file.slice(start, end);
var formData = new FormData();
formData.append('file', chunk, fileName);
formData.append('file_name', fileName);
formData.append('chunk_index', chunkIndex);
formData.append('total_chunks', totalChunks);
formData.append('iso_upload', 'true');
formData.append('csrfmiddlewaretoken', '{{ csrf_token }}');
$.ajax({
url: "{% url 'storage' compute.id pool %}",
type: 'POST',
data: formData,
processData: false,
contentType: false,
success: function(data) {
chunkIndex++;
var percentComplete = Math.round((chunkIndex / totalChunks) * 100);
progressBar.width(percentComplete + '%').attr('aria-valuenow', percentComplete).text(percentComplete + '%');
if (data.reload) {
location.reload();
} else if (chunkIndex < totalChunks) {
uploadChunk();
}
},
error: function(jqXHR, textStatus, errorThrown) {
var errorText = jqXHR.responseJSON && jqXHR.responseJSON.error ? jqXHR.responseJSON.error : "{% trans 'An error occurred during upload.'|escapejs %}";
errorMessage.text(errorText).show();
progressContainer.hide();
}
});
}
uploadChunk();
});
});
</script> </script>
{% endblock %} {% endblock %}

View file

@ -5,14 +5,11 @@ from admin.decorators import superuser_only
from appsettings.settings import app_settings from appsettings.settings import app_settings
from computes.models import Compute from computes.models import Compute
from django.contrib import messages from django.contrib import messages
from django.http import HttpResponse, HttpResponseRedirect, JsonResponse from django.http import HttpResponse, HttpResponseRedirect
from django.shortcuts import get_object_or_404, redirect, render from django.shortcuts import get_object_or_404, redirect, render
from django.urls import reverse from django.urls import reverse
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from libvirt import libvirtError from libvirt import libvirtError
import paramiko
from vrtManager.connection import CONN_SSH, CONN_SOCKET
from vrtManager.storage import wvmStorage, wvmStorages from vrtManager.storage import wvmStorage, wvmStorages
from storages.forms import AddStgPool, CloneImage, CreateVolumeForm from storages.forms import AddStgPool, CloneImage, CreateVolumeForm
@ -105,52 +102,20 @@ def storage(request, compute_id, pool):
:param pool: :param pool:
:return: :return:
""" """
def handle_uploaded_file(conn, path, file_name, file_chunk, is_last_chunk):
temp_name = f"{file_name}.part"
target_temp = os.path.normpath(os.path.join(path, temp_name))
target_final = os.path.normpath(os.path.join(path, file_name))
if not target_temp.startswith(path) or not target_final.startswith(path): def handle_uploaded_file(path, f_name):
target = os.path.normpath(os.path.join(path, str(f_name)))
if not target.startswith(path):
raise Exception(_("Security Issues with file uploading")) raise Exception(_("Security Issues with file uploading"))
if conn.conn == CONN_SSH: try:
try: with open(target, "wb+") as f:
hostname, port = conn.host, 22 for chunk in f_name.chunks():
if ":" in hostname: f.write(chunk)
hostname, port_str = hostname.split(":") except FileNotFoundError:
port = int(port_str) messages.error(
request, _("File not found. Check the path variable and filename")
ssh = paramiko.SSHClient() )
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=hostname, port=port, username=conn.login, password=conn.passwd)
sftp = ssh.open_sftp()
remote_file = sftp.open(target_temp, 'ab')
remote_file.set_pipelined(True)
for chunk_data in file_chunk.chunks():
remote_file.write(chunk_data)
remote_file.close()
if is_last_chunk:
sftp.rename(target_temp, target_final)
sftp.close()
ssh.close()
except Exception as e:
raise Exception(_("SSH upload failed: {}").format(e))
elif conn.conn == CONN_SOCKET:
try:
with open(target_temp, "ab") as f:
for chunk_data in file_chunk.chunks():
f.write(chunk_data)
if is_last_chunk:
if os.path.exists(target_final):
os.remove(target_final)
os.rename(target_temp, target_final)
except FileNotFoundError:
raise Exception(_("File not found. Check the path variable and filename"))
else:
raise Exception(_("Unsupported connection type for file upload."))
compute = get_object_or_404(Compute, pk=compute_id) compute = get_object_or_404(Compute, pk=compute_id)
meta_prealloc = False meta_prealloc = False
@ -162,16 +127,12 @@ def storage(request, compute_id, pool):
storages = conn.get_storages() storages = conn.get_storages()
state = conn.is_active() state = conn.is_active()
try: size, free = conn.get_size()
size, free = conn.get_size() used = size - free
used = size - free if state:
if state: percent = (used * 100) // size
percent = (used * 100) // size else:
else: percent = 0
percent = 0
except libvirtError:
size, free, used, percent = 0, 0, 0, 0
status = conn.get_status() status = conn.get_status()
path = conn.get_target_path() path = conn.get_target_path()
type = conn.get_type() type = conn.get_type()
@ -209,56 +170,16 @@ def storage(request, compute_id, pool):
return redirect(reverse("storage", args=[compute.id, pool])) return redirect(reverse("storage", args=[compute.id, pool]))
# return HttpResponseRedirect(request.get_full_path()) # return HttpResponseRedirect(request.get_full_path())
if "iso_upload" in request.POST: if "iso_upload" in request.POST:
file_chunk = request.FILES.get("file") if str(request.FILES["file"]) in conn.update_volumes():
if not file_chunk: error_msg = _("ISO image already exist")
return JsonResponse({"error": _("No file chunk was submitted.")}, status=400)
file_name = request.POST.get("file_name")
chunk_index = int(request.POST.get("chunk_index", 0))
total_chunks = int(request.POST.get("total_chunks", 1))
is_last_chunk = chunk_index == total_chunks - 1
# On first chunk, check if file already exists
if chunk_index == 0:
if file_name in conn.get_volumes():
return JsonResponse({"error": _("ISO image already exists")}, status=400)
# Clean up any partial files from previous failed uploads
temp_part_file = os.path.normpath(os.path.join(path, f"{file_name}.part"))
if conn.conn == CONN_SOCKET and os.path.exists(temp_part_file):
os.remove(temp_part_file)
elif conn.conn == CONN_SSH:
try:
hostname, port = conn.host, 22
if ":" in hostname:
hostname, port_str = hostname.split(":")
port = int(port_str)
ssh = paramiko.SSHClient()
ssh.set_missing_host_key_policy(paramiko.AutoAddPolicy())
ssh.connect(hostname=hostname, port=port, username=conn.login, password=conn.passwd)
sftp = ssh.open_sftp()
try:
sftp.remove(temp_part_file)
except FileNotFoundError:
pass # File doesn't exist, which is fine
sftp.close()
ssh.close()
except Exception:
# Best effort to clean up, if it fails, let it be.
pass
try:
handle_uploaded_file(conn, path, file_name, file_chunk, is_last_chunk)
if is_last_chunk:
success_msg = _("ISO: %(file)s has been uploaded successfully.") % {"file": file_name}
messages.success(request, success_msg)
return JsonResponse({"success": True, "message": success_msg, "reload": True})
else:
return JsonResponse({"success": True, "message": "Chunk received."})
except Exception as e:
error_msg = str(e)
messages.error(request, error_msg) messages.error(request, error_msg)
return JsonResponse({"error": error_msg}, status=500) else:
handle_uploaded_file(path, request.FILES["file"])
messages.success(
request,
_("ISO: %(file)s is uploaded.") % {"file": request.FILES["file"]},
)
return HttpResponseRedirect(request.get_full_path())
if "cln_volume" in request.POST: if "cln_volume" in request.POST:
form = CloneImage(request.POST) form = CloneImage(request.POST)
if form.is_valid(): if form.is_valid():