Add a form to provision a new instance

This commit is contained in:
Tom Wiesing 2023-02-27 11:10:28 +01:00
parent 80906d3791
commit 53f63d4efd
No known key found for this signature in database
25 changed files with 367 additions and 236 deletions

View file

@ -1,6 +1,10 @@
import "~/src/lib/remote"
// setup highlighting
import "~/src/lib/highlight"
// setup remote actions
import setup from "~/src/lib/remote"
setup();
// include the user styles!
import "../User/index.ts"
import "../User/index.css"

View file

@ -0,0 +1,32 @@
import "../Admin/index.ts"
import "../Admin/index.css"
import { createModal } from "~/src/lib/remote"
const provision = document.getElementById("provision") as HTMLFormElement;
const slug = document.getElementById("slug") as HTMLInputElement;
// add an event handler to open the modal form!
provision.addEventListener('submit', (evt) => {
evt.preventDefault();
// flags used to create the server
const flags = { Slug: slug.value };
// open a modal to provision a new instance
createModal("provision", [JSON.stringify(flags)], {
bufferSize: 0,
onClose: (success: boolean) => {
if (success) {
location.href = "/admin/instance/" + flags.Slug
} else {
location.reload();
}
},
})
})
// enable the form!
provision.querySelector('fieldset')?.removeAttribute('disabled');

View file

@ -46,163 +46,128 @@ function makeTextBuffer(target: HTMLElement, scrollContainer: HTMLElement, size:
return println;
}
const remote_action = document.getElementsByClassName('remote-action')
Array.from(remote_action).forEach((element) => {
const action = element.getAttribute('data-action') as string;
const reload = element.getAttribute('data-force-reload');
const param = element.getAttribute('data-param') as string | undefined;
export default function setup() {
const remote_action = document.getElementsByClassName('remote-action')
Array.from(remote_action).forEach((element) => {
const action = element.getAttribute('data-action') as string;
const reload = element.getAttribute('data-force-reload');
const param = element.getAttribute('data-param') as string | undefined;
const confirmElementName = element.getAttribute('data-confirm-param');
const confirmElement = (confirmElementName ? document.querySelector(confirmElementName) : null) as HTMLInputElement | null;
const confirmElementName = element.getAttribute('data-confirm-param');
const confirmElement = (confirmElementName ? document.querySelector(confirmElementName) : null) as HTMLInputElement | null;
const bufferSize = (function () {
const number = parseInt(element.getAttribute('data-buffer') ?? "", 10) ?? 0;
return (isFinite(number) && number > 0) ? number : 0;
})()
const validate = function() {
if (!confirmElement) return true
return confirmElement.value === param;
}
if (confirmElement) {
const runValidation = () => {
if (validate()) {
element.removeAttribute('disabled')
} else {
element.setAttribute('disabled', 'disabled')
}
const bufferSize = (function () {
const number = parseInt(element.getAttribute('data-buffer') ?? "", 10) ?? 0;
return (isFinite(number) && number > 0) ? number : 0;
})()
const validate = function() {
if (!confirmElement) return true
return confirmElement.value === param;
}
confirmElement.addEventListener('change', runValidation)
runValidation()
}
element.addEventListener('click', function (ev) {
ev.preventDefault();
// do nothing if the validation fails
if (!validate()) return;
// create a modal dialog and append it to the body
const modal = document.createElement("div")
modal.className = "modal-terminal"
document.body.append(modal)
// create a <pre> to write stuff into
const target = document.createElement("pre")
const println = makeTextBuffer(target, modal, bufferSize)
modal.append(target)
// create a button to eventually close everything
const button = document.createElement("button")
button.className = "pure-button pure-button-success"
button.append(typeof reload === 'string' ? "Close & Reload" : "Close")
button.addEventListener('click', function (event) {
event.preventDefault();
if (typeof reload === 'string') {
button.setAttribute('disabled', 'disabled')
target.innerHTML = 'Reloading page ...'
if (reload === '') {
location.reload()
if (confirmElement) {
const runValidation = () => {
if (validate()) {
element.removeAttribute('disabled')
} else {
location.href = reload
element.setAttribute('disabled', 'disabled')
}
return;
}
modal.parentNode?.removeChild(modal);
})
const onbeforeunload = window.onbeforeunload;
window.onbeforeunload = () => "A remote session is in progress. Are you sure you want to leave?";
// when closing, add a button to the modal!
let didClose = false
const close = function () {
if (didClose) return
didClose = true
window.onbeforeunload = onbeforeunload;
modal.append(button)
// DEBUG: print terminal stats!
// const quota = (println.paintedFrames / (println.missedFrames + println.paintedFrames)) * 100
// println(`Terminal: painted=${println.paintedFrames} missed=${println.missedFrames} (${quota}%)`, true)
confirmElement.addEventListener('change', runValidation)
runValidation()
}
println("Connecting ...", true)
// connect to the socket and send the action
connectSocket((socket) => {
println("Connected", true)
socket.send(action);
if (typeof param === 'string') {
socket.send(param);
let onClose: (success: boolean) => void | null;
if (typeof reload === 'string') {
onClose = () => {
if (reload === '') location.reload();
else location.href = reload;
}
}, (data) => {
println(data);
}).then(() => {
println("Connection closed.", true)
close();
}).catch(() => {
println("Connection errored.", true)
close();
});
});
})
const remote_link = document.getElementsByClassName('remote-link')
Array.from(remote_link).forEach((element) => {
const action = element.getAttribute('data-action') as string;
const param = element.getAttribute('data-params') as string | undefined;
const params = param?.split(" ");
element.addEventListener('click', function (ev) {
ev.preventDefault();
getValue(action, params).then(v => {
window.open(v);
}).catch(e => {
console.error(e);
})
});
})
async function getValue(action: string, params?: Array<string>): Promise<any> {
return new Promise((rs, rj) => {
let buffer = "";
var resolve = function() {
const index = buffer.indexOf('\n')
if (index < 0) {
rj("invalid buffer");
return
}
// check that the server sent back true
const ok = buffer.substring(0, index) === 'true';
if(!ok) {
rj(buffer);
return
}
// parse the rest as json
const value = JSON.parse(buffer.substring(index+1))
rs(value);
}
element.addEventListener('click', function (ev) {
ev.preventDefault();
// do nothing if the validation fails
if (!validate()) return;
connectSocket((socket) => {
socket.send(action);
if (params) {
params.forEach(p => socket.send(p))
}
}, (data) => {
buffer += data + "\n";
}).then(() => {
resolve();
}).catch(() => {
buffer = "false\n";
resolve();
// create a modal dialog
const params = (typeof param === 'string') ? [param] : [];
createModal(action, params, {
onClose: onClose,
bufferSize: bufferSize,
});
});
})
}
type ModalOptions = {
bufferSize: number;
onClose: (success: boolean) => void
}
export function createModal(action: string, params: string[], opts: Partial<ModalOptions>) {
// create a modal dialog and append it to the body
const modal = document.createElement("div")
modal.className = "modal-terminal"
document.body.append(modal)
// create a <pre> to write stuff into
const target = document.createElement("pre")
const println = makeTextBuffer(target, modal, opts.bufferSize ?? 1000)
modal.append(target)
// create a button to eventually close everything
const button = document.createElement("button")
button.className = "pure-button pure-button-success"
button.append(typeof opts?.onClose === 'function' ? "Close & Finish" : "Close")
let success = false;
button.addEventListener('click', function (event) {
event.preventDefault();
if (typeof opts?.onClose === 'function') {
button.setAttribute('disabled', 'disabled')
target.innerHTML = 'Finishing up ...'
opts.onClose(success)
return;
}
modal.parentNode?.removeChild(modal);
})
const onbeforeunload = window.onbeforeunload;
window.onbeforeunload = () => "A remote session is in progress. Are you sure you want to leave?";
// when closing, add a button to the modal!
let didClose = false
const close = function () {
if (didClose) return
didClose = true
window.onbeforeunload = onbeforeunload;
modal.append(button)
// DEBUG: print terminal stats!
// const quota = (println.paintedFrames / (println.missedFrames + println.paintedFrames)) * 100
// println(`Terminal: painted=${println.paintedFrames} missed=${println.missedFrames} (${quota}%)`, true)
}
println("Connecting ...", true)
// connect to the socket and send the action
connectSocket((socket) => {
println("Connected", true)
socket.send(action)
params.forEach(p => socket.send(p))
}, (data) => {
println(data);
}).then(() => {
success = true
println("Connection closed.", true)
close();
}).catch(() => {
success = false
println("Connection errored.", true)
close();
});
}