Fix Sync Issue with Instance Power Action #4

Merged
alu merged 2 commits from instance-power-sync-fix into main 2025-05-13 17:41:28 +00:00
3 changed files with 84 additions and 5 deletions

View File

@@ -4,6 +4,7 @@ import (
"fmt"
"net/http"
"proxmoxaas-dashboard/app/common"
"strconv"
"github.com/gin-gonic/gin"
"github.com/go-viper/mapstructure/v2"
@@ -60,6 +61,20 @@ type InstanceCard struct {
NodeStatus string
}
// used in retriving cluster tasks
type Task struct {
Type string
Node string
User string
ID string
VMID uint
Status string
}
type InstanceStatus struct {
Status string
}
func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]Node, error) {
ctx := common.RequestContext{
Cookies: map[string]string{
@@ -73,7 +88,7 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
return nil, nil, err
}
if code != 200 { // if we did not successfully retrieve resources, then return 500 because auth was 1 but was invalid somehow
return nil, nil, fmt.Errorf("request to /cluster/resources/ resulted in %+v", res)
return nil, nil, fmt.Errorf("request to /cluster/resources resulted in %+v", res)
}
instances := map[uint]InstanceCard{}
@@ -103,5 +118,58 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
instance.NodeStatus = nodestatus
instances[vmid] = instance
}
ctx.Body = map[string]any{}
res, code, err = common.RequestGetAPI("/proxmox/cluster/tasks", ctx)
if err != nil {
return nil, nil, err
}
if code != 200 { // if we did not successfully retrieve tasks, then return 500 because auth was 1 but was invalid somehow
return nil, nil, fmt.Errorf("request to /cluster/tasks resulted in %+v", res)
}
for _, v := range ctx.Body["data"].([]any) {
task := Task{}
err := mapstructure.Decode(v, &task)
if err != nil {
return nil, nil, err
}
x, err := strconv.Atoi(task.ID)
task.VMID = uint(x)
if err != nil {
return nil, nil, err
}
if task.User != auth.Username { // task was not made by user (ie was not a power on/off task)
continue
} else if _, ok := instances[task.VMID]; !ok { // task does not refer to an instance in user's instances
continue
} else if instances[task.VMID].Node != task.Node { // task does not have the correct node reference (should not happen)
continue
} else if !(task.Type == "qmstart" || task.Type == "qmstop" || task.Type == "vzstart" || task.Type == "vzstop") { // task is not start/stop for qemu or lxc
continue
} else if !(task.Status == "running" || task.Status == "OK") { // task is not running or finished with status OK
continue
} else { // recent task is a start or stop task for user instance which is running or "OK"
// get /status/current which is updated faster than /cluster/resources
instance := instances[task.VMID]
path := fmt.Sprintf("/proxmox/nodes/%s/%s/%d/status/current", instance.Node, instance.Type, instance.VMID)
ctx.Body = map[string]any{}
res, code, err := common.RequestGetAPI(path, ctx)
if err != nil {
return nil, nil, err
}
if code != 200 { // if we did not successfully retrieve tasks, then return 500 because auth was 1 but was invalid somehow
return nil, nil, fmt.Errorf("request to %s resulted in %+v", path, res)
}
status := InstanceStatus{}
mapstructure.Decode(ctx.Body["data"], &status)
instance.Status = status.Status
instances[task.VMID] = instance
}
}
return instances, nodes, nil
}

View File

@@ -1,4 +1,4 @@
import { requestPVE, requestAPI, goToPage, setAppearance, getSearchSettings, goToURL, requestDash } from "./utils.js";
import { requestPVE, requestAPI, goToPage, setAppearance, getSearchSettings, goToURL, requestDash, setSVGSrc, setSVGAlt } from "./utils.js";
import { alert, dialog } from "./dialog.js";
import { setupClientSync } from "./clientsync.js";
import wfaInit from "../modules/wfa.js";
@@ -141,6 +141,16 @@ class InstanceCard extends HTMLElement {
}
}
setStatusLoading() {
this.status = "loading"
let statusicon = this.shadowRoot.querySelector("#status")
let powerbtn = this.shadowRoot.querySelector("#power-btn")
setSVGSrc(statusicon, "images/status/loading.svg")
setSVGAlt(statusicon, "instance is loading")
setSVGSrc(powerbtn, "images/status/loading.svg")
setSVGAlt(powerbtn, "")
}
async handlePowerButton () {
if (!this.actionLock) {
const header = `${this.status === "running" ? "Stop" : "Start"} VM ${this.vmid}`;
@@ -151,6 +161,7 @@ class InstanceCard extends HTMLElement {
const targetAction = this.status === "running" ? "stop" : "start";
const result = await requestPVE(`/nodes/${this.node.name}/${this.type}/${this.vmid}/status/${targetAction}`, "POST", { node: this.node.name, vmid: this.vmid });
this.setStatusLoading()
const waitFor = delay => new Promise(resolve => setTimeout(resolve, delay));

View File

@@ -16,11 +16,11 @@
<p class="w3-col l1 m2 w3-hide-small">{{.Type}}</p>
<div class="w3-col l2 m3 s6 flex row nowrap">
{{if eq .Status "running"}}
<svg aria-label="instance is running"><use href="images/status/active.svg#symb"></svg>
<svg id="status" aria-label="instance is running"><use href="images/status/active.svg#symb"></svg>
{{else if eq .Status "stopped"}}
<svg aria-label="instance is stopped"><use href="images/status/inactive.svg#symb"></svg>
<svg id="status" aria-label="instance is stopped"><use href="images/status/inactive.svg#symb"></svg>
{{else if eq .Status "loading"}}
<svg aria-label="instance is loading"><use href="images/status/loading.svg#symb"></svg>
<svg id="status" aria-label="instance is loading"><use href="images/status/loading.svg#symb"></svg>
{{else}}
{{end}}
<p>{{.Status}}</p>