Add support for provisioning and rebuilding via interface

This commit is contained in:
Tom 2023-07-09 11:18:14 +02:00
parent f5c5999f44
commit ddb4bb3546
76 changed files with 1306 additions and 625 deletions

View file

@ -1,30 +1,22 @@
import "../Admin/index.ts"
import "../Admin/index.css"
import { createModal } from "~/src/lib/remote"
import { Provision } from "~/src/lib/remote/api"
const provision = document.getElementById("provision") as HTMLFormElement;
const slug = document.getElementById("slug") as HTMLInputElement;
const php = document.getElementById("php") as HTMLSelectElement;
const opcacheDevelopment = document.getElementById("opcacheDevelopment") 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();
}
},
})
Provision({ Slug: slug.value, System: { PHP: php.value, OpCacheDevelopment: opcacheDevelopment.checked } })
.then(slug => {
location.href = "/admin/instance/" + slug;
})
.catch((e) => {console.error(e); location.reload()});
})
// enable the form!

View file

@ -0,0 +1,24 @@
import "../Admin/index.ts"
import "../Admin/index.css"
import { Rebuild } from "~/src/lib/remote/api"
const slug = document.getElementById("slug") as HTMLInputElement
const provision = document.getElementById("provision") as HTMLFormElement;
const php = document.getElementById("php") as HTMLSelectElement;
const opcacheDevelopment = document.getElementById("opcacheDevelopment") as HTMLInputElement;
// add an event handler to open the modal form!
provision.addEventListener('submit', (evt) => {
evt.preventDefault();
Rebuild(slug.value, { PHP: php.value, OpCacheDevelopment: opcacheDevelopment.checked })
.then(slug => {
location.href = "/admin/instance/" + slug;
})
.catch((e) => {console.error(e); location.reload()});
})
// enable the form!
provision.querySelector('fieldset')?.removeAttribute('disabled');

View file

@ -0,0 +1,50 @@
import { createModal } from "~/src/lib/remote"
/**
* Flags to provision a new system.
* Should mirror "provision".Flags.
*/
interface ProvisionFlags {
Slug: string
System: System
}
interface System {
PHP: string;
OpCacheDevelopment: boolean
}
/** Rebuild the specified instance */
export async function Rebuild(slug: string, system: System): Promise<string> {
return new Promise((rs, rj) => {
createModal("rebuild", [slug, JSON.stringify(system)], {
bufferSize: 0,
onClose: (success: boolean, message?: string) => {
if (!success) {
rj(new Error(message ?? "unspecified error"))
return;
}
rs(slug);
},
})
});
}
/** Provision provisions a new instance */
export async function Provision(flags: ProvisionFlags): Promise<string> {
// open a modal to provision a new instance
return new Promise((rs, rj) => {
createModal("provision", [JSON.stringify(flags)], {
bufferSize: 0,
onClose: (success: boolean, message?: string) => {
if (!success) {
rj(new Error(message ?? "unspecified error"))
return;
}
rs(flags.Slug);
},
})
});
}

View file

@ -105,7 +105,7 @@ export default function setup() {
type ModalOptions = {
bufferSize: number;
onClose: (success: boolean) => void
onClose: (success: boolean, message?: string) => void
}
export function createModal(action: string, params: string[], opts: Partial<ModalOptions>) {
// create a modal dialog and append it to the body
@ -123,14 +123,14 @@ export function createModal(action: string, params: string[], opts: Partial<Moda
finishButton.className = "pure-button pure-button-success"
finishButton.append(typeof opts?.onClose === 'function' ? "Close & Finish" : "Close")
let result = {success: false, error: "unknown error"};
let result: ResultMessage = {success: false};
finishButton.addEventListener('click', (event) => {
event.preventDefault();
if (typeof opts?.onClose === 'function') {
finishButton.setAttribute('disabled', 'disabled')
target.innerHTML = 'Finishing up ...'
opts.onClose(result.success)
opts.onClose(result.success, result.message)
return;
}
@ -147,7 +147,9 @@ export function createModal(action: string, params: string[], opts: Partial<Moda
window.onbeforeunload = () => "A remote session is in progress. Are you sure you want to leave?";
// when closing, add a button to the modal!
const close = (result: ResultMessage) => {
const close = (message: ResultMessage) => {
result = message
if (result.success) {
println('Process completed successfully. ', true);
} else {
@ -168,6 +170,7 @@ export function createModal(action: string, params: string[], opts: Partial<Moda
// connect to the socket and send the action
callServerAction(
location.href.replace('http', 'ws'),
{
'name': action,
'params': params,

View file

@ -11,12 +11,14 @@ function isResultMessage(value: any): value is ResultMessage {
/**
* Opens a WebSocket connection and calls a server action
* @param endpoint Endpoint to call
* @param call Function to call
* @param onOpen callback for once the connection is opened. The send function can be used to send additional text to the server.
* @param onText called when the connection receives some text
* @returns a promise that is resolved once the conneciton is closed. Rejected if the connection errors.
*/
export default async function callServerAction(
endpoint: string,
call: CallMessage,
onOpen: (send: (text: string) => void, cancel: () => void) => void,
onText: (text: string) => void,
@ -24,7 +26,7 @@ export default async function callServerAction(
return new Promise((rs, rj) => {
const mutex = new Mutex();
const socket = new WebSocket(location.href.replace('http', 'ws'));
const socket = new WebSocket(endpoint);
let result: ResultMessage;
socket.onmessage = (msg) => {