From 478ca20451461e8718951d15921ec9d0d7836372 Mon Sep 17 00:00:00 2001 From: Arthur Lu Date: Wed, 28 May 2025 20:08:52 +0000 Subject: [PATCH] implement ssr modal dialog form construction for create instances --- web/css/form.css | 1 - web/html/index.html | 51 ++++++++++++++++++++ web/scripts/dialog.js | 61 ++++++++++++++++++++++++ web/scripts/index.js | 73 +++++++++-------------------- web/templates/instance-card.go.tmpl | 33 +++++++++++++ 5 files changed, 167 insertions(+), 52 deletions(-) diff --git a/web/css/form.css b/web/css/form.css index 6eb5ca4..c5a9f9b 100644 --- a/web/css/form.css +++ b/web/css/form.css @@ -77,6 +77,5 @@ input[type="radio"] { dialog { max-width: calc(min(50%, 80ch)); - background-color: var(--main-bg-color); color: var(--main-text-color); } \ No newline at end of file diff --git a/web/html/index.html b/web/html/index.html index 3854de9..019acc8 100644 --- a/web/html/index.html +++ b/web/html/index.html @@ -64,4 +64,55 @@ + + + + \ No newline at end of file diff --git a/web/scripts/dialog.js b/web/scripts/dialog.js index f3fecc0..5025647 100644 --- a/web/scripts/dialog.js +++ b/web/scripts/dialog.js @@ -1,3 +1,64 @@ +/** + * Custom modal dialog with form support. Assumes the following structure: + * + * Where prompt is the modal dialog's prompt or header, + * body contains an optional form or other information, + * and controls contains a series of buttons which controls the form + */ +class ModalDialog extends HTMLElement { + shadowRoot = null; + dialog = null; + + constructor () { + super(); + // setup shadowDOM + const internals = this.attachInternals(); + this.shadowRoot = internals.shadowRoot; + this.dialog = this.shadowRoot.querySelector("dialog"); + // add dialog handler to each control button with the return value corresponding to their value attribute + const controls = this.shadowRoot.querySelector("#controls"); + for (const button of controls.childNodes) { + button.addEventListener("click", async (e) => { + e.preventDefault(); + this.dialog.close(e.target.value); + }); + } + this.setOnClose(); // default behavior to just close the dialog, should call setOnClose to override this behavior + } + + showModal () { + this.dialog.showModal(); + } + + querySelector (query) { + return this.shadowRoot.querySelector(query); + } + + querySelectorAll (query) { + return this.shadowRoot.querySelectorAll(query); + } + + setOnClose (callback = (result, form) => {}) { + this.dialog.addEventListener("close", () => { + const formElem = this.dialog.querySelector("form"); + const formData = formElem ? new FormData(formElem) : null; + callback(this.dialog.returnValue, formData); + formElem.reset(); + this.dialog.close(); + }); + } +} + +customElements.define("modal-dialog", ModalDialog); + export function dialog (header, body, onclose = async (result, form) => { }) { const dialog = document.createElement("dialog"); dialog.innerHTML = ` diff --git a/web/scripts/index.js b/web/scripts/index.js index e8316bd..d747458 100644 --- a/web/scripts/index.js +++ b/web/scripts/index.js @@ -123,12 +123,12 @@ class InstanceCard extends HTMLElement { if (powerButton.classList.contains("clickable")) { powerButton.onclick = this.handlePowerButton.bind(this); powerButton.onkeydown = (event) => { - console.log(event.key, event.key === "Enter") + console.log(event.key, event.key === "Enter"); if (event.key === "Enter") { - event.preventDefault() - this.handlePowerButton() + event.preventDefault(); + this.handlePowerButton(); } - } + }; } const deleteButton = this.shadowRoot.querySelector("#delete-btn"); @@ -136,10 +136,10 @@ class InstanceCard extends HTMLElement { deleteButton.onclick = this.handleDeleteButton.bind(this); deleteButton.onkeydown = (event) => { if (event.key === "Enter") { - event.preventDefault() - this.handleDeleteButton() + event.preventDefault(); + this.handleDeleteButton(); } - } + }; } } @@ -155,9 +155,8 @@ class InstanceCard extends HTMLElement { async handlePowerButton () { if (!this.actionLock) { - const header = `${this.status === "running" ? "Stop" : "Start"} VM ${this.vmid}`; - const body = `

Are you sure you want to ${this.status === "running" ? "stop" : "start"} VM ${this.vmid}

`; - dialog(header, body, async (result, form) => { + const dialog = this.shadowRoot.querySelector("#power-dialog"); + dialog.setOnClose(async (result, form) => { if (result === "confirm") { this.actionLock = true; const targetAction = this.status === "running" ? "stop" : "start"; @@ -185,13 +184,14 @@ class InstanceCard extends HTMLElement { refreshInstances(); } }); + dialog.showModal(); } } handleDeleteButton () { if (!this.actionLock && this.status === "stopped") { - const header = `Delete VM ${this.vmid}`; - const body = `

Are you sure you want to delete VM ${this.vmid}

`; + const header = `Delete ${this.vmid}`; + const body = `

Are you sure you want to delete ${this.vmid}

`; dialog(header, body, async (result, form) => { if (result === "confirm") { @@ -313,46 +313,9 @@ function sortInstances () { } async function handleInstanceAdd () { - const header = "Create New Instance"; + const d = document.querySelector("#create-instance-dialog"); - const body = ` -
- - - - - - - - - - - - - - -

Container Options

- - - - - - - - - - - - -
- `; - - const templates = await requestAPI("/user/ct-templates", "GET"); - - const d = dialog(header, body, async (result, form) => { + d.setOnClose(async (result, form) => { if (result === "confirm") { const body = { name: form.get("name"), @@ -381,6 +344,8 @@ async function handleInstanceAdd () { } }); + const templates = await requestAPI("/user/ct-templates", "GET"); + const typeSelect = d.querySelector("#type"); typeSelect.selectedIndex = -1; typeSelect.addEventListener("change", () => { @@ -397,6 +362,10 @@ async function handleInstanceAdd () { }); } }); + d.querySelectorAll(".container-specific").forEach((element) => { + element.classList.add("none"); + element.disabled = true; + }); const rootfsContent = "rootdir"; const rootfsStorage = d.querySelector("#rootfs-storage"); @@ -468,4 +437,6 @@ async function handleInstanceAdd () { password.addEventListener("change", validatePassword); confirmPassword.addEventListener("keyup", validatePassword); + + d.showModal(); } diff --git a/web/templates/instance-card.go.tmpl b/web/templates/instance-card.go.tmpl index 869e5c9..03015af 100644 --- a/web/templates/instance-card.go.tmpl +++ b/web/templates/instance-card.go.tmpl @@ -61,6 +61,39 @@ {{end}} + + + {{end}} \ No newline at end of file