diff --git a/app/app.go b/app/app.go index 81d97b7..2c8d67c 100644 --- a/app/app.go +++ b/app/app.go @@ -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) diff --git a/app/common/types.go b/app/common/types.go index f38ad5c..902681b 100644 --- a/app/common/types.go +++ b/app/common/types.go @@ -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 { diff --git a/app/common/utils.go b/app/common/utils.go index 1561b3f..acbd91b 100644 --- a/app/common/utils.go +++ b/app/common/utils.go @@ -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, + }, + } +} diff --git a/app/routes/account.go b/app/routes/account.go index 499d125..f024c23 100644 --- a/app/routes/account.go +++ b/app/routes/account.go @@ -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 diff --git a/app/routes/backups.go b/app/routes/backups.go index 93aadd6..944d299 100644 --- a/app/routes/backups.go +++ b/app/routes/backups.go @@ -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 { diff --git a/app/routes/config.go b/app/routes/config.go index fcb6536..c43d6a3 100644 --- a/app/routes/config.go +++ b/app/routes/config.go @@ -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 { diff --git a/app/routes/index.go b/app/routes/index.go index f37b5ba..94e27e1 100644 --- a/app/routes/index.go +++ b/app/routes/index.go @@ -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 diff --git a/app/routes/login.go b/app/routes/login.go index f7a3c3a..29ecd86 100644 --- a/app/routes/login.go +++ b/app/routes/login.go @@ -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) diff --git a/proxmoxaas-common-lib b/proxmoxaas-common-lib index cc53d7b..52ac2c2 160000 --- a/proxmoxaas-common-lib +++ b/proxmoxaas-common-lib @@ -1 +1 @@ -Subproject commit cc53d7bdea6ead7ceba9bfd0c25e41392601094b +Subproject commit 52ac2c2b97050ef476005a2294e4433f10559735 diff --git a/web/html/account.html b/web/html/account.html index e531bb1..d15c14c 100644 --- a/web/html/account.html +++ b/web/html/account.html @@ -42,9 +42,6 @@

Account Details

Username: {{.account.Username}}

-

Pools: {{MapKeys .account.Pools ", "}}

-

VMID Range: {{.account.VMID.Min}} - {{.account.VMID.Max}}

-

Nodes: {{MapKeys .account.Nodes ", "}}

@@ -52,33 +49,9 @@
-
-

Cluster Resources

-
- {{range $category, $v := .account.Resources}} - {{if ne $category ""}} -

{{$category}}

- {{end}} -
- {{range $v}} - {{if .Display}} - {{if eq .Type "numeric"}} - {{template "resource-chart" .}} - {{end}} - {{if eq .Type "storage"}} - {{template "resource-chart" .}} - {{end}} - {{if eq .Type "list"}} - {{range .Resources}} - {{template "resource-chart" .}} - {{end}} - {{end}} - {{end}} - {{end}} -
- {{end}} -
-
+ {{range $poolname, $pool := .account.Pools}} + {{template "pool-resources" $pool}} + {{end}} - +