move getAPI request interface body to parameter,

move VMPath and FormatNumber methods to common utils
This commit is contained in:
2025-06-30 23:44:28 +00:00
parent a62fc83386
commit 8c339794b3
6 changed files with 100 additions and 89 deletions

View File

@@ -31,7 +31,6 @@ type RequestType int
type RequestContext struct { type RequestContext struct {
Cookies map[string]string Cookies map[string]string
Body map[string]any
} }
type Auth struct { type Auth struct {

View File

@@ -10,6 +10,7 @@ import (
"io" "io"
"io/fs" "io/fs"
"log" "log"
"math"
"net/http" "net/http"
"os" "os"
"reflect" "reflect"
@@ -22,6 +23,12 @@ import (
var TMPL *template.Template var TMPL *template.Template
var Global Config var Global Config
type VMPath struct {
Node string
Type string
VMID string
}
func GetConfig(configPath string) Config { func GetConfig(configPath string) Config {
content, err := os.ReadFile(configPath) content, err := os.ReadFile(configPath)
if err != nil { if err != nil {
@@ -159,7 +166,7 @@ func HandleNonFatalError(c *gin.Context, err error) {
c.Status(http.StatusInternalServerError) c.Status(http.StatusInternalServerError)
} }
func RequestGetAPI(path string, context RequestContext) (*http.Response, int, error) { func RequestGetAPI(path string, context RequestContext, body any) (*http.Response, int, error) {
req, err := http.NewRequest("GET", Global.API+path, nil) req, err := http.NewRequest("GET", Global.API+path, nil)
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
@@ -186,9 +193,18 @@ func RequestGetAPI(path string, context RequestContext) (*http.Response, int, er
return nil, response.StatusCode, err return nil, response.StatusCode, err
} }
err = json.Unmarshal(data, &context.Body) switch body.(type) { // write json to body object depending on type, currently supports map[string]any (ie json) or []any (ie array of json)
if err != nil { case *map[string]any:
return nil, response.StatusCode, err err = json.Unmarshal(data, &body)
if err != nil {
return nil, response.StatusCode, err
}
case *[]any:
err = json.Unmarshal(data, &body)
if err != nil {
return nil, response.StatusCode, err
}
default:
} }
return response, response.StatusCode, nil return response, response.StatusCode, nil
@@ -205,3 +221,38 @@ func GetAuth(c *gin.Context) (Auth, error) {
return Auth{username, token, csrf}, nil return Auth{username, token, csrf}, nil
} }
} }
func ExtractVMPath(c *gin.Context) (VMPath, error) {
req_node := c.Query("node")
req_type := c.Query("type")
req_vmid := c.Query("vmid")
if req_node == "" || req_type == "" || req_vmid == "" {
return VMPath{}, fmt.Errorf("request missing required values: (node: %s, type: %s, vmid: %s)", req_node, req_type, req_vmid)
}
vm_path := VMPath{
Node: req_node,
Type: req_type,
VMID: req_vmid,
}
return vm_path, nil
}
func FormatNumber(val int64, base int64) (float64, string) {
valf := float64(val)
basef := float64(base)
steps := 0
for math.Abs(valf) > basef && steps < 4 {
valf /= basef
steps++
}
if base == 1000 {
prefixes := []string{"", "K", "M", "G", "T"}
return valf, prefixes[steps]
} else if base == 1024 {
prefixes := []string{"", "Ki", "Mi", "Gi", "Ti"}
return valf, prefixes[steps]
} else {
return 0, ""
}
}

View File

@@ -2,7 +2,6 @@ package routes
import ( import (
"fmt" "fmt"
"math"
"net/http" "net/http"
"proxmoxaas-dashboard/app/common" "proxmoxaas-dashboard/app/common"
@@ -108,7 +107,7 @@ func HandleGETAccount(c *gin.Context) {
for k, v := range account.Resources { for k, v := range account.Resources {
switch t := v.(type) { switch t := v.(type) {
case NumericResource: case NumericResource:
avail, prefix := FormatNumber(t.Total.Avail*t.Multiplier, t.Base) avail, prefix := common.FormatNumber(t.Total.Avail*t.Multiplier, t.Base)
account.Resources[k] = ResourceChart{ account.Resources[k] = ResourceChart{
Type: t.Type, Type: t.Type,
Display: t.Display, Display: t.Display,
@@ -121,7 +120,7 @@ func HandleGETAccount(c *gin.Context) {
ColorHex: InterpolateColorHSV(Green, Red, float64(t.Total.Used)/float64(t.Total.Max)).ToHTML(), ColorHex: InterpolateColorHSV(Green, Red, float64(t.Total.Used)/float64(t.Total.Max)).ToHTML(),
} }
case StorageResource: case StorageResource:
avail, prefix := FormatNumber(t.Total.Avail*t.Multiplier, t.Base) avail, prefix := common.FormatNumber(t.Total.Avail*t.Multiplier, t.Base)
account.Resources[k] = ResourceChart{ account.Resources[k] = ResourceChart{
Type: t.Type, Type: t.Type,
Display: t.Display, Display: t.Display,
@@ -181,45 +180,45 @@ func GetUserAccount(auth common.Auth) (Account, error) {
"PVEAuthCookie": auth.Token, "PVEAuthCookie": auth.Token,
"CSRFPreventionToken": auth.CSRF, "CSRFPreventionToken": auth.CSRF,
}, },
Body: map[string]any{},
} }
// get user account basic data // get user account basic data
res, code, err := common.RequestGetAPI("/user/config/cluster", ctx) body := map[string]any{}
res, code, err := common.RequestGetAPI("/user/config/cluster", ctx, &body)
if err != nil { if err != nil {
return account, err return account, err
} }
if code != 200 { if code != 200 {
return account, fmt.Errorf("request to /user/config/cluster resulted in %+v", res) return account, fmt.Errorf("request to /user/config/cluster resulted in %+v", res)
} }
err = mapstructure.Decode(ctx.Body, &account) err = mapstructure.Decode(body, &account)
if err != nil { if err != nil {
return account, err return account, err
} else { } else {
account.Username = auth.Username account.Username = auth.Username
} }
ctx.Body = map[string]any{} body = map[string]any{}
// get user resources // get user resources
res, code, err = common.RequestGetAPI("/user/dynamic/resources", ctx) res, code, err = common.RequestGetAPI("/user/dynamic/resources", ctx, &body)
if err != nil { if err != nil {
return account, err return account, err
} }
if code != 200 { if code != 200 {
return account, fmt.Errorf("request to /user/dynamic/resources resulted in %+v", res) return account, fmt.Errorf("request to /user/dynamic/resources resulted in %+v", res)
} }
resources := ctx.Body resources := body
ctx.Body = map[string]any{} body = map[string]any{}
// get resource meta data // get resource meta data
res, code, err = common.RequestGetAPI("/global/config/resources", ctx) res, code, err = common.RequestGetAPI("/global/config/resources", ctx, &body)
if err != nil { if err != nil {
return account, err return account, err
} }
if code != 200 { if code != 200 {
return account, fmt.Errorf("request to /global/config/resources resulted in %+v", res) return account, fmt.Errorf("request to /global/config/resources resulted in %+v", res)
} }
meta := ctx.Body["resources"].(map[string]any) meta := body["resources"].(map[string]any)
// build each resource by its meta type // build each resource by its meta type
for k, v := range meta { for k, v := range meta {
@@ -259,26 +258,6 @@ func GetUserAccount(auth common.Auth) (Account, error) {
return account, nil return account, nil
} }
func FormatNumber(val int64, base int64) (float64, string) {
valf := float64(val)
basef := float64(base)
steps := 0
for math.Abs(valf) > basef && steps < 4 {
valf /= basef
steps++
}
if base == 1000 {
prefixes := []string{"", "K", "M", "G", "T"}
return valf, prefixes[steps]
} else if base == 1024 {
prefixes := []string{"", "Ki", "Mi", "Gi", "Ti"}
return valf, prefixes[steps]
} else {
return 0, ""
}
}
// interpolate between min and max by normalized (0 - 1) val // interpolate between min and max by normalized (0 - 1) val
func InterpolateColorHSV(min color.RGB, max color.RGB, val float64) color.RGB { func InterpolateColorHSV(min color.RGB, max color.RGB, val float64) color.RGB {
minhsl := min.ToHSL() minhsl := min.ToHSL()

View File

@@ -13,12 +13,6 @@ import (
"github.com/go-viper/mapstructure/v2" "github.com/go-viper/mapstructure/v2"
) )
type VMPath struct {
Node string
Type string
VMID string
}
// imported types from fabric // imported types from fabric
type InstanceConfig struct { type InstanceConfig struct {
@@ -56,7 +50,7 @@ type CPUConfig struct {
func HandleGETConfig(c *gin.Context) { func HandleGETConfig(c *gin.Context) {
auth, err := common.GetAuth(c) auth, err := common.GetAuth(c)
if err == nil { if err == nil {
vm_path, err := ExtractVMPath(c) vm_path, err := common.ExtractVMPath(c)
if err != nil { if err != nil {
common.HandleNonFatalError(c, err) common.HandleNonFatalError(c, err)
} }
@@ -91,7 +85,7 @@ func HandleGETConfig(c *gin.Context) {
func HandleGETConfigVolumesFragment(c *gin.Context) { func HandleGETConfigVolumesFragment(c *gin.Context) {
auth, err := common.GetAuth(c) auth, err := common.GetAuth(c)
if err == nil { if err == nil {
vm_path, err := ExtractVMPath(c) vm_path, err := common.ExtractVMPath(c)
if err != nil { if err != nil {
common.HandleNonFatalError(c, err) common.HandleNonFatalError(c, err)
} }
@@ -114,7 +108,7 @@ func HandleGETConfigVolumesFragment(c *gin.Context) {
func HandleGETConfigNetsFragment(c *gin.Context) { func HandleGETConfigNetsFragment(c *gin.Context) {
auth, err := common.GetAuth(c) auth, err := common.GetAuth(c)
if err == nil { if err == nil {
vm_path, err := ExtractVMPath(c) vm_path, err := common.ExtractVMPath(c)
if err != nil { if err != nil {
common.HandleNonFatalError(c, err) common.HandleNonFatalError(c, err)
} }
@@ -137,7 +131,7 @@ func HandleGETConfigNetsFragment(c *gin.Context) {
func HandleGETConfigDevicesFragment(c *gin.Context) { func HandleGETConfigDevicesFragment(c *gin.Context) {
auth, err := common.GetAuth(c) auth, err := common.GetAuth(c)
if err == nil { if err == nil {
vm_path, err := ExtractVMPath(c) vm_path, err := common.ExtractVMPath(c)
if err != nil { if err != nil {
common.HandleNonFatalError(c, err) common.HandleNonFatalError(c, err)
} }
@@ -160,7 +154,7 @@ func HandleGETConfigDevicesFragment(c *gin.Context) {
func HandleGETConfigBootFragment(c *gin.Context) { func HandleGETConfigBootFragment(c *gin.Context) {
auth, err := common.GetAuth(c) auth, err := common.GetAuth(c)
if err == nil { if err == nil {
vm_path, err := ExtractVMPath(c) vm_path, err := common.ExtractVMPath(c)
if err != nil { if err != nil {
common.HandleNonFatalError(c, err) common.HandleNonFatalError(c, err)
} }
@@ -180,22 +174,7 @@ func HandleGETConfigBootFragment(c *gin.Context) {
} }
} }
func ExtractVMPath(c *gin.Context) (VMPath, error) { func GetInstanceConfig(vm common.VMPath, auth common.Auth) (InstanceConfig, error) {
req_node := c.Query("node")
req_type := c.Query("type")
req_vmid := c.Query("vmid")
if req_node == "" || req_type == "" || req_vmid == "" {
return VMPath{}, fmt.Errorf("request missing required values: (node: %s, type: %s, vmid: %s)", req_node, req_type, req_vmid)
}
vm_path := VMPath{
Node: req_node,
Type: req_type,
VMID: req_vmid,
}
return vm_path, nil
}
func GetInstanceConfig(vm VMPath, auth common.Auth) (InstanceConfig, error) {
config := InstanceConfig{} config := InstanceConfig{}
path := fmt.Sprintf("/cluster/%s/%s/%s", vm.Node, vm.Type, vm.VMID) path := fmt.Sprintf("/cluster/%s/%s/%s", vm.Node, vm.Type, vm.VMID)
ctx := common.RequestContext{ ctx := common.RequestContext{
@@ -204,9 +183,9 @@ func GetInstanceConfig(vm VMPath, auth common.Auth) (InstanceConfig, error) {
"PVEAuthCookie": auth.Token, "PVEAuthCookie": auth.Token,
"CSRFPreventionToken": auth.CSRF, "CSRFPreventionToken": auth.CSRF,
}, },
Body: map[string]any{},
} }
res, code, err := common.RequestGetAPI(path, ctx) body := map[string]any{}
res, code, err := common.RequestGetAPI(path, ctx, &body)
if err != nil { if err != nil {
return config, err return config, err
} }
@@ -214,7 +193,7 @@ func GetInstanceConfig(vm VMPath, auth common.Auth) (InstanceConfig, error) {
return config, fmt.Errorf("request to %s resulted in %+v", path, res) return config, fmt.Errorf("request to %s resulted in %+v", path, res)
} }
err = mapstructure.Decode(ctx.Body, &config) err = mapstructure.Decode(body, &config)
if err != nil { if err != nil {
return config, err return config, err
} }
@@ -225,7 +204,7 @@ func GetInstanceConfig(vm VMPath, auth common.Auth) (InstanceConfig, error) {
return config, nil return config, nil
} }
func GetCPUTypes(vm VMPath, auth common.Auth) (common.Select, error) { func GetCPUTypes(vm common.VMPath, auth common.Auth) (common.Select, error) {
cputypes := common.Select{ cputypes := common.Select{
ID: "proctype", ID: "proctype",
Required: true, Required: true,
@@ -238,10 +217,10 @@ func GetCPUTypes(vm VMPath, auth common.Auth) (common.Select, error) {
"PVEAuthCookie": auth.Token, "PVEAuthCookie": auth.Token,
"CSRFPreventionToken": auth.CSRF, "CSRFPreventionToken": auth.CSRF,
}, },
Body: map[string]any{},
} }
body := map[string]any{}
path := "/global/config/resources" path := "/global/config/resources"
res, code, err := common.RequestGetAPI(path, ctx) res, code, err := common.RequestGetAPI(path, ctx, &body)
if err != nil { if err != nil {
return cputypes, err return cputypes, err
} }
@@ -249,15 +228,15 @@ func GetCPUTypes(vm VMPath, auth common.Auth) (common.Select, error) {
return cputypes, fmt.Errorf("request to %s resulted in %+v", path, res) return cputypes, fmt.Errorf("request to %s resulted in %+v", path, res)
} }
global := GlobalConfig{} global := GlobalConfig{}
err = mapstructure.Decode(ctx.Body["resources"], &global) err = mapstructure.Decode(body["resources"], &global)
if err != nil { if err != nil {
return cputypes, err return cputypes, err
} }
// get user resource config // get user resource config
ctx.Body = map[string]any{} body = map[string]any{}
path = "/user/config/resources" path = "/user/config/resources"
res, code, err = common.RequestGetAPI(path, ctx) res, code, err = common.RequestGetAPI(path, ctx, &body)
if err != nil { if err != nil {
return cputypes, err return cputypes, err
} }
@@ -265,7 +244,7 @@ func GetCPUTypes(vm VMPath, auth common.Auth) (common.Select, error) {
return cputypes, fmt.Errorf("request to %s resulted in %+v", path, res) return cputypes, fmt.Errorf("request to %s resulted in %+v", path, res)
} }
user := UserConfigResources{} user := UserConfigResources{}
err = mapstructure.Decode(ctx.Body, &user) err = mapstructure.Decode(body, &user)
if err != nil { if err != nil {
return cputypes, err return cputypes, err
} }
@@ -287,9 +266,9 @@ func GetCPUTypes(vm VMPath, auth common.Auth) (common.Select, error) {
} }
} else { // cpu is a blacklist } else { // cpu is a blacklist
// get the supported cpu types from the node // get the supported cpu types from the node
ctx.Body = map[string]any{} body = map[string]any{}
path = fmt.Sprintf("/proxmox/nodes/%s/capabilities/qemu/cpu", vm.Node) path = fmt.Sprintf("/proxmox/nodes/%s/capabilities/qemu/cpu", vm.Node)
res, code, err = common.RequestGetAPI(path, ctx) res, code, err = common.RequestGetAPI(path, ctx, &body)
if err != nil { if err != nil {
return cputypes, err return cputypes, err
} }
@@ -299,7 +278,7 @@ func GetCPUTypes(vm VMPath, auth common.Auth) (common.Select, error) {
supported := struct { supported := struct {
data []CPUConfig data []CPUConfig
}{} }{}
err = mapstructure.Decode(ctx.Body, supported) err = mapstructure.Decode(body, supported)
if err != nil { if err != nil {
return cputypes, err return cputypes, err
} }

View File

@@ -26,6 +26,7 @@ type InstanceCard struct {
NodeStatus string NodeStatus string
ConfigPath string ConfigPath string
ConsolePath string ConsolePath string
BackupsPath string
} }
// used in retriving cluster tasks // used in retriving cluster tasks
@@ -85,9 +86,9 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
"PVEAuthCookie": auth.Token, "PVEAuthCookie": auth.Token,
"CSRFPreventionToken": auth.CSRF, "CSRFPreventionToken": auth.CSRF,
}, },
Body: map[string]any{},
} }
res, code, err := common.RequestGetAPI("/proxmox/cluster/resources", ctx) body := map[string]any{}
res, code, err := common.RequestGetAPI("/proxmox/cluster/resources", ctx, &body)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -99,7 +100,7 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
nodes := map[string]Node{} nodes := map[string]Node{}
// if we successfully retrieved the resources, then process it and return index // if we successfully retrieved the resources, then process it and return index
for _, v := range ctx.Body["data"].([]any) { for _, v := range body["data"].([]any) {
m := v.(map[string]any) m := v.(map[string]any)
if m["type"] == "node" { if m["type"] == "node" {
node := Node{} node := Node{}
@@ -126,11 +127,12 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
} else if instance.Type == "lxc" { } 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) 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)
} }
instance.BackupsPath = fmt.Sprintf("backups?node=%s&type=%s&vmid=%d", instance.Node, instance.Type, instance.VMID)
instances[vmid] = instance instances[vmid] = instance
} }
ctx.Body = map[string]any{} body = map[string]any{}
res, code, err = common.RequestGetAPI("/proxmox/cluster/tasks", ctx) res, code, err = common.RequestGetAPI("/proxmox/cluster/tasks", ctx, &body)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -141,7 +143,7 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
most_recent_task := map[uint]uint{} most_recent_task := map[uint]uint{}
expected_state := map[uint]string{} expected_state := map[uint]string{}
for _, v := range ctx.Body["data"].([]any) { for _, v := range body["data"].([]any) {
task := Task{} task := Task{}
err := mapstructure.Decode(v, &task) err := mapstructure.Decode(v, &task)
if err != nil { if err != nil {
@@ -180,8 +182,8 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
// get /status/current which is updated faster than /cluster/resources // get /status/current which is updated faster than /cluster/resources
instance := instances[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{} body = map[string]any{}
res, code, err := common.RequestGetAPI(path, ctx) res, code, err := common.RequestGetAPI(path, ctx, &body)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
} }
@@ -190,7 +192,7 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
} }
status := InstanceStatus{} status := InstanceStatus{}
mapstructure.Decode(ctx.Body["data"], &status) mapstructure.Decode(body["data"], &status)
instance.Status = status.Status instance.Status = status.Status
instances[vmid] = instance instances[vmid] = instance

View File

@@ -26,9 +26,10 @@ func GetLoginRealms() ([]Realm, error) {
ctx := common.RequestContext{ ctx := common.RequestContext{
Cookies: nil, Cookies: nil,
Body: map[string]any{}, //Body: map[string]any{},
} }
res, code, err := common.RequestGetAPI("/proxmox/access/domains", ctx) body := map[string]any{}
res, code, err := common.RequestGetAPI("/proxmox/access/domains", ctx, &body)
if err != nil { if err != nil {
return realms, err return realms, err
} }
@@ -36,7 +37,7 @@ func GetLoginRealms() ([]Realm, error) {
return realms, fmt.Errorf("request to /proxmox/access/domains resulted in %+v", res) return realms, fmt.Errorf("request to /proxmox/access/domains resulted in %+v", res)
} }
for _, v := range ctx.Body["data"].([]any) { for _, v := range body["data"].([]any) {
v = v.(map[string]any) v = v.(map[string]any)
realm := Realm{} realm := Realm{}
err := mapstructure.Decode(v, &realm) err := mapstructure.Decode(v, &realm)