cleanup various unused code,

simplify instance action hiding when node is not online
This commit is contained in:
2025-04-29 17:44:02 +00:00
parent bb0d363298
commit 800ad7cd60
12 changed files with 39 additions and 284 deletions

View File

@@ -10,7 +10,7 @@ import (
"proxmoxaas-dashboard/app/routes"
"github.com/gin-gonic/gin"
"github.com/tdewolff/minify"
"github.com/tdewolff/minify/v2"
)
func Run() {

View File

@@ -3,17 +3,19 @@ package common
import (
"io"
"github.com/tdewolff/minify"
"github.com/tdewolff/minify/css"
"github.com/tdewolff/minify/html"
"github.com/tdewolff/minify/js"
"github.com/tdewolff/minify/v2"
"github.com/tdewolff/minify/v2/css"
"github.com/tdewolff/minify/v2/html"
"github.com/tdewolff/minify/v2/js"
)
// defines mime type and associated minifier
type MimeType struct {
Type string
Minifier func(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error
}
// map file extension to mime types
var MimeTypes = map[string]MimeType{
"css": {
Type: "text/css",

View File

@@ -16,7 +16,7 @@ import (
"strings"
"github.com/gin-gonic/gin"
"github.com/tdewolff/minify"
"github.com/tdewolff/minify/v2"
)
var TMPL *template.Template

2
go.mod
View File

@@ -35,7 +35,9 @@ require (
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
github.com/tdewolff/minify/v2 v2.23.1 // indirect
github.com/tdewolff/parse v2.3.4+incompatible // indirect
github.com/tdewolff/parse/v2 v2.7.23 // indirect
github.com/tdewolff/test v1.0.11 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.2.12 // indirect

View File

@@ -77,4 +77,6 @@ input[type="radio"] {
dialog {
max-width: calc(min(50%, 80ch));
background-color: var(--main-bg-color);
color: var(--main-text-color);
}

View File

@@ -61,13 +61,10 @@ body {
grid-template-rows: auto 1fr;
}
main, dialog {
main {
max-width: 100vw;
background-color: var(--main-bg-color);
color: var(--main-text-color);
}
main {
padding: 0 16px 16px;
}

View File

@@ -25,7 +25,7 @@
</header>
<main>
<section>
<h2 id="name"><a href="index">Instances</a> / {{.config.Name}}</h2>
<h2><a href="index">Instances</a> / {{.config.Name}}</h2>
<form>
<fieldset class="w3-card w3-padding">
<legend>Resources</legend>

View File

@@ -12,10 +12,10 @@ async function init () {
function handlePasswordChangeForm () {
const body = `
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
<label for="new-password">New Password</label>
<input class="w3-input w3-border" id="new-password" name="new-password" type="password"required>
<label for="confirm-password">Confirm Password</label>
<input class="w3-input w3-border" id="confirm-password" name="confirm-password" type="password" required>
<label for="new-password">New Password</label>
<input class="w3-input w3-border" id="new-password" name="new-password" type="password"required>
<label for="confirm-password">Confirm Password</label>
<input class="w3-input w3-border" id="confirm-password" name="confirm-password" type="password" required>
</form>
`;
const d = dialog("Change Password", body, async (result, form) => {

View File

@@ -6,7 +6,6 @@ window.addEventListener("DOMContentLoaded", init);
let node;
let type;
let vmid;
let config;
async function init () {
setAppearance();
@@ -16,11 +15,6 @@ async function init () {
type = uriData.type;
vmid = uriData.vmid;
await getConfig();
const name = type === "qemu" ? "name" : "hostname";
document.querySelector("#name").innerHTML = document.querySelector("#name").innerHTML.replace("%{vmname}", config.data[name]);
initVolumes();
initNetworks();
initDevices();
@@ -28,10 +22,6 @@ async function init () {
document.querySelector("#exit").addEventListener("click", handleFormExit);
}
async function getConfig () {
config = await requestPVE(`/nodes/${node}/${type}/${vmid}/config`, "GET");
}
class VolumeAction extends HTMLElement {
shadowRoot = null;
@@ -83,7 +73,7 @@ class VolumeAction extends HTMLElement {
const body = `
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
<label for="device">${type === "qemu" ? "SCSI" : "MP"}</label>
<input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="${type === "qemu" ? "5" : "255"}" required>
<input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="${type === "qemu" ? "30" : "255"}" required>
</form>
`;
@@ -113,7 +103,7 @@ class VolumeAction extends HTMLElement {
<label for="size-increment">Size Increment (GiB)</label>
<input class="w3-input w3-border" name="size-increment" id="size-increment" type="number" min="0" max="131072">
</form>
`;
`;
dialog(header, body, async (result, form) => {
if (result === "confirm") {
@@ -135,9 +125,7 @@ class VolumeAction extends HTMLElement {
async handleDiskMove () {
const content = type === "qemu" ? "images" : "rootdir";
const storage = await requestPVE(`/nodes/${node}/storage`, "GET");
const header = `Move ${this.dataset.volume}`;
let options = "";
storage.data.forEach((element) => {
if (element.content.includes(content)) {
@@ -215,9 +203,7 @@ async function refreshVolumes () {
async function handleDiskAdd () {
const content = type === "qemu" ? "images" : "rootdir";
const storage = await requestPVE(`/nodes/${node}/storage`, "GET");
const header = "Create New Disk";
let options = "";
storage.data.forEach((element) => {
if (element.content.includes(content)) {
@@ -228,7 +214,7 @@ async function handleDiskAdd () {
const body = `
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
<label for="device">${type === "qemu" ? "SCSI" : "MP"}</label><input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="${type === "qemu" ? "5" : "255"}" value="0" required>
<label for="device">${type === "qemu" ? "SCSI" : "MP"}</label><input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="${type === "qemu" ? "30" : "255"}" value="0" required>
${select}
<label for="size">Size (GiB)</label><input class="w3-input w3-border" name="size" id="size" type="number" min="0" max="131072" required>
</form>
@@ -255,9 +241,7 @@ async function handleDiskAdd () {
async function handleCDAdd () {
const isos = await requestAPI("/user/vm-isos", "GET");
const header = "Mount a CDROM";
const body = `
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
<label for="device">IDE</label><input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="3" required>
@@ -341,7 +325,6 @@ class NetworkAction extends HTMLElement {
const netID = this.dataset.network;
const header = `Delete ${netID}`;
const body = "";
dialog(header, body, async (result, form) => {
if (result === "confirm") {
setSVGSrc(document.querySelector(`svg[data-network="${netID}"]`), "images/status/loading.svg");
@@ -388,7 +371,6 @@ async function handleNetworkAdd () {
body += "<label for=\"name\">Interface Name</label><input type=\"text\" id=\"name\" name=\"name\" class=\"w3-input w3-border\">";
}
body += "</form>";
dialog(header, body, async (result, form) => {
if (result === "confirm") {
const body = {
@@ -468,7 +450,6 @@ class DeviceAction extends HTMLElement {
const deviceID = this.dataset.device;
const header = `Remove Expansion Card ${deviceID}`;
const body = "";
dialog(header, body, async (result, form) => {
if (result === "confirm") {
this.setStatusLoading();
@@ -514,7 +495,6 @@ async function handleDeviceAdd () {
<label for="pcie">PCI-Express</label><input type="checkbox" id="pcie" name="pcie" class="w3-input w3-border">
</form>
`;
const d = dialog(header, body, async (result, form) => {
if (result === "confirm") {
const hostpci = form.get("hostpci");

View File

@@ -3,6 +3,20 @@ import { alert, dialog } from "./dialog.js";
import { setupClientSync } from "./clientsync.js";
import wfaInit from "../modules/wfa.js";
window.addEventListener("DOMContentLoaded", init);
async function init () {
setAppearance();
wfaInit("modules/wfa.wasm");
initInstances();
document.querySelector("#instance-add").addEventListener("click", handleInstanceAdd);
document.querySelector("#vm-search").addEventListener("input", sortInstances);
setupClientSync(refreshInstances);
}
class InstanceCard extends HTMLElement {
actionLock = false;
shadowRoot = null;
@@ -125,20 +139,12 @@ class InstanceCard extends HTMLElement {
if (deleteButton.classList.contains("clickable")) {
deleteButton.onclick = this.handleDeleteButton.bind(this);
}
if (this.node.status !== "online") {
powerButton.classList.add("hidden");
configButton.classList.add("hidden");
consoleButton.classList.add("hidden");
deleteButton.classList.add("hidden");
}
}
async handlePowerButton () {
if (!this.actionLock) {
const header = `${this.status === "running" ? "Stop" : "Start"} VM ${this.vmid}`;
const body = `<p>Are you sure you want to ${this.status === "running" ? "stop" : "start"} VM ${this.vmid}</p>`;
dialog(header, body, async (result, form) => {
if (result === "confirm") {
this.actionLock = true;
@@ -226,22 +232,8 @@ class InstanceCard extends HTMLElement {
customElements.define("instance-card", InstanceCard);
window.addEventListener("DOMContentLoaded", init);
async function init () {
setAppearance();
wfaInit("modules/wfa.wasm");
initInstances();
document.querySelector("#instance-add").addEventListener("click", handleInstanceAdd);
document.querySelector("#vm-search").addEventListener("input", sortInstances);
setupClientSync(refreshInstances);
}
async function getInstancesFragment () {
return await requestDash("/index/instances", "GET")
return await requestDash("/index/instances", "GET");
}
async function refreshInstances () {

View File

@@ -1,91 +1,3 @@
export const resourcesConfig = {
cpu: {
name: "CPU Type",
icon: "images/resources/cpu.svg",
id: "proctype",
unitText: null
},
cores: {
name: "CPU Amount",
icon: "images/resources/cpu.svg",
id: "cores",
unitText: "Cores"
},
memory: {
name: "Memory",
icon: "images/resources/ram.svg",
id: "ram",
unitText: "MiB"
},
swap: {
name: "Swap",
icon: "images/resources/swap.svg",
id: "swap",
unitText: "MiB"
},
disk: {
actionBarOrder: ["move", "resize", "detach_attach", "delete"],
lxc: {
prefixOrder: ["rootfs", "mp", "unused"],
rootfs: { name: "ROOTFS", icon: "images/resources/drive.svg", actions: ["move", "resize"] },
mp: { name: "MP", icon: "images/resources/drive.svg", actions: ["detach", "move", "reassign", "resize"] },
unused: { name: "UNUSED", icon: "images/resources/drive.svg", actions: ["attach", "delete", "reassign"] }
},
qemu: {
prefixOrder: ["ide", "sata", "scsi", "unused"],
ide: { name: "IDE", icon: "images/resources/disk.svg", actions: ["delete"] },
sata: { name: "SATA", icon: "images/resources/drive.svg", actions: ["detach", "move", "reassign", "resize"] },
scsi: { name: "SCSI", icon: "images/resources/drive.svg", actions: ["detach", "move", "reassign", "resize"] },
unused: { name: "UNUSED", icon: "images/resources/drive.svg", actions: ["attach", "delete", "reassign"] }
},
actions: {
attach: {
src: "images/actions/disk/attach.svg",
title: "Attach Disk"
},
detach: {
src: "images/actions/disk/detach.svg",
title: "Detach Disk"
},
delete: null
}
},
network: {
name: "Network",
icon: "images/resources/network.svg",
id: "network",
unitText: "MB/s",
prefix: "net"
},
pci: {
name: "Devices",
icon: "images/resources/device.svg",
id: "devices",
unitText: null,
prefix: "hostpci"
}
};
export const bootConfig = {
eligiblePrefixes: ["ide", "sata", "scsi", "net"],
ide: {
icon: "images/resources/disk.svg",
alt: "IDE Bootable Icon"
},
sata: {
icon: "images/resources/drive.svg",
alt: "SATA Bootable Icon"
},
scsi: {
icon: "images/resources/drive.svg",
alt: "SCSI Bootable Icon"
},
net: {
icon: "images/resources/network.svg",
alt: "NET Bootable Icon"
}
};
export function setCookie (cname, cval) {
document.cookie = `${cname}=${cval}`;
}
@@ -301,135 +213,3 @@ export function setSVGSrc (svgElem, href) {
export function setSVGAlt (svgElem, alt) {
svgElem.setAttribute("aria-label", alt);
}
/**
* Simple object check.
* @param item
* @returns {boolean}
*/
export function isObject (item) {
return (item && typeof item === "object" && !Array.isArray(item));
}
/**
* Deep merge two objects.
* @param target
* @param ...sources
*/
export function mergeDeep (target, ...sources) {
if (!sources.length) return target;
const source = sources.shift();
if (isObject(target) && isObject(source)) {
for (const key in source) {
if (isObject(source[key])) {
if (!target[key]) Object.assign(target, { [key]: {} });
mergeDeep(target[key], source[key]);
}
else {
Object.assign(target, { [key]: source[key] });
}
}
}
return mergeDeep(target, ...sources);
}
/**
* Checks if object or array is empty
* @param {*} obj
* @returns
*/
export function isEmpty (obj) {
if (obj instanceof Array) {
return obj.length === 0;
}
else {
for (const prop in obj) {
if (Object.hasOwn(obj, prop)) {
return false;
}
}
return true;
}
}
/*
export function addResourceLine (resourceConfig, field, attributesOverride, labelPrefix = null) {
const iconHref = resourceConfig.icon;
const elementType = resourceConfig.element;
const labelText = labelPrefix ? `${labelPrefix} ${resourceConfig.name}` : resourceConfig.name;
const id = resourceConfig.id;
const unitText = resourceConfig.unitText;
const attributes = { ...(resourceConfig.attributes), ...(attributesOverride) };
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
setSVGSrc(icon, iconHref);
setSVGAlt(icon, labelText);
field.append(icon);
const label = document.createElement("label");
label.innerText = labelText;
label.htmlFor = id;
field.append(label);
let element;
if (elementType === "input") {
element = document.createElement("input");
for (const k in attributes) {
element.setAttribute(k, attributes[k]);
}
element.id = id;
element.name = id;
element.required = true;
element.classList.add("w3-input");
element.classList.add("w3-border");
field.append(element);
}
else if (elementType === "select" || elementType === "multi-select") {
element = document.createElement("select");
for (const option of attributes.options) {
element.append(new Option(option));
}
element.value = attributes.value;
element.id = id;
element.name = id;
element.required = true;
element.classList.add("w3-select");
element.classList.add("w3-border");
if (elementType === "multi-select") {
element.setAttribute("multiple", true);
}
field.append(element);
}
else if (customElements.get(elementType)) {
element = document.createElement(elementType);
if (attributes.options) {
for (const option of attributes.options) {
element.append(new Option(option));
}
}
element.value = attributes.value;
element.id = id;
element.name = id;
element.required = true;
field.append(element);
}
let unit;
if (unitText) {
unit = document.createElement("p");
unit.innerText = unitText;
field.append(unit);
}
else {
unit = document.createElement("div");
unit.classList.add("hidden");
field.append(unit);
}
return { icon, label, element, unit };
}
*/

View File

@@ -38,17 +38,17 @@
<p>{{.NodeStatus}}</p>
</div>
<div class="w3-col l2 m2 s6 flex row nowrap" style="height: 1lh;">
{{if eq .Status "running"}}
{{if and (eq .NodeStatus "online") (eq .Status "running")}}
<svg id="power-btn" class="clickable" aria-label="shutdown instance"><use href="images/actions/instance/stop.svg#symb"></svg>
<svg id="configure-btn" aria-label=""><use href="images/actions/instance/config-inactive.svg#symb"></svg>
<svg id="console-btn" class="clickable" aria-label="open console"><use href="images/actions/instance/console-active.svg#symb"></svg>
<svg id="delete-btn" aria-label=""><use href="images/actions/instance/delete-inactive.svg#symb"></svg>
{{else if eq .Status "stopped"}}
{{else if and (eq .NodeStatus "online") (eq .Status "stopped")}}
<svg id="power-btn" class="clickable" aria-label="start instance"><use href="images/actions/instance/start.svg#symb"></svg>
<svg id="configure-btn" class="clickable" aria-label="change configuration"><use href="images/actions/instance/config-active.svg#symb"></svg>
<svg id="console-btn" aria-label=""><use href="images/actions/instance/console-inactive.svg#symb"></svg>
<svg id="delete-btn" class="clickable" aria-label="delete instance"><use href="images/actions/instance/delete-active.svg#symb"></svg>
{{else if eq .Status "loading"}}
{{else if and (eq .NodeStatus "online") (eq .Status "loading")}}
<svg id="power-btn" aria-label=""><use href="images/actions/instance/loading.svg#symb"></svg>
<svg id="configure-btn" aria-label=""><use href="images/actions/instance/config-inactive.svg#symb"></svg>
<svg id="console-btn" aria-label=""><use href="images/actions/instance/console-inactive.svg#symb"></svg>