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 {
Cookies map[string]string
Body map[string]any
}
type Auth struct {

View File

@@ -10,6 +10,7 @@ import (
"io"
"io/fs"
"log"
"math"
"net/http"
"os"
"reflect"
@@ -22,6 +23,12 @@ import (
var TMPL *template.Template
var Global Config
type VMPath struct {
Node string
Type string
VMID string
}
func GetConfig(configPath string) Config {
content, err := os.ReadFile(configPath)
if err != nil {
@@ -159,7 +166,7 @@ func HandleNonFatalError(c *gin.Context, err error) {
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)
if err != nil {
return nil, 0, err
@@ -186,9 +193,18 @@ func RequestGetAPI(path string, context RequestContext) (*http.Response, int, er
return nil, response.StatusCode, err
}
err = json.Unmarshal(data, &context.Body)
if err != nil {
return nil, response.StatusCode, err
switch body.(type) { // write json to body object depending on type, currently supports map[string]any (ie json) or []any (ie array of json)
case *map[string]any:
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
@@ -205,3 +221,38 @@ func GetAuth(c *gin.Context) (Auth, error) {
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 (
"fmt"
"math"
"net/http"
"proxmoxaas-dashboard/app/common"
@@ -108,7 +107,7 @@ func HandleGETAccount(c *gin.Context) {
for k, v := range account.Resources {
switch t := v.(type) {
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{
Type: t.Type,
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(),
}
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{
Type: t.Type,
Display: t.Display,
@@ -181,45 +180,45 @@ func GetUserAccount(auth common.Auth) (Account, error) {
"PVEAuthCookie": auth.Token,
"CSRFPreventionToken": auth.CSRF,
},
Body: map[string]any{},
}
// 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 {
return account, err
}
if code != 200 {
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 {
return account, err
} else {
account.Username = auth.Username
}
ctx.Body = map[string]any{}
body = map[string]any{}
// 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 {
return account, err
}
if code != 200 {
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
res, code, err = common.RequestGetAPI("/global/config/resources", ctx)
res, code, err = common.RequestGetAPI("/global/config/resources", ctx, &body)
if err != nil {
return account, err
}
if code != 200 {
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
for k, v := range meta {
@@ -259,26 +258,6 @@ func GetUserAccount(auth common.Auth) (Account, error) {
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
func InterpolateColorHSV(min color.RGB, max color.RGB, val float64) color.RGB {
minhsl := min.ToHSL()

View File

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

View File

@@ -26,6 +26,7 @@ type InstanceCard struct {
NodeStatus string
ConfigPath string
ConsolePath string
BackupsPath string
}
// used in retriving cluster tasks
@@ -85,9 +86,9 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
"PVEAuthCookie": auth.Token,
"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 {
return nil, nil, err
}
@@ -99,7 +100,7 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
nodes := map[string]Node{}
// 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)
if m["type"] == "node" {
node := Node{}
@@ -126,11 +127,12 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
} 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.BackupsPath = fmt.Sprintf("backups?node=%s&type=%s&vmid=%d", instance.Node, instance.Type, instance.VMID)
instances[vmid] = instance
}
ctx.Body = map[string]any{}
res, code, err = common.RequestGetAPI("/proxmox/cluster/tasks", ctx)
body = map[string]any{}
res, code, err = common.RequestGetAPI("/proxmox/cluster/tasks", ctx, &body)
if err != nil {
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{}
expected_state := map[uint]string{}
for _, v := range ctx.Body["data"].([]any) {
for _, v := range body["data"].([]any) {
task := Task{}
err := mapstructure.Decode(v, &task)
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
instance := instances[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)
body = map[string]any{}
res, code, err := common.RequestGetAPI(path, ctx, &body)
if err != nil {
return nil, nil, err
}
@@ -190,7 +192,7 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
}
status := InstanceStatus{}
mapstructure.Decode(ctx.Body["data"], &status)
mapstructure.Decode(body["data"], &status)
instance.Status = status.Status
instances[vmid] = instance

View File

@@ -26,9 +26,10 @@ func GetLoginRealms() ([]Realm, error) {
ctx := common.RequestContext{
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 {
return realms, err
}
@@ -36,7 +37,7 @@ func GetLoginRealms() ([]Realm, error) {
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)
realm := Realm{}
err := mapstructure.Decode(v, &realm)