mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-11 12:46:49 +00:00
feat(instances): a running VM can be cloned
libvirt's backup copies the disks of a running or paused VM from one instant into new volumes while it keeps running; the clone is defined shut off as before. The VM is held for the copy, the copy can be cancelled, and recovery reads the job's records and libvirt.
This commit is contained in:
parent
39ad472a7c
commit
46a611941e
10 changed files with 653 additions and 19 deletions
|
|
@ -70,8 +70,9 @@ function jobPanel(element, urls, csrfToken, initialIds) {
|
|||
|
||||
function outcomeHtml(job) {
|
||||
var left = (job.result && job.result.left_behind) || [];
|
||||
var warn = left.length ? '<div class="small mt-2">Files not removed:</div><ul class="list-unstyled small mb-0">'
|
||||
+ left.map(function (path) { return '<li class="text-break"><code>' + esc(path) + "</code></li>"; }).join("") + "</ul>" : "";
|
||||
var note = job.result && job.result.warning ? '<div class="small mt-2">' + esc(job.result.warning) + "</div>" : "";
|
||||
var warn = note + (left.length ? '<div class="small mt-2">Files not removed:</div><ul class="list-unstyled small mb-0">'
|
||||
+ left.map(function (path) { return '<li class="text-break"><code>' + esc(path) + "</code></li>"; }).join("") + "</ul>" : "");
|
||||
var buttons = '<button type="button" class="btn btn-sm btn-primary" data-refresh="1">Refresh</button>'
|
||||
+ '<button type="button" class="btn btn-sm btn-outline-secondary" data-dismiss="' + esc(job.id)
|
||||
+ '">Dismiss</button>';
|
||||
|
|
|
|||
|
|
@ -56,6 +56,9 @@
|
|||
</ul>
|
||||
</dd>
|
||||
{% endif %}
|
||||
{% if job.result.warning %}
|
||||
<dt class="col-sm-3">{% trans "Warning" %}</dt><dd class="col-sm-9 text-break">{{ job.result.warning }}</dd>
|
||||
{% endif %}
|
||||
{% if job.result.links %}
|
||||
<dt class="col-sm-3">{% trans "Results" %}</dt>
|
||||
<dd class="col-sm-9">
|
||||
|
|
|
|||
|
|
@ -92,8 +92,9 @@ assert.strictEqual(window.backgroundJobRunning, true, "still true while the outc
|
|||
answer("/j/a", { status: 0 }, false); // a network error: asked again on the next poll
|
||||
advance(3000);
|
||||
answer("/r", empty);
|
||||
answer("/j/a", { ...running, state: "succeeded", result: { left_behind: ["/pool/a.one"] } });
|
||||
answer("/j/a", { ...running, state: "succeeded", result: { left_behind: ["/pool/a.one"], warning: "not <thawed>" } });
|
||||
assert.ok(el.innerHTML.includes('<li class="text-break"><code>/pool/a.one</code></li>'), "shows the warning from the result");
|
||||
assert.ok(el.innerHTML.includes('<div class="small mt-2">not <thawed></div>'), "shows the result's warning escaped");
|
||||
assert.ok(el.innerHTML.includes('class="alert alert-success') && el.innerHTML.includes('<span class="badge bg-success">succeeded</span>')
|
||||
&& el.innerHTML.includes('<div class="ms-auto d-flex gap-2"><button type="button" class="btn btn-sm btn-primary" data-refresh'),
|
||||
"the outcome has the state colour and its buttons on the right");
|
||||
|
|
|
|||
|
|
@ -73,6 +73,13 @@ class JobsPageTestCase(TransactionTestCase):
|
|||
self.assertNotContains(response, "<script>x</script>")
|
||||
self.assertContains(response, "Waiting for recovery")
|
||||
|
||||
def test_the_detail_shows_the_results_warning_escaped(self):
|
||||
Operation.objects.filter(pk=self.op.pk).update(result={"warning": "not <b>thawed</b>"},
|
||||
state=Operation.SUCCEEDED)
|
||||
self.client.force_login(self.user)
|
||||
response = self.client.get(reverse("operations:job", args=[self.op.pk]))
|
||||
self.assertContains(response, "not <b>thawed</b>")
|
||||
|
||||
def test_pagination_keeps_the_filters(self):
|
||||
for i in range(51):
|
||||
Operation.objects.create(kind="test.noop", user=self.user, username="page_user",
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue