initial changes for API v2.0.0:
- added access manager api token to auth object - update account page to show pool based resource quotas - update config logic to use pool based resource quotas - minor improvements and cleanup
This commit is contained in:
+1
-1
@@ -38,7 +38,7 @@ func Run(configPath *string) {
|
||||
router.GET("/settings", routes.HandleGETSettings)
|
||||
|
||||
// run on all interfaces with port
|
||||
log.Fatal(router.Run(fmt.Sprintf("0.0.0.0:%d", common.Global.Port)))
|
||||
log.Fatal("[Error] starting gin router: ", router.Run(fmt.Sprintf("0.0.0.0:%d", common.Global.Port)))
|
||||
}
|
||||
|
||||
// setup static resources under web (css, images, modules, scripts)
|
||||
|
||||
+4
-3
@@ -48,9 +48,10 @@ type RequestContext struct {
|
||||
}
|
||||
|
||||
type Auth struct {
|
||||
Username string
|
||||
Token string
|
||||
CSRF string
|
||||
Username string
|
||||
Token string
|
||||
CSRF string
|
||||
AccessManagerTicket string
|
||||
}
|
||||
|
||||
type Icon struct {
|
||||
|
||||
+22
-10
@@ -24,12 +24,12 @@ import (
|
||||
func GetConfig(configPath string) Config {
|
||||
content, err := os.ReadFile(configPath)
|
||||
if err != nil {
|
||||
log.Fatal("Error when opening config file: ", err)
|
||||
log.Fatal("[Error] when opening config file: ", err)
|
||||
}
|
||||
var config Config
|
||||
err = json.Unmarshal(content, &config)
|
||||
if err != nil {
|
||||
log.Fatal("Error during parsing config file: ", err)
|
||||
log.Fatal("[Error] during parsing config file: ", err)
|
||||
}
|
||||
return config
|
||||
}
|
||||
@@ -54,7 +54,7 @@ func MinifyStatic(m *minify.M, files embed.FS) map[string]StaticFile {
|
||||
if !entry.IsDir() {
|
||||
v, err := files.ReadFile(path)
|
||||
if err != nil {
|
||||
log.Fatalf("error parsing template file %s: %s", path, err.Error())
|
||||
log.Fatalf("[Error] parsing template file %s: %s", path, err.Error())
|
||||
}
|
||||
x := strings.Split(entry.Name(), ".")
|
||||
if len(x) >= 2 { // file has extension
|
||||
@@ -62,7 +62,7 @@ func MinifyStatic(m *minify.M, files embed.FS) map[string]StaticFile {
|
||||
if ok && mimetype.Minifier != nil { // if the extension is mapped in MimeTypes and has a minifier
|
||||
min, err := m.String(mimetype.Type, string(v)) // try to minify
|
||||
if err != nil {
|
||||
log.Fatalf("error minifying file %s: %s", path, err.Error())
|
||||
log.Fatalf("[Error] minifying file %s: %s", path, err.Error())
|
||||
}
|
||||
minified[path] = StaticFile{
|
||||
Data: min,
|
||||
@@ -185,7 +185,6 @@ func RequestGetAPI(path string, context RequestContext, body any) (*http.Respons
|
||||
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)
|
||||
@@ -208,10 +207,11 @@ func GetAuth(c *gin.Context) (Auth, error) {
|
||||
username, errUsername := c.Cookie("username")
|
||||
token, errToken := c.Cookie("PVEAuthCookie")
|
||||
csrf, errCSRF := c.Cookie("CSRFPreventionToken")
|
||||
if errUsername != nil || errAuth != nil || errToken != nil || errCSRF != nil {
|
||||
access, errAccess := c.Cookie("PAASAccessManagerTicket")
|
||||
if errUsername != nil || errAuth != nil || errToken != nil || errCSRF != nil || errAccess != nil {
|
||||
return Auth{}, fmt.Errorf("error occured getting user cookies: (auth: %s, token: %s, csrf: %s)", errAuth, errToken, errCSRF)
|
||||
} else {
|
||||
return Auth{username, token, csrf}, nil
|
||||
return Auth{username, token, csrf, access}, nil
|
||||
}
|
||||
}
|
||||
|
||||
@@ -239,13 +239,25 @@ func FormatNumber(val int64, base int64) (float64, string) {
|
||||
steps++
|
||||
}
|
||||
|
||||
if base == 1000 {
|
||||
switch base {
|
||||
case 1000:
|
||||
prefixes := []string{"", "K", "M", "G", "T"}
|
||||
return valf, prefixes[steps]
|
||||
} else if base == 1024 {
|
||||
case 1024:
|
||||
prefixes := []string{"", "Ki", "Mi", "Gi", "Ti"}
|
||||
return valf, prefixes[steps]
|
||||
} else {
|
||||
default:
|
||||
return 0, ""
|
||||
}
|
||||
}
|
||||
|
||||
func GetRequestContextFromCookies(auth Auth) RequestContext {
|
||||
return RequestContext{
|
||||
Cookies: map[string]string{
|
||||
"username": auth.Username,
|
||||
"PVEAuthCookie": auth.Token,
|
||||
"CSRFPreventionToken": auth.CSRF,
|
||||
"PAASAccessManagerTicket": auth.AccessManagerTicket,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+122
-130
@@ -3,6 +3,7 @@ package routes
|
||||
import (
|
||||
"fmt"
|
||||
"net/http"
|
||||
paas "proxmoxaas-common-lib"
|
||||
"proxmoxaas-dashboard/app/common"
|
||||
|
||||
"github.com/gerow/go-color"
|
||||
@@ -12,13 +13,7 @@ import (
|
||||
|
||||
type Account struct {
|
||||
Username string
|
||||
Pools map[string]bool
|
||||
Nodes map[string]bool
|
||||
VMID struct {
|
||||
Min int
|
||||
Max int
|
||||
}
|
||||
Resources map[string]map[string]any
|
||||
Pools map[string]paas.Pool
|
||||
}
|
||||
|
||||
// numerical constraint
|
||||
@@ -103,171 +98,168 @@ var Green = color.RGB{
|
||||
func HandleGETAccount(c *gin.Context) {
|
||||
auth, err := common.GetAuth(c)
|
||||
if err == nil {
|
||||
account, err := GetUserAccount(auth)
|
||||
pools, err := GetUserPools(auth)
|
||||
if err != nil {
|
||||
common.HandleNonFatalError(c, err)
|
||||
return
|
||||
}
|
||||
|
||||
// for each resource category, create a resource chart
|
||||
for category, resources := range account.Resources {
|
||||
for resource, v := range resources {
|
||||
switch t := v.(type) {
|
||||
case NumericResource:
|
||||
avail, prefix := common.FormatNumber(t.Total.Avail*t.Multiplier, t.Base)
|
||||
account.Resources[category][resource] = ResourceChart{
|
||||
Type: t.Type,
|
||||
Display: t.Display,
|
||||
Name: t.Name,
|
||||
Used: t.Total.Used,
|
||||
Max: t.Total.Max,
|
||||
Avail: avail,
|
||||
Prefix: prefix,
|
||||
Unit: t.Unit,
|
||||
ColorHex: InterpolateColorHSV(Green, Red, float64(t.Total.Used)/float64(t.Total.Max)).ToHTML(),
|
||||
}
|
||||
case StorageResource:
|
||||
avail, prefix := common.FormatNumber(t.Total.Avail*t.Multiplier, t.Base)
|
||||
account.Resources[category][resource] = ResourceChart{
|
||||
Type: t.Type,
|
||||
Display: t.Display,
|
||||
Name: t.Name,
|
||||
Used: t.Total.Used,
|
||||
Max: t.Total.Max,
|
||||
Avail: avail,
|
||||
Prefix: prefix,
|
||||
Unit: t.Unit,
|
||||
ColorHex: InterpolateColorHSV(Green, Red, float64(t.Total.Used)/float64(t.Total.Max)).ToHTML(),
|
||||
}
|
||||
case ListResource:
|
||||
l := struct {
|
||||
Type string
|
||||
Display bool
|
||||
Resources []ResourceChart
|
||||
}{
|
||||
Type: t.Type,
|
||||
Display: t.Display,
|
||||
Resources: []ResourceChart{},
|
||||
}
|
||||
|
||||
for _, r := range t.Total {
|
||||
l.Resources = append(l.Resources, ResourceChart{
|
||||
for poolname, pool := range pools {
|
||||
// for each resource category
|
||||
for category := range pool.Resources {
|
||||
// for each resource in each category
|
||||
for resource, v := range pool.Resources[category].(map[string]any) {
|
||||
// create a resource chart for resource depending on resource type
|
||||
switch t := v.(type) {
|
||||
case NumericResource:
|
||||
avail, prefix := common.FormatNumber(t.Total.Avail*t.Multiplier, t.Base)
|
||||
pools[poolname].Resources[category].(map[string]any)[resource] = ResourceChart{
|
||||
Type: t.Type,
|
||||
Display: t.Display,
|
||||
Name: r.Name,
|
||||
Used: r.Used,
|
||||
Max: r.Max,
|
||||
Avail: float64(r.Avail), // usually an int
|
||||
Unit: "",
|
||||
ColorHex: InterpolateColorHSV(Green, Red, float64(r.Used)/float64(r.Max)).ToHTML(),
|
||||
})
|
||||
Name: t.Name,
|
||||
Used: t.Total.Used,
|
||||
Max: t.Total.Max,
|
||||
Avail: avail,
|
||||
Prefix: prefix,
|
||||
Unit: t.Unit,
|
||||
ColorHex: InterpolateColorHSV(Green, Red, float64(t.Total.Used)/float64(t.Total.Max)).ToHTML(),
|
||||
}
|
||||
case StorageResource:
|
||||
avail, prefix := common.FormatNumber(t.Total.Avail*t.Multiplier, t.Base)
|
||||
pools[poolname].Resources[category].(map[string]any)[resource] = ResourceChart{
|
||||
Type: t.Type,
|
||||
Display: t.Display,
|
||||
Name: t.Name,
|
||||
Used: t.Total.Used,
|
||||
Max: t.Total.Max,
|
||||
Avail: avail,
|
||||
Prefix: prefix,
|
||||
Unit: t.Unit,
|
||||
ColorHex: InterpolateColorHSV(Green, Red, float64(t.Total.Used)/float64(t.Total.Max)).ToHTML(),
|
||||
}
|
||||
case ListResource:
|
||||
l := struct {
|
||||
Type string
|
||||
Display bool
|
||||
Resources []ResourceChart
|
||||
}{
|
||||
Type: t.Type,
|
||||
Display: t.Display,
|
||||
Resources: []ResourceChart{},
|
||||
}
|
||||
|
||||
for _, r := range t.Total {
|
||||
l.Resources = append(l.Resources, ResourceChart{
|
||||
Type: t.Type,
|
||||
Display: t.Display,
|
||||
Name: r.Name,
|
||||
Used: r.Used,
|
||||
Max: r.Max,
|
||||
Avail: float64(r.Avail), // usually an int
|
||||
Unit: "",
|
||||
ColorHex: InterpolateColorHSV(Green, Red, float64(r.Used)/float64(r.Max)).ToHTML(),
|
||||
})
|
||||
}
|
||||
pools[poolname].Resources[category].(map[string]any)[resource] = l
|
||||
}
|
||||
account.Resources[category][resource] = l
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
c.HTML(http.StatusOK, "html/account.html", gin.H{
|
||||
"global": common.Global,
|
||||
"page": "account",
|
||||
"account": account,
|
||||
"global": common.Global,
|
||||
"page": "account",
|
||||
"account": map[string]any{
|
||||
"Username": auth.Username,
|
||||
"Pools": pools,
|
||||
},
|
||||
})
|
||||
} else {
|
||||
c.Redirect(http.StatusFound, "/login") // if user is not authed, redirect user to login page
|
||||
}
|
||||
}
|
||||
|
||||
func GetUserAccount(auth common.Auth) (Account, error) {
|
||||
account := Account{
|
||||
Resources: map[string]map[string]any{},
|
||||
}
|
||||
func GetUserPools(auth common.Auth) (map[string]paas.Pool, error) {
|
||||
pools := map[string]paas.Pool{}
|
||||
|
||||
ctx := common.RequestContext{
|
||||
Cookies: map[string]string{
|
||||
"username": auth.Username,
|
||||
"PVEAuthCookie": auth.Token,
|
||||
"CSRFPreventionToken": auth.CSRF,
|
||||
},
|
||||
}
|
||||
|
||||
// get user account basic data
|
||||
// get all pools
|
||||
ctx := common.GetRequestContextFromCookies(auth)
|
||||
body := map[string]any{}
|
||||
res, code, err := common.RequestGetAPI("/user/config/cluster", ctx, &body)
|
||||
res, code, err := common.RequestGetAPI("/access/pools", ctx, &body)
|
||||
if err != nil {
|
||||
return account, err
|
||||
return pools, err
|
||||
}
|
||||
if code != 200 {
|
||||
return account, fmt.Errorf("request to /user/config/cluster resulted in %+v", res)
|
||||
return pools, fmt.Errorf("request to /access/pools resulted in %+v", res)
|
||||
}
|
||||
err = mapstructure.Decode(body, &account)
|
||||
err = mapstructure.Decode(body["pools"].(map[string]any), &pools)
|
||||
if err != nil {
|
||||
return account, err
|
||||
} else {
|
||||
account.Username = auth.Username
|
||||
return pools, err
|
||||
}
|
||||
|
||||
body = map[string]any{}
|
||||
// get user resources
|
||||
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 := body
|
||||
|
||||
// get global config for resource type metadata
|
||||
body = map[string]any{}
|
||||
// get resource meta data
|
||||
res, code, err = common.RequestGetAPI("/global/config/resources", ctx, &body)
|
||||
if err != nil {
|
||||
return account, err
|
||||
return pools, err
|
||||
}
|
||||
if code != 200 {
|
||||
return account, fmt.Errorf("request to /global/config/resources resulted in %+v", res)
|
||||
return pools, fmt.Errorf("request to /global/config/resources resulted in %+v", res)
|
||||
}
|
||||
meta := body["resources"].(map[string]any)
|
||||
|
||||
// build each resource by its meta type
|
||||
for k, v := range meta {
|
||||
m := v.(map[string]any)
|
||||
t := m["type"].(string)
|
||||
r := resources[k].(map[string]any)
|
||||
category := m["category"].(string)
|
||||
if _, ok := account.Resources[category]; !ok {
|
||||
account.Resources[category] = map[string]any{}
|
||||
}
|
||||
if t == "numeric" {
|
||||
n := NumericResource{}
|
||||
n.Type = t
|
||||
err_m := mapstructure.Decode(m, &n)
|
||||
err_r := mapstructure.Decode(r, &n)
|
||||
if err_m != nil || err_r != nil {
|
||||
return account, fmt.Errorf("%s\n%s", err_m.Error(), err_r.Error())
|
||||
// for each pool
|
||||
for poolname, pool := range pools {
|
||||
// for each resource in pool data
|
||||
for k, v := range pool.Resources {
|
||||
m := meta[k].(map[string]any)
|
||||
t := m["type"].(string)
|
||||
r := v.(map[string]any)
|
||||
category := m["category"].(string)
|
||||
|
||||
// create a category if it does not already exist
|
||||
if _, ok := pool.Resources[category]; !ok {
|
||||
pool.Resources[category] = map[string]any{}
|
||||
}
|
||||
account.Resources[category][k] = n
|
||||
} else if t == "storage" {
|
||||
n := StorageResource{}
|
||||
n.Type = t
|
||||
err_m := mapstructure.Decode(m, &n)
|
||||
err_r := mapstructure.Decode(r, &n)
|
||||
if err_m != nil || err_r != nil {
|
||||
return account, fmt.Errorf("%s\n%s", err_m.Error(), err_r.Error())
|
||||
|
||||
// depending on type, decode the pool data into the corresponding resource type
|
||||
switch t {
|
||||
case "numeric":
|
||||
n := NumericResource{}
|
||||
n.Type = t
|
||||
err_m := mapstructure.Decode(m, &n)
|
||||
err_r := mapstructure.Decode(r, &n)
|
||||
if err_m != nil || err_r != nil {
|
||||
return pools, fmt.Errorf("%s\n%s", err_m.Error(), err_r.Error())
|
||||
}
|
||||
pools[poolname].Resources[category].(map[string]any)[k] = n
|
||||
case "storage":
|
||||
n := StorageResource{}
|
||||
n.Type = t
|
||||
err_m := mapstructure.Decode(m, &n)
|
||||
err_r := mapstructure.Decode(r, &n)
|
||||
if err_m != nil || err_r != nil {
|
||||
return pools, fmt.Errorf("%s\n%s", err_m.Error(), err_r.Error())
|
||||
}
|
||||
pools[poolname].Resources[category].(map[string]any)[k] = n
|
||||
case "list":
|
||||
n := ListResource{}
|
||||
n.Type = t
|
||||
err_m := mapstructure.Decode(m, &n)
|
||||
err_r := mapstructure.Decode(r, &n)
|
||||
if err_m != nil || err_r != nil {
|
||||
return pools, fmt.Errorf("%s\n%s", err_m.Error(), err_r.Error())
|
||||
}
|
||||
pools[poolname].Resources[category].(map[string]any)[k] = n
|
||||
}
|
||||
account.Resources[category][k] = n
|
||||
} else if t == "list" {
|
||||
n := ListResource{}
|
||||
n.Type = t
|
||||
err_m := mapstructure.Decode(m, &n)
|
||||
err_r := mapstructure.Decode(r, &n)
|
||||
if err_m != nil || err_r != nil {
|
||||
return account, fmt.Errorf("%s\n%s", err_m.Error(), err_r.Error())
|
||||
}
|
||||
account.Resources[category][k] = n
|
||||
|
||||
// delete the old entry, only categories should be left at the end of the loop
|
||||
delete(pools[poolname].Resources, k)
|
||||
}
|
||||
}
|
||||
|
||||
return account, nil
|
||||
return pools, nil
|
||||
}
|
||||
|
||||
// interpolate between min and max by normalized (0 - 1) val
|
||||
|
||||
+1
-10
@@ -2,7 +2,6 @@ package routes
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"proxmoxaas-dashboard/app/common"
|
||||
"time"
|
||||
@@ -39,8 +38,6 @@ func HandleGETBackups(c *gin.Context) {
|
||||
common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance config: %s", err.Error()))
|
||||
}
|
||||
|
||||
log.Printf("%+v", backups)
|
||||
|
||||
c.HTML(http.StatusOK, "html/backups.html", gin.H{
|
||||
"global": common.Global,
|
||||
"page": "backups",
|
||||
@@ -79,13 +76,7 @@ func HandleGETBackupsFragment(c *gin.Context) {
|
||||
func GetInstanceBackups(vm common.VMPath, auth common.Auth) ([]InstanceBackup, error) {
|
||||
backups := []InstanceBackup{}
|
||||
path := fmt.Sprintf("/cluster/%s/%s/%s/backup", vm.Node, vm.Type, vm.VMID)
|
||||
ctx := common.RequestContext{
|
||||
Cookies: map[string]string{
|
||||
"username": auth.Username,
|
||||
"PVEAuthCookie": auth.Token,
|
||||
"CSRFPreventionToken": auth.CSRF,
|
||||
},
|
||||
}
|
||||
ctx := common.GetRequestContextFromCookies(auth)
|
||||
body := []any{}
|
||||
res, code, err := common.RequestGetAPI(path, ctx, &body)
|
||||
if err != nil {
|
||||
|
||||
+22
-47
@@ -15,16 +15,7 @@ import (
|
||||
// imported types from fabric
|
||||
|
||||
type InstanceConfig struct {
|
||||
Type paas.InstanceType `json:"type"`
|
||||
Name string `json:"name"`
|
||||
CPU string `json:"cpu"`
|
||||
Cores uint64 `json:"cores"`
|
||||
Memory uint64 `json:"memory"`
|
||||
Swap uint64 `json:"swap"`
|
||||
Volumes map[string]*paas.Volume `json:"volumes"`
|
||||
Nets map[string]*paas.Net `json:"nets"`
|
||||
Devices map[string]*paas.Device `json:"devices"`
|
||||
Boot paas.BootOrder `json:"boot"`
|
||||
paas.Instance `mapstructure:",squash"`
|
||||
// overrides
|
||||
ProctypeSelect common.Select
|
||||
}
|
||||
@@ -35,17 +26,13 @@ type GlobalConfig struct {
|
||||
}
|
||||
}
|
||||
|
||||
type UserConfigResources struct {
|
||||
type PoolConfig struct {
|
||||
CPU struct {
|
||||
Global []CPUConfig
|
||||
Nodes map[string][]CPUConfig
|
||||
Global []paas.MatchLimit
|
||||
Nodes map[string][]paas.MatchLimit
|
||||
}
|
||||
}
|
||||
|
||||
type CPUConfig struct {
|
||||
Name string
|
||||
}
|
||||
|
||||
func HandleGETConfig(c *gin.Context) {
|
||||
auth, err := common.GetAuth(c)
|
||||
if err == nil {
|
||||
@@ -61,13 +48,13 @@ func HandleGETConfig(c *gin.Context) {
|
||||
}
|
||||
|
||||
if config.Type == "VM" { // if VM, fetch CPU types from node
|
||||
config.ProctypeSelect, err = GetCPUTypes(vm_path, auth)
|
||||
config.ProctypeSelect, err = GetCPUTypes(vm_path, config.Pool, auth)
|
||||
if err != nil {
|
||||
common.HandleNonFatalError(c, fmt.Errorf("error encountered getting proctypes: %s", err.Error()))
|
||||
}
|
||||
}
|
||||
for i, cpu := range config.ProctypeSelect.Options {
|
||||
if cpu.Value == config.CPU {
|
||||
if cpu.Value == config.Proctype {
|
||||
config.ProctypeSelect.Options[i].Selected = true
|
||||
}
|
||||
}
|
||||
@@ -181,13 +168,7 @@ func HandleGETConfigBootFragment(c *gin.Context) {
|
||||
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{
|
||||
Cookies: map[string]string{
|
||||
"username": auth.Username,
|
||||
"PVEAuthCookie": auth.Token,
|
||||
"CSRFPreventionToken": auth.CSRF,
|
||||
},
|
||||
}
|
||||
ctx := common.GetRequestContextFromCookies(auth)
|
||||
body := map[string]any{}
|
||||
res, code, err := common.RequestGetAPI(path, ctx, &body)
|
||||
if err != nil {
|
||||
@@ -208,20 +189,14 @@ func GetInstanceConfig(vm common.VMPath, auth common.Auth) (InstanceConfig, erro
|
||||
return config, nil
|
||||
}
|
||||
|
||||
func GetCPUTypes(vm common.VMPath, auth common.Auth) (common.Select, error) {
|
||||
func GetCPUTypes(vm common.VMPath, pool string, auth common.Auth) (common.Select, error) {
|
||||
cputypes := common.Select{
|
||||
ID: "proctype",
|
||||
Required: true,
|
||||
}
|
||||
|
||||
// get global resource config
|
||||
ctx := common.RequestContext{
|
||||
Cookies: map[string]string{
|
||||
"username": auth.Username,
|
||||
"PVEAuthCookie": auth.Token,
|
||||
"CSRFPreventionToken": auth.CSRF,
|
||||
},
|
||||
}
|
||||
ctx := common.GetRequestContextFromCookies(auth)
|
||||
body := map[string]any{}
|
||||
path := "/global/config/resources"
|
||||
res, code, err := common.RequestGetAPI(path, ctx, &body)
|
||||
@@ -231,15 +206,15 @@ func GetCPUTypes(vm common.VMPath, auth common.Auth) (common.Select, error) {
|
||||
if code != 200 {
|
||||
return cputypes, fmt.Errorf("request to %s resulted in %+v", path, res)
|
||||
}
|
||||
global := GlobalConfig{}
|
||||
err = mapstructure.Decode(body["resources"], &global)
|
||||
globalConfig := GlobalConfig{}
|
||||
err = mapstructure.Decode(body["resources"], &globalConfig)
|
||||
if err != nil {
|
||||
return cputypes, err
|
||||
}
|
||||
|
||||
// get user resource config
|
||||
// get pool resource config
|
||||
body = map[string]any{}
|
||||
path = "/user/config/resources"
|
||||
path = fmt.Sprintf("/access/pools/%s", pool)
|
||||
res, code, err = common.RequestGetAPI(path, ctx, &body)
|
||||
if err != nil {
|
||||
return cputypes, err
|
||||
@@ -247,21 +222,21 @@ func GetCPUTypes(vm common.VMPath, auth common.Auth) (common.Select, error) {
|
||||
if code != 200 {
|
||||
return cputypes, fmt.Errorf("request to %s resulted in %+v", path, res)
|
||||
}
|
||||
user := UserConfigResources{}
|
||||
err = mapstructure.Decode(body, &user)
|
||||
poolCPUConfig := PoolConfig{}
|
||||
err = mapstructure.Decode(body["pool"].(map[string]any)["resources"], &poolCPUConfig)
|
||||
if err != nil {
|
||||
return cputypes, err
|
||||
}
|
||||
|
||||
// use node specific rules if present, otherwise use global rules
|
||||
var userCPU []CPUConfig
|
||||
if _, ok := user.CPU.Nodes[vm.Node]; ok {
|
||||
userCPU = user.CPU.Nodes[vm.Node]
|
||||
var userCPU []paas.MatchLimit
|
||||
if _, ok := poolCPUConfig.CPU.Nodes[vm.Node]; ok {
|
||||
userCPU = poolCPUConfig.CPU.Nodes[vm.Node]
|
||||
} else {
|
||||
userCPU = user.CPU.Global
|
||||
userCPU = poolCPUConfig.CPU.Global
|
||||
}
|
||||
|
||||
if global.CPU.Whitelist { // cpu is a whitelist
|
||||
if globalConfig.CPU.Whitelist { // cpu is a whitelist
|
||||
for _, cpu := range userCPU { // for each cpu type in user config add it to the options
|
||||
cputypes.Options = append(cputypes.Options, common.Option{
|
||||
Display: cpu.Name,
|
||||
@@ -280,7 +255,7 @@ func GetCPUTypes(vm common.VMPath, auth common.Auth) (common.Select, error) {
|
||||
return cputypes, fmt.Errorf("request to %s resulted in %+v", path, res)
|
||||
}
|
||||
supported := struct {
|
||||
data []CPUConfig
|
||||
data []paas.MatchLimit
|
||||
}{}
|
||||
err = mapstructure.Decode(body, supported)
|
||||
if err != nil {
|
||||
@@ -289,7 +264,7 @@ func GetCPUTypes(vm common.VMPath, auth common.Auth) (common.Select, error) {
|
||||
|
||||
// for each node supported cpu type, if it is NOT in the user's config (aka is not blacklisted) then add it to the options
|
||||
for _, cpu := range supported.data {
|
||||
contains := slices.ContainsFunc(userCPU, func(c CPUConfig) bool {
|
||||
contains := slices.ContainsFunc(userCPU, func(c paas.MatchLimit) bool {
|
||||
return c.Name == cpu.Name
|
||||
})
|
||||
if !contains {
|
||||
|
||||
+17
-19
@@ -83,13 +83,8 @@ func HandleGETInstancesFragment(c *gin.Context) {
|
||||
}
|
||||
|
||||
func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]Node, error) {
|
||||
ctx := common.RequestContext{
|
||||
Cookies: map[string]string{
|
||||
"PVEAuthCookie": auth.Token,
|
||||
"CSRFPreventionToken": auth.CSRF,
|
||||
},
|
||||
}
|
||||
body := map[string]any{}
|
||||
ctx := common.GetRequestContextFromCookies(auth)
|
||||
body := []any{}
|
||||
res, code, err := common.RequestGetAPI("/proxmox/cluster/resources", ctx, &body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -102,16 +97,17 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
|
||||
nodes := map[string]Node{}
|
||||
|
||||
// parse /proxmox/cluster/resources to separate instances and nodes
|
||||
for _, v := range body["data"].([]any) {
|
||||
for _, v := range body {
|
||||
m := v.(map[string]any)
|
||||
if m["type"] == "node" { // if type is node -> parse as Node object
|
||||
switch m["type"] {
|
||||
case "node": // if type is node -> parse as Node object
|
||||
node := Node{}
|
||||
err := mapstructure.Decode(v, &node)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
nodes[node.Node] = node
|
||||
} else if m["type"] == "lxc" || m["type"] == "qemu" { // if type is lxc or qemu -> parse as InstanceCard object
|
||||
case "lxc", "qemu": // if type is lxc or qemu -> parse as InstanceCard object
|
||||
instance := InstanceCard{}
|
||||
err := mapstructure.Decode(v, &instance)
|
||||
if err != nil {
|
||||
@@ -127,9 +123,10 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
|
||||
// set instance's config link path
|
||||
instance.ConfigPath = fmt.Sprintf("config?node=%s&type=%s&vmid=%d", instance.Node, instance.Type, instance.VMID)
|
||||
// set the instance's console link path
|
||||
if instance.Type == "qemu" {
|
||||
switch instance.Type {
|
||||
case "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" {
|
||||
case "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)
|
||||
}
|
||||
// set the instance's backups link path
|
||||
@@ -138,7 +135,7 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
|
||||
instances[vmid] = instance
|
||||
}
|
||||
|
||||
body = map[string]any{}
|
||||
body = []any{}
|
||||
res, code, err = common.RequestGetAPI("/proxmox/cluster/tasks", ctx, &body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -151,7 +148,7 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
|
||||
expected_state := map[uint]string{}
|
||||
|
||||
// iterate through recent user accessible tasks to find the task most recently made on an instance
|
||||
for _, v := range body["data"].([]any) {
|
||||
for _, v := range body {
|
||||
// parse task as Task object
|
||||
task := Task{}
|
||||
err := mapstructure.Decode(v, &task)
|
||||
@@ -179,10 +176,11 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
|
||||
continue
|
||||
} 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
|
||||
most_recent_task[task.VMID] = task.EndTime // update the most recent task
|
||||
switch task.Type {
|
||||
case "qmstart", "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
|
||||
case "qmstop", "vzstop": // if the task was a stop task, update the expected state to stopped
|
||||
expected_state[task.VMID] = "stopped"
|
||||
}
|
||||
}
|
||||
@@ -195,7 +193,7 @@ 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)
|
||||
body = map[string]any{}
|
||||
body := map[string]any{}
|
||||
res, code, err := common.RequestGetAPI(path, ctx, &body)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
@@ -205,7 +203,7 @@ func GetClusterResources(auth common.Auth) (map[uint]InstanceCard, map[string]No
|
||||
}
|
||||
|
||||
status := InstanceStatus{}
|
||||
mapstructure.Decode(body["data"], &status)
|
||||
mapstructure.Decode(body, &status)
|
||||
|
||||
instance.Status = status.Status
|
||||
instances[vmid] = instance
|
||||
|
||||
+2
-2
@@ -27,7 +27,7 @@ func GetLoginRealms() ([]Realm, error) {
|
||||
ctx := common.RequestContext{
|
||||
Cookies: nil,
|
||||
}
|
||||
body := map[string]any{}
|
||||
body := []any{}
|
||||
res, code, err := common.RequestGetAPI("/proxmox/access/domains", ctx, &body)
|
||||
if err != nil {
|
||||
return realms, err
|
||||
@@ -36,7 +36,7 @@ func GetLoginRealms() ([]Realm, error) {
|
||||
return realms, fmt.Errorf("request to /proxmox/access/domains resulted in %+v", res)
|
||||
}
|
||||
|
||||
for _, v := range body["data"].([]any) {
|
||||
for _, v := range body {
|
||||
v = v.(map[string]any)
|
||||
realm := Realm{}
|
||||
err := mapstructure.Decode(v, &realm)
|
||||
|
||||
Reference in New Issue
Block a user