mirror of
https://github.com/retspen/webvirtcloud
synced 2026-11-09 19:56:48 +00:00
feat(instances): a clone takes the size asked and starts if asked
A VM made from a template came out at the template's size and shut off, and was then resized and started by hand. The Clone tab now asks for the clone's vCPUs and memory, filled in with the VM's, and whether to start it. After the copy, before the clone is recorded, it is resized with the Resize tab's own functions and started; a step that fails is a warning of the job, and the clone stays. The quota counts the larger of the size asked and the VM's, so a resize that fails leaves the clone at a size it allowed. When the job ends the page opens the clone's, not the cloned VM's again: a template's page, shut off, read as a clone that had not started. An outcome kept for a warning links to it.
This commit is contained in:
parent
9aedb5b4d1
commit
8a809443a4
9 changed files with 423 additions and 233 deletions
|
|
@ -74,7 +74,8 @@ function jobPanel(element, urls, csrfToken, initialIds) {
|
|||
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>'
|
||||
var buttons = (opens(job) ? '<a class="btn btn-sm btn-primary" href="' + esc(job.result.open) + '">Open</a>' : "")
|
||||
+ '<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>';
|
||||
return alertHtml(job.state, header(job.label, job.state, "", buttons) + errorHtml(job) + warn);
|
||||
|
|
@ -114,6 +115,11 @@ function jobPanel(element, urls, csrfToken, initialIds) {
|
|||
holding = busy;
|
||||
}
|
||||
|
||||
// the page of what the job made, a path of this site
|
||||
function opens(job) {
|
||||
return !!(job.result && /^\/[^\/]/.test(job.result.open || ""));
|
||||
}
|
||||
|
||||
// a job that did what it was asked, with nothing more to read: the page shows its effect
|
||||
function quiet(job) {
|
||||
var result = job.result || {};
|
||||
|
|
@ -134,7 +140,13 @@ function jobPanel(element, urls, csrfToken, initialIds) {
|
|||
var others = Object.keys(outcomes).length + Object.keys(pending).length + Object.keys(shown).length;
|
||||
// the page in use (poll.js): its outcome waits in the panel, with Refresh
|
||||
if (quiet(job) && !pageInUse() && !others) {
|
||||
window.location.reload(); // the job is not remembered again: no loop
|
||||
// a job that made a VM opens its page (result.open); another reloads this one,
|
||||
// where the job is not remembered again: no loop
|
||||
if (opens(job)) {
|
||||
window.location.assign(job.result.open);
|
||||
} else {
|
||||
window.location.reload();
|
||||
}
|
||||
return;
|
||||
}
|
||||
outcomes[id] = job;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,8 @@ function advance(ms) {
|
|||
const realNow = Date.now;
|
||||
Date.now = () => 1700000000000 + now;
|
||||
global.document = { hidden: false, addEventListener() {}, activeElement: null, querySelector: () => null };
|
||||
global.window = { backgroundJobRunning: false, location: { reload() { window.reloaded = true; } } };
|
||||
global.window = { backgroundJobRunning: false,
|
||||
location: { reload() { window.reloaded = true; }, assign(url) { window.opened = url; } } };
|
||||
|
||||
// requests settle only when the test says so; callbacks run in registration order
|
||||
let open = [];
|
||||
|
|
@ -264,6 +265,26 @@ answer("/j/u", { ...running, id: "u", label: "Upload", state: "succeeded" });
|
|||
assert.ok(window.reloaded, "a job that succeeds without a warning reloads the page: it shows the job's effect");
|
||||
assert.ok(!el11.innerHTML.includes('<span class="badge bg-success">succeeded</span>'), "not shown as well");
|
||||
|
||||
// a job that made a VM opens its page; a warning keeps the outcome, with Open; only a path of this site
|
||||
open = []; timers = []; window.reloaded = false; window.opened = null;
|
||||
const elv = { innerHTML: "", addEventListener() {} };
|
||||
jobPanel(elv, urls, "tok", ["v"]);
|
||||
advance(0);
|
||||
answer("/r", empty);
|
||||
answer("/j/v", { ...running, id: "v", label: "Clone VM", state: "succeeded", result: { open: "/instances/9/" } });
|
||||
assert.ok(window.opened === "/instances/9/" && !window.reloaded, "opens the clone's page");
|
||||
for (const [result, shown] of [[{ open: "/instances/9/", warning: "not started" }, true],
|
||||
[{ open: "//evil.example.com/" }, false], [{ open: "javascript:alert(1)" }, false]]) {
|
||||
open = []; timers = []; window.reloaded = false; window.opened = null;
|
||||
const elw = { innerHTML: "", addEventListener() {} };
|
||||
jobPanel(elw, urls, "tok", ["w"]);
|
||||
advance(0);
|
||||
answer("/r", empty);
|
||||
answer("/j/w", { ...running, id: "w", state: "succeeded", result });
|
||||
assert.strictEqual(window.opened, null, JSON.stringify(result));
|
||||
assert.strictEqual(elw.innerHTML.includes('href="/instances/9/">Open</a>'), shown, JSON.stringify(result));
|
||||
}
|
||||
|
||||
// a dialog open or a field in use: the outcome waits in the panel, with Refresh
|
||||
for (const [label, doc] of [["dialog", { querySelector: (s) => s === ".modal.show" ? {} : null }],
|
||||
["field", { activeElement: { matches: (s) => s === "input, select, textarea" } }]]) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue