minor optimizations to instance card config and console actions

This commit is contained in:
2025-05-18 01:12:58 +00:00
parent e6cd1fbb3d
commit 53832b67a2
6 changed files with 51 additions and 60 deletions

View File

@@ -53,22 +53,25 @@ type Node struct {
// used in constructing instance cards in index // used in constructing instance cards in index
type InstanceCard struct { type InstanceCard struct {
VMID uint VMID uint
Name string Name string
Type string Type string
Status string Status string
Node string Node string
NodeStatus string NodeStatus string
ConfigPath string
ConsolePath string
} }
// used in retriving cluster tasks // used in retriving cluster tasks
type Task struct { type Task struct {
Type string Type string
Node string Node string
User string User string
ID string ID string
VMID uint VMID uint
Status string Status string
EndTime uint
} }
type InstanceStatus struct { type InstanceStatus struct {
@@ -116,6 +119,12 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
for vmid, instance := range instances { for vmid, instance := range instances {
nodestatus := nodes[instance.Node].Status nodestatus := nodes[instance.Node].Status
instance.NodeStatus = nodestatus instance.NodeStatus = nodestatus
instance.ConfigPath = fmt.Sprintf("config?node=%s&type=%s&vmid=%d", instance.Node, instance.Type, instance.VMID)
if instance.Type == "qemu" {
instance.ConsolePath = fmt.Sprintf("%s/?console=kvm&vmid=%d&vmname=%s&node=%s&resize=off&cmd=&novnc=1", common.Global.PVE, instance.VMID, instance.Name, instance.Node)
} else if instance.Type == "lxc" {
instance.ConsolePath = fmt.Sprintf("%s/?console=lxc&vmid=%d&vmname=%s&node=%s&resize=off&cmd=&xtermjs=1", common.Global.PVE, instance.VMID, instance.Name, instance.Node)
}
instances[vmid] = instance instances[vmid] = instance
} }
@@ -128,6 +137,9 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
return nil, nil, fmt.Errorf("request to /cluster/tasks resulted in %+v", res) return nil, nil, fmt.Errorf("request to /cluster/tasks resulted in %+v", res)
} }
most_recent_task := map[uint]uint{}
expected_state := map[uint]string{}
for _, v := range ctx.Body["data"].([]any) { for _, v := range ctx.Body["data"].([]any) {
task := Task{} task := Task{}
err := mapstructure.Decode(v, &task) err := mapstructure.Decode(v, &task)
@@ -151,8 +163,21 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
} else if !(task.Status == "running" || task.Status == "OK") { // task is not running or finished with status OK } else if !(task.Status == "running" || task.Status == "OK") { // task is not running or finished with status OK
continue continue
} else { // recent task is a start or stop task for user instance which is running or "OK" } else { // recent task is a start or stop task for user instance which is running or "OK"
if task.EndTime > most_recent_task[task.VMID] { // if the task's end time is later than the most recent one encountered
most_recent_task[task.VMID] = task.EndTime // update the most recent task
if task.Type == "qmstart" || task.Type == "vzstart" { // if the task was a start task, update the expected state to running
expected_state[task.VMID] = "running"
} else if task.Type == "qmstop" || task.Type == "vzstop" { // if the task was a stop task, update the expected state to stopped
expected_state[task.VMID] = "stopped"
}
}
}
}
for vmid, expected_state := range expected_state { // for the expected states from recent tasks
if instances[vmid].Status != expected_state { // if the current node's state from /cluster/resources differs from expected state
// get /status/current which is updated faster than /cluster/resources // get /status/current which is updated faster than /cluster/resources
instance := instances[task.VMID] instance := instances[vmid]
path := fmt.Sprintf("/proxmox/nodes/%s/%s/%d/status/current", instance.Node, instance.Type, instance.VMID) path := fmt.Sprintf("/proxmox/nodes/%s/%s/%d/status/current", instance.Node, instance.Type, instance.VMID)
ctx.Body = map[string]any{} ctx.Body = map[string]any{}
res, code, err := common.RequestGetAPI(path, ctx) res, code, err := common.RequestGetAPI(path, ctx)
@@ -167,7 +192,7 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
mapstructure.Decode(ctx.Body["data"], &status) mapstructure.Decode(ctx.Body["data"], &status)
instance.Status = status.Status instance.Status = status.Status
instances[task.VMID] = instance instances[vmid] = instance
} }
} }

View File

@@ -26,7 +26,7 @@
<main> <main>
<section> <section>
<h2><a href="index">Instances</a> / {{.config.Name}}</h2> <h2><a href="index">Instances</a> / {{.config.Name}}</h2>
<form> <form id="config-form">
<fieldset class="w3-card w3-padding"> <fieldset class="w3-card w3-padding">
<legend>Resources</legend> <legend>Resources</legend>
<div class="input-grid" id="resources" style="grid-template-columns: auto auto auto 1fr;"> <div class="input-grid" id="resources" style="grid-template-columns: auto auto auto 1fr;">
@@ -91,7 +91,7 @@
</fieldset> </fieldset>
{{end}} {{end}}
<div class="w3-container w3-center" id="form-actions"> <div class="w3-container w3-center" id="form-actions">
<button class="w3-button w3-margin" id="exit" type="button">EXIT</button> <button class="w3-button w3-margin" id="exit" type="submit">EXIT</button>
</div> </div>
</form> </form>
</section> </section>

View File

@@ -19,7 +19,7 @@ async function init () {
initNetworks(); initNetworks();
initDevices(); initDevices();
document.querySelector("#exit").addEventListener("click", handleFormExit); document.querySelector("#config-form").addEventListener("submit", handleFormExit);
} }
class VolumeAction extends HTMLElement { class VolumeAction extends HTMLElement {
@@ -530,7 +530,8 @@ async function refreshBoot () {
} }
} }
async function handleFormExit () { async function handleFormExit (event) {
event.preventDefault();
const body = { const body = {
cores: document.querySelector("#cores").value, cores: document.querySelector("#cores").value,
memory: document.querySelector("#ram").value memory: document.querySelector("#ram").value

View File

@@ -1,4 +1,4 @@
import { requestPVE, requestAPI, goToPage, setAppearance, getSearchSettings, goToURL, requestDash, setSVGSrc, setSVGAlt } from "./utils.js"; import { requestPVE, requestAPI, setAppearance, getSearchSettings, requestDash, setSVGSrc, setSVGAlt } from "./utils.js";
import { alert, dialog } from "./dialog.js"; import { alert, dialog } from "./dialog.js";
import { setupClientSync } from "./clientsync.js"; import { setupClientSync } from "./clientsync.js";
import wfaInit from "../modules/wfa.js"; import wfaInit from "../modules/wfa.js";
@@ -124,17 +124,6 @@ class InstanceCard extends HTMLElement {
powerButton.onclick = this.handlePowerButton.bind(this); powerButton.onclick = this.handlePowerButton.bind(this);
} }
const configButton = this.shadowRoot.querySelector("#configure-btn");
if (configButton.classList.contains("clickable")) {
configButton.onclick = this.handleConfigButton.bind(this);
}
const consoleButton = this.shadowRoot.querySelector("#console-btn");
if (consoleButton.classList.contains("clickable")) {
consoleButton.classList.add("clickable");
consoleButton.onclick = this.handleConsoleButton.bind(this);
}
const deleteButton = this.shadowRoot.querySelector("#delete-btn"); const deleteButton = this.shadowRoot.querySelector("#delete-btn");
if (deleteButton.classList.contains("clickable")) { if (deleteButton.classList.contains("clickable")) {
deleteButton.onclick = this.handleDeleteButton.bind(this); deleteButton.onclick = this.handleDeleteButton.bind(this);
@@ -186,20 +175,6 @@ class InstanceCard extends HTMLElement {
} }
} }
handleConfigButton () {
if (!this.actionLock && this.status === "stopped") { // if the action lock is false, and the node is stopped, then navigate to the config page with the node info in the search query
goToPage("config", { node: this.node.name, type: this.type, vmid: this.vmid });
}
}
handleConsoleButton () {
if (!this.actionLock && this.status === "running") {
const data = { console: `${this.type === "qemu" ? "kvm" : "lxc"}`, vmid: this.vmid, vmname: this.name, node: this.node.name, resize: "off", cmd: "" };
data[`${this.type === "qemu" ? "novnc" : "xtermjs"}`] = 1;
goToURL(window.PVE, data, true);
}
}
handleDeleteButton () { handleDeleteButton () {
if (!this.actionLock && this.status === "stopped") { if (!this.actionLock && this.status === "stopped") {
const header = `Delete VM ${this.vmid}`; const header = `Delete VM ${this.vmid}`;

View File

@@ -114,20 +114,6 @@ export function goToPage (page, data = null) {
window.location.href = `${page}${data ? "?" : ""}${params}`; window.location.href = `${page}${data ? "?" : ""}${params}`;
} }
export function goToURL (href, data = {}, newwindow = false) {
const url = new URL(href);
for (const k in data) {
url.searchParams.append(k, data[k]);
}
if (newwindow) {
window.open(url, document.title, "height=480,width=848");
}
else {
window.location.assign(url.toString());
}
}
export function getURIData () { export function getURIData () {
const url = new URL(window.location.href); const url = new URL(window.location.href);
return Object.fromEntries(url.searchParams); return Object.fromEntries(url.searchParams);

View File

@@ -41,11 +41,15 @@
{{if and (eq .NodeStatus "online") (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="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="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> <a href="{{.ConsolePath}}" target="_blank">
<svg id="console-btn" class="clickable" aria-label="open console"><use href="images/actions/instance/console-active.svg#symb"></svg>
</a>
<svg id="delete-btn" aria-label=""><use href="images/actions/instance/delete-inactive.svg#symb"></svg> <svg id="delete-btn" aria-label=""><use href="images/actions/instance/delete-inactive.svg#symb"></svg>
{{else if and (eq .NodeStatus "online") (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="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> <a href="{{.ConfigPath}}">
<svg id="configure-btn" class="clickable" aria-label="change configuration"><use href="images/actions/instance/config-active.svg#symb"></svg>
</a>
<svg id="console-btn" aria-label=""><use href="images/actions/instance/console-inactive.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> <svg id="delete-btn" class="clickable" aria-label="delete instance"><use href="images/actions/instance/delete-active.svg#symb"></svg>
{{else if and (eq .NodeStatus "online") (eq .Status "loading")}} {{else if and (eq .NodeStatus "online") (eq .Status "loading")}}