Compare commits
20
Commits
5db28a4cf1
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2a6641f03c | ||
|
|
a5b20519d4 | ||
|
|
e000a90456 | ||
|
|
b5c8576ab5 | ||
|
|
e2039a15b1 | ||
|
|
492ae56d48 | ||
|
|
033017864e | ||
|
|
cbed40d904 | ||
|
|
075ea07766 | ||
|
|
91be26e320 | ||
|
|
ff63d8dc15 | ||
|
|
ffa58e4218 | ||
|
|
613f01456f | ||
|
|
e8fd120ead | ||
|
|
ce1576fbd6 | ||
|
|
0caf27eb7d | ||
|
|
7f5c016e31 | ||
|
|
4582d9726d | ||
|
|
1f672e16e6 | ||
|
|
5786b7e1be |
@@ -1,5 +1,3 @@
|
|||||||
**/config.json
|
**/config.json
|
||||||
**/package-lock.json
|
|
||||||
**/node_modules
|
|
||||||
dist/*
|
dist/*
|
||||||
go.sum
|
go.sum
|
||||||
@@ -1,13 +1,14 @@
|
|||||||
.PHONY: build test clean wfa-js
|
.PHONY: build build-web build-wfa-js clean clean-wfa-js ensure-dist workflow-init
|
||||||
|
|
||||||
build: clean build-wfa-js
|
build: clean ensure-dist build-web build-wfa-js
|
||||||
@echo "======================== Building Binary ======================="
|
@echo "======================== Building Binary ======================="
|
||||||
mkdir -p dist
|
|
||||||
# resolve symbolic links in web by copying it into dist/web/
|
# resolve symbolic links in web by copying it into dist/web/
|
||||||
cp -rL web/ dist/web/
|
|
||||||
CGO_ENABLED=0 go build -tags release -ldflags="-s -w" -v -o dist/ .
|
CGO_ENABLED=0 go build -tags release -ldflags="-s -w" -v -o dist/ .
|
||||||
|
|
||||||
build-wfa-js:
|
build-web: ensure-dist
|
||||||
|
cp -rL web/ dist/web/
|
||||||
|
|
||||||
|
build-wfa-js: ensure-dist
|
||||||
$(MAKE) -C WFA-JS
|
$(MAKE) -C WFA-JS
|
||||||
cp -f WFA-JS/dist/* web/modules
|
cp -f WFA-JS/dist/* web/modules
|
||||||
|
|
||||||
@@ -18,3 +19,9 @@ clean: clean-wfa-js
|
|||||||
|
|
||||||
clean-wfa-js:
|
clean-wfa-js:
|
||||||
$(MAKE) clean -C WFA-JS
|
$(MAKE) clean -C WFA-JS
|
||||||
|
|
||||||
|
ensure-dist:
|
||||||
|
mkdir -p dist
|
||||||
|
|
||||||
|
workflow-init: ensure-dist build-web
|
||||||
|
cp -r dev_config/. .
|
||||||
@@ -13,7 +13,7 @@ ProxmoxAAS Dashboard provides users of a proxmox based compute on demand service
|
|||||||
|
|
||||||
## ProxmoxAAS System Installation Overview
|
## ProxmoxAAS System Installation Overview
|
||||||
|
|
||||||
The ProxmoxAAS project is large and is split into multiple components. There are four required components, the Dashboard, API, Fabric, and access-manager-api. The instalation order should start with the Dashboard and then proceed to the other backend components. This will require some foresight into the setup process.
|
The ProxmoxAAS project is large and is split into multiple components. There are four required components, the Dashboard, API, Fabric, and access-manager-api. The installation order should start with the Dashboard and then proceed to the other backend components. This will require some foresight into the setup process.
|
||||||
|
|
||||||
The supported setup is to use a reverse proxy to serve both the original Proxmox web interface and ProxmoxAAS components. It is possible other setups can work. Rather than provide specific steps to duplicate a certain setup, the steps included are intended as a guideline of steps required for proper function in most setups. Consequently, the examples provided are only to highlight key settings and do not represent complete working configurations. The instructions also assume you have your own domain name which will substitute `domain.net` in some of the configs.
|
The supported setup is to use a reverse proxy to serve both the original Proxmox web interface and ProxmoxAAS components. It is possible other setups can work. Rather than provide specific steps to duplicate a certain setup, the steps included are intended as a guideline of steps required for proper function in most setups. Consequently, the examples provided are only to highlight key settings and do not represent complete working configurations. The instructions also assume you have your own domain name which will substitute `domain.net` in some of the configs.
|
||||||
|
|
||||||
|
|||||||
+1
-1
Submodule WFA-JS updated: f8f8636cc3...f53f344fb3
+4
-2
@@ -6,7 +6,7 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"proxmoxaas-dashboard/app/common"
|
"proxmoxaas-dashboard/app/common"
|
||||||
"proxmoxaas-dashboard/app/routes"
|
"proxmoxaas-dashboard/app/routes"
|
||||||
"proxmoxaas-dashboard/dist/web" // go will complain here until the first build
|
"web" // go will complain here until the first build
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/tdewolff/minify/v2"
|
"github.com/tdewolff/minify/v2"
|
||||||
@@ -18,9 +18,11 @@ func Run() {
|
|||||||
|
|
||||||
common.Global = common.GetConfig(*configPath)
|
common.Global = common.GetConfig(*configPath)
|
||||||
|
|
||||||
// setup static resources
|
// setup gin
|
||||||
gin.SetMode(gin.ReleaseMode)
|
gin.SetMode(gin.ReleaseMode)
|
||||||
router := gin.Default()
|
router := gin.Default()
|
||||||
|
|
||||||
|
// setup static resources
|
||||||
m := common.InitMinify()
|
m := common.InitMinify()
|
||||||
ServeStatic(router, m)
|
ServeStatic(router, m)
|
||||||
html := common.MinifyStatic(m, web.Templates)
|
html := common.MinifyStatic(m, web.Templates)
|
||||||
|
|||||||
@@ -36,10 +36,6 @@ var MimeTypes = map[string]MimeType{
|
|||||||
Type: "image/svg+xml",
|
Type: "image/svg+xml",
|
||||||
Minifier: nil,
|
Minifier: nil,
|
||||||
},
|
},
|
||||||
"png": {
|
|
||||||
Type: "image/png",
|
|
||||||
Minifier: nil,
|
|
||||||
},
|
|
||||||
"js": {
|
"js": {
|
||||||
Type: "application/javascript",
|
Type: "application/javascript",
|
||||||
Minifier: nil,
|
Minifier: nil,
|
||||||
|
|||||||
@@ -40,10 +40,6 @@ var MimeTypes = map[string]MimeType{
|
|||||||
Type: "image/svg+xml",
|
Type: "image/svg+xml",
|
||||||
Minifier: svg.Minify,
|
Minifier: svg.Minify,
|
||||||
},
|
},
|
||||||
"png": {
|
|
||||||
Type: "image/png",
|
|
||||||
Minifier: nil,
|
|
||||||
},
|
|
||||||
"js": {
|
"js": {
|
||||||
Type: "application/javascript",
|
Type: "application/javascript",
|
||||||
Minifier: js.Minify,
|
Minifier: js.Minify,
|
||||||
|
|||||||
+18
-79
@@ -11,71 +11,11 @@ import (
|
|||||||
"github.com/go-viper/mapstructure/v2"
|
"github.com/go-viper/mapstructure/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Account struct {
|
|
||||||
User paas.User
|
|
||||||
Pools map[string]Pool
|
|
||||||
}
|
|
||||||
|
|
||||||
type Pool struct {
|
type Pool struct {
|
||||||
paas.Pool `mapstructure:",squash"`
|
paas.Pool `mapstructure:",squash"`
|
||||||
ResourceCharts map[string]map[string]any
|
ResourceCharts map[string]map[string]any
|
||||||
}
|
}
|
||||||
|
|
||||||
// numerical constraint
|
|
||||||
type Constraint struct {
|
|
||||||
Max int64
|
|
||||||
Used int64
|
|
||||||
Avail int64
|
|
||||||
}
|
|
||||||
|
|
||||||
// match constraint
|
|
||||||
type Match struct {
|
|
||||||
Name string
|
|
||||||
Match string
|
|
||||||
Max int64
|
|
||||||
Used int64
|
|
||||||
Avail int64
|
|
||||||
}
|
|
||||||
|
|
||||||
type NumericResource struct {
|
|
||||||
Type string
|
|
||||||
Name string
|
|
||||||
Multiplier int64
|
|
||||||
Base uint64
|
|
||||||
Compact bool
|
|
||||||
Unit string
|
|
||||||
Display bool
|
|
||||||
Global Constraint
|
|
||||||
Nodes map[string]Constraint
|
|
||||||
Total Constraint
|
|
||||||
Category string
|
|
||||||
}
|
|
||||||
|
|
||||||
type StorageResource struct {
|
|
||||||
Type string
|
|
||||||
Name string
|
|
||||||
Multiplier int64
|
|
||||||
Base uint64
|
|
||||||
Compact bool
|
|
||||||
Unit string
|
|
||||||
Display bool
|
|
||||||
Disks []string
|
|
||||||
Global Constraint
|
|
||||||
Nodes map[string]Constraint
|
|
||||||
Total Constraint
|
|
||||||
Category string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ListResource struct {
|
|
||||||
Type string
|
|
||||||
Whitelist bool
|
|
||||||
Display bool
|
|
||||||
Global []Match
|
|
||||||
Nodes map[string][]Match
|
|
||||||
Total []Match
|
|
||||||
Category string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ResourceChart struct {
|
type ResourceChart struct {
|
||||||
Type string
|
Type string
|
||||||
Display bool
|
Display bool
|
||||||
@@ -103,28 +43,23 @@ var Green = color.RGB{
|
|||||||
func HandleGETAccount(c *gin.Context) {
|
func HandleGETAccount(c *gin.Context) {
|
||||||
auth, err := common.GetAuthFromRequest(c)
|
auth, err := common.GetAuthFromRequest(c)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
account := Account{}
|
|
||||||
|
|
||||||
user, err := GetUserBasic(auth)
|
user, err := GetUserBasic(auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.HandleNonFatalError(c, err)
|
common.HandleNonFatalError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
account.User = user
|
|
||||||
|
|
||||||
pools, err := GetUserPools(auth)
|
pools, err := GetUserPools(auth)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
common.HandleNonFatalError(c, err)
|
common.HandleNonFatalError(c, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
account.Pools = pools
|
|
||||||
|
|
||||||
account.FormatPoolResourceCharts()
|
|
||||||
|
|
||||||
c.HTML(http.StatusOK, "html/account.html", gin.H{
|
c.HTML(http.StatusOK, "html/account.html", gin.H{
|
||||||
"global": common.Global,
|
"global": common.Global,
|
||||||
"page": "account",
|
"page": "account",
|
||||||
"account": account,
|
"user": user,
|
||||||
|
"pools": pools,
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
c.Redirect(http.StatusFound, "/login") // if user is not authed, redirect user to login page
|
c.Redirect(http.StatusFound, "/login") // if user is not authed, redirect user to login page
|
||||||
@@ -195,7 +130,7 @@ func GetUserPools(auth paas.Auth) (map[string]Pool, error) {
|
|||||||
// depending on type, decode the apool data into the corresponding resource type
|
// depending on type, decode the apool data into the corresponding resource type
|
||||||
switch t {
|
switch t {
|
||||||
case "numeric":
|
case "numeric":
|
||||||
n := NumericResource{}
|
n := paas.NumericResource{}
|
||||||
n.Type = t
|
n.Type = t
|
||||||
err_m := mapstructure.Decode(m, &n)
|
err_m := mapstructure.Decode(m, &n)
|
||||||
err_r := mapstructure.Decode(r, &n)
|
err_r := mapstructure.Decode(r, &n)
|
||||||
@@ -204,7 +139,7 @@ func GetUserPools(auth paas.Auth) (map[string]Pool, error) {
|
|||||||
}
|
}
|
||||||
pool.ResourceCharts[category][k] = n
|
pool.ResourceCharts[category][k] = n
|
||||||
case "storage":
|
case "storage":
|
||||||
n := StorageResource{}
|
n := paas.StorageResource{}
|
||||||
n.Type = t
|
n.Type = t
|
||||||
err_m := mapstructure.Decode(m, &n)
|
err_m := mapstructure.Decode(m, &n)
|
||||||
err_r := mapstructure.Decode(r, &n)
|
err_r := mapstructure.Decode(r, &n)
|
||||||
@@ -213,7 +148,7 @@ func GetUserPools(auth paas.Auth) (map[string]Pool, error) {
|
|||||||
}
|
}
|
||||||
pool.ResourceCharts[category][k] = n
|
pool.ResourceCharts[category][k] = n
|
||||||
case "list":
|
case "list":
|
||||||
n := ListResource{}
|
n := paas.ListResource{}
|
||||||
n.Type = t
|
n.Type = t
|
||||||
err_m := mapstructure.Decode(m, &n)
|
err_m := mapstructure.Decode(m, &n)
|
||||||
err_r := mapstructure.Decode(r, &n)
|
err_r := mapstructure.Decode(r, &n)
|
||||||
@@ -229,21 +164,25 @@ func GetUserPools(auth paas.Auth) (map[string]Pool, error) {
|
|||||||
pools[poolname] = pool
|
pools[poolname] = pool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
err = FormatPoolResourceCharts(&pools)
|
||||||
|
if err != nil {
|
||||||
|
return pools, err
|
||||||
|
}
|
||||||
|
|
||||||
return pools, nil
|
return pools, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (account *Account) FormatPoolResourceCharts() error {
|
func FormatPoolResourceCharts(pools *map[string]Pool) error {
|
||||||
pools := account.Pools
|
for poolname, pool := range *pools {
|
||||||
for poolname, pool := range pools {
|
|
||||||
// for each resource category
|
// for each resource category
|
||||||
for categoryname, category := range pool.ResourceCharts {
|
for categoryname, category := range pool.ResourceCharts {
|
||||||
// for each resource in each category
|
// for each resource in each category
|
||||||
for resourcename, resource := range category {
|
for resourcename, resource := range category {
|
||||||
// create a resource chart for resource depending on resource type
|
// create a resource chart for resource depending on resource type
|
||||||
switch t := resource.(type) {
|
switch t := resource.(type) {
|
||||||
case NumericResource:
|
case paas.NumericResource:
|
||||||
avail, prefix := paas.FormatNumber(paas.SafeUint64(t.Total.Avail*t.Multiplier), t.Base)
|
avail, prefix := paas.FormatNumber(paas.SafeUint64(t.Total.Avail*t.Multiplier), t.Base)
|
||||||
pools[poolname].ResourceCharts[categoryname][resourcename] = ResourceChart{
|
(*pools)[poolname].ResourceCharts[categoryname][resourcename] = ResourceChart{
|
||||||
Type: t.Type,
|
Type: t.Type,
|
||||||
Display: t.Display,
|
Display: t.Display,
|
||||||
Name: t.Name,
|
Name: t.Name,
|
||||||
@@ -254,9 +193,9 @@ func (account *Account) FormatPoolResourceCharts() error {
|
|||||||
Unit: t.Unit,
|
Unit: t.Unit,
|
||||||
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 paas.StorageResource:
|
||||||
avail, prefix := paas.FormatNumber(paas.SafeUint64(t.Total.Avail*t.Multiplier), t.Base)
|
avail, prefix := paas.FormatNumber(paas.SafeUint64(t.Total.Avail*t.Multiplier), t.Base)
|
||||||
pools[poolname].ResourceCharts[categoryname][resourcename] = ResourceChart{
|
(*pools)[poolname].ResourceCharts[categoryname][resourcename] = ResourceChart{
|
||||||
Type: t.Type,
|
Type: t.Type,
|
||||||
Display: t.Display,
|
Display: t.Display,
|
||||||
Name: t.Name,
|
Name: t.Name,
|
||||||
@@ -267,7 +206,7 @@ func (account *Account) FormatPoolResourceCharts() error {
|
|||||||
Unit: t.Unit,
|
Unit: t.Unit,
|
||||||
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 ListResource:
|
case paas.ListResource:
|
||||||
l := struct {
|
l := struct {
|
||||||
Type string
|
Type string
|
||||||
Display bool
|
Display bool
|
||||||
@@ -291,7 +230,7 @@ func (account *Account) FormatPoolResourceCharts() error {
|
|||||||
ColorHex: InterpolateColorHSV(Green, Red, float64(r.Used)/float64(r.Max)).ToHTML(),
|
ColorHex: InterpolateColorHSV(Green, Red, float64(r.Used)/float64(r.Max)).ToHTML(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
pools[poolname].ResourceCharts[categoryname][resourcename] = l
|
(*pools)[poolname].ResourceCharts[categoryname][resourcename] = l
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ import (
|
|||||||
"github.com/go-viper/mapstructure/v2"
|
"github.com/go-viper/mapstructure/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// imported types from fabric
|
|
||||||
|
|
||||||
type InstanceConfig struct {
|
type InstanceConfig struct {
|
||||||
paas.Instance `mapstructure:",squash"`
|
paas.Instance `mapstructure:",squash"`
|
||||||
// overrides
|
// overrides
|
||||||
@@ -28,8 +26,8 @@ type GlobalConfig struct {
|
|||||||
|
|
||||||
type PoolConfig struct {
|
type PoolConfig struct {
|
||||||
CPU struct {
|
CPU struct {
|
||||||
Global []paas.MatchLimit
|
Global []paas.Match
|
||||||
Nodes map[string][]paas.MatchLimit
|
Nodes map[string][]paas.Match
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -243,7 +241,7 @@ func GetCPUTypes(vm paas.InstancePath, pool string, auth paas.Auth) (common.Sele
|
|||||||
}
|
}
|
||||||
|
|
||||||
// use node specific rules if present, otherwise use global rules
|
// use node specific rules if present, otherwise use global rules
|
||||||
var userCPU []paas.MatchLimit
|
var userCPU []paas.Match
|
||||||
if _, ok := poolCPUConfig.CPU.Nodes[vm.NodeName]; ok {
|
if _, ok := poolCPUConfig.CPU.Nodes[vm.NodeName]; ok {
|
||||||
userCPU = poolCPUConfig.CPU.Nodes[vm.NodeName]
|
userCPU = poolCPUConfig.CPU.Nodes[vm.NodeName]
|
||||||
} else {
|
} else {
|
||||||
@@ -269,7 +267,7 @@ func GetCPUTypes(vm paas.InstancePath, pool string, auth paas.Auth) (common.Sele
|
|||||||
return cputypes, fmt.Errorf("request to %s resulted in %+v", path, res)
|
return cputypes, fmt.Errorf("request to %s resulted in %+v", path, res)
|
||||||
}
|
}
|
||||||
supported := struct {
|
supported := struct {
|
||||||
data []paas.MatchLimit
|
data []paas.Match
|
||||||
}{}
|
}{}
|
||||||
err = mapstructure.Decode(body, supported)
|
err = mapstructure.Decode(body, supported)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -278,7 +276,7 @@ func GetCPUTypes(vm paas.InstancePath, pool string, auth paas.Auth) (common.Sele
|
|||||||
|
|
||||||
// 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 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 {
|
for _, cpu := range supported.data {
|
||||||
contains := slices.ContainsFunc(userCPU, func(c paas.MatchLimit) bool {
|
contains := slices.ContainsFunc(userCPU, func(c paas.Match) bool {
|
||||||
return c.Name == cpu.Name
|
return c.Name == cpu.Name
|
||||||
})
|
})
|
||||||
if !contains {
|
if !contains {
|
||||||
|
|||||||
@@ -9,11 +9,6 @@ import (
|
|||||||
"github.com/go-viper/mapstructure/v2"
|
"github.com/go-viper/mapstructure/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
// used when requesting GET /access/domains
|
|
||||||
type GetRealmsBody struct {
|
|
||||||
Data []Realm `json:"data"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// stores each realm's data
|
// stores each realm's data
|
||||||
type Realm struct {
|
type Realm struct {
|
||||||
Default int `json:"default"`
|
Default int `json:"default"`
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
web/modules/*
|
||||||
|
WFA-JS/*
|
||||||
|
dist/*
|
||||||
@@ -0,0 +1,3 @@
|
|||||||
|
web/modules/*
|
||||||
|
WFA-JS/*
|
||||||
|
dist/*
|
||||||
@@ -1,8 +1,11 @@
|
|||||||
import { defineConfig } from "eslint/config";
|
import { defineConfig, globalIgnores } from "eslint/config";
|
||||||
import globals from "globals";
|
import globals from "globals";
|
||||||
import js from "@eslint/js";
|
import js from "@eslint/js";
|
||||||
|
|
||||||
export default defineConfig([js.configs.recommended,{
|
export default defineConfig([
|
||||||
|
js.configs.recommended,
|
||||||
|
globalIgnores(["dist/", "web/modules", "WFA-JS"]),
|
||||||
|
{
|
||||||
languageOptions: {
|
languageOptions: {
|
||||||
globals: {
|
globals: {
|
||||||
...globals.browser,
|
...globals.browser,
|
||||||
@@ -26,4 +29,5 @@ export default defineConfig([js.configs.recommended,{
|
|||||||
}],
|
}],
|
||||||
"prefer-const": ["error"]
|
"prefer-const": ["error"]
|
||||||
},
|
},
|
||||||
}]);
|
}
|
||||||
|
]);
|
||||||
@@ -1,23 +1,26 @@
|
|||||||
module proxmoxaas-dashboard
|
module proxmoxaas-dashboard
|
||||||
|
|
||||||
go 1.26.5
|
go 1.27.0
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/gerow/go-color v0.0.0-20140219113758-125d37f527f1
|
github.com/gerow/go-color v0.0.0-20140219113758-125d37f527f1
|
||||||
github.com/gin-gonic/gin v1.12.0
|
github.com/gin-gonic/gin v1.12.0
|
||||||
github.com/go-viper/mapstructure/v2 v2.5.0
|
github.com/go-viper/mapstructure/v2 v2.5.0
|
||||||
github.com/tdewolff/minify/v2 v2.24.13
|
github.com/tdewolff/minify/v2 v2.24.17
|
||||||
proxmoxaas-common-lib v0.0.0
|
proxmoxaas-common-lib v0.0.0
|
||||||
|
web v0.0.0
|
||||||
)
|
)
|
||||||
|
|
||||||
replace proxmoxaas-common-lib => ./proxmoxaas-common-lib
|
replace proxmoxaas-common-lib => ./proxmoxaas-common-lib
|
||||||
|
|
||||||
|
replace web => ./dist/web
|
||||||
|
|
||||||
require (
|
require (
|
||||||
github.com/bytedance/gopkg v0.1.4 // indirect
|
github.com/bytedance/gopkg v0.1.4 // indirect
|
||||||
github.com/bytedance/sonic v1.15.2 // indirect
|
github.com/bytedance/sonic v1.15.2 // indirect
|
||||||
github.com/bytedance/sonic/loader v0.5.1 // indirect
|
github.com/bytedance/sonic/loader v0.5.2 // indirect
|
||||||
github.com/cloudwego/base64x v0.1.7 // indirect
|
github.com/cloudwego/base64x v0.1.7 // indirect
|
||||||
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
|
github.com/gabriel-vasile/mimetype v1.4.15 // indirect
|
||||||
github.com/gin-contrib/sse v1.1.1 // indirect
|
github.com/gin-contrib/sse v1.1.1 // indirect
|
||||||
github.com/go-playground/locales v0.14.1 // indirect
|
github.com/go-playground/locales v0.14.1 // indirect
|
||||||
github.com/go-playground/universal-translator v0.18.1 // indirect
|
github.com/go-playground/universal-translator v0.18.1 // indirect
|
||||||
@@ -26,21 +29,21 @@ require (
|
|||||||
github.com/goccy/go-yaml v1.19.2 // indirect
|
github.com/goccy/go-yaml v1.19.2 // indirect
|
||||||
github.com/json-iterator/go v1.1.12 // indirect
|
github.com/json-iterator/go v1.1.12 // indirect
|
||||||
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
|
||||||
github.com/leodido/go-urn v1.4.0 // indirect
|
github.com/leodido/go-urn v1.5.0 // indirect
|
||||||
github.com/mattn/go-isatty v0.0.23 // indirect
|
github.com/mattn/go-isatty v0.0.24 // indirect
|
||||||
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
|
||||||
github.com/modern-go/reflect2 v1.0.2 // indirect
|
github.com/modern-go/reflect2 v1.0.2 // indirect
|
||||||
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
|
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
|
||||||
github.com/quic-go/qpack v0.6.0 // indirect
|
github.com/quic-go/qpack v0.6.0 // indirect
|
||||||
github.com/quic-go/quic-go v0.60.0 // indirect
|
github.com/quic-go/quic-go v0.61.0 // indirect
|
||||||
github.com/tdewolff/parse/v2 v2.8.13 // indirect
|
github.com/tdewolff/parse/v2 v2.8.16 // indirect
|
||||||
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
|
||||||
github.com/ugorji/go/codec v1.3.1 // indirect
|
github.com/ugorji/go/codec v1.3.2 // indirect
|
||||||
go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect
|
go.mongodb.org/mongo-driver/v2 v2.8.1 // indirect
|
||||||
golang.org/x/arch v0.29.0 // indirect
|
golang.org/x/arch v0.30.0 // indirect
|
||||||
golang.org/x/crypto v0.54.0 // indirect
|
golang.org/x/crypto v0.55.0 // indirect
|
||||||
golang.org/x/net v0.57.0 // indirect
|
golang.org/x/net v0.58.0 // indirect
|
||||||
golang.org/x/sys v0.47.0 // indirect
|
golang.org/x/sys v0.47.0 // indirect
|
||||||
golang.org/x/text v0.40.0 // indirect
|
golang.org/x/text v0.41.0 // indirect
|
||||||
google.golang.org/protobuf v1.36.11 // indirect
|
google.golang.org/protobuf v1.36.12 // indirect
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,18 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "proxmoxaas-dashboard",
|
|
||||||
"version": "1.0.0",
|
|
||||||
"description": "Front-end for ProxmoxAAS",
|
|
||||||
"type": "module",
|
|
||||||
"scripts": {
|
|
||||||
"lint": "html-validate --config dev_config/.htmlvalidate.json web/html/*; stylelint --config dev_config/.stylelintrc.json --formatter verbose --fix web/css/*.css; DEBUG=eslint:cli-engine eslint --config dev_config/eslint.config.mjs --fix web/scripts/"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@eslint/eslintrc": "^3.3.5",
|
|
||||||
"@eslint/js": "^10.0.1",
|
|
||||||
"eslint": "^10.2.1",
|
|
||||||
"globals": "^17.5.0",
|
|
||||||
"html-validate": "^9.4.0",
|
|
||||||
"stylelint": "^15.9.0",
|
|
||||||
"stylelint-config-standard": "^33.0.0"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
+1
-1
Submodule proxmoxaas-common-lib updated: 05519694e1...8284ffbc43
+14
-1
@@ -130,6 +130,19 @@ hr {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.row-reverse {
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
row-gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.column {
|
||||||
|
flex-direction: column;
|
||||||
|
row-gap: 10px;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
.column-reverse {
|
.column-reverse {
|
||||||
flex-direction: column-reverse;
|
flex-direction: column-reverse;
|
||||||
row-gap: 10px;
|
row-gap: 10px;
|
||||||
@@ -189,7 +202,7 @@ hr {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* fixes edge case in w3-hide-small when width between 600 and 601 */
|
/* fixes edge case in w3-hide-small when width between 600 and 601 */
|
||||||
@media screen and (width <=601px) {
|
@media screen and (width <= 601px) and (width >= 440px) {
|
||||||
.hide-large {display: none !important;}
|
.hide-large {display: none !important;}
|
||||||
.hide-medium {display:none !important}
|
.hide-medium {display:none !important}
|
||||||
.hide-small {display:none !important}
|
.hide-small {display:none !important}
|
||||||
|
|||||||
@@ -0,0 +1,3 @@
|
|||||||
|
module web
|
||||||
|
|
||||||
|
go 1.26.0
|
||||||
@@ -39,11 +39,11 @@
|
|||||||
<h2>Account</h2>
|
<h2>Account</h2>
|
||||||
<section class="w3-card w3-padding">
|
<section class="w3-card w3-padding">
|
||||||
<h3>Account Details</h3>
|
<h3>Account Details</h3>
|
||||||
<p id="username">Username: {{.account.User.Username.UserID}}@{{.account.User.Username.Realm}}</p>
|
<p id="username">Username: {{.user.Username.UserID}}@{{.user.Username.Realm}}</p>
|
||||||
<p id="email">Email: {{.account.User.Mail}}</p>
|
<p id="email">Email: {{.user.Mail}}</p>
|
||||||
<p>Password: <button class="w3-button" id="change-password" type="button" style="padding: 0em; height: 1.5em; line-height: 1.5em;">Change Password</button></p>
|
<p>Password: <button class="w3-button" id="change-password" type="button" style="padding: 0em; height: 1.5em; line-height: 1.5em;">Change Password</button></p>
|
||||||
</section>
|
</section>
|
||||||
{{range $poolname, $pool := .account.Pools}}
|
{{range $poolname, $pool := .pools}}
|
||||||
{{template "pool-resources" $pool}}
|
{{template "pool-resources" $pool}}
|
||||||
{{end}}
|
{{end}}
|
||||||
</main>
|
</main>
|
||||||
@@ -58,9 +58,9 @@
|
|||||||
<div id="body">
|
<div id="body">
|
||||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||||
<label for="new-password">New Password</label>
|
<label for="new-password">New Password</label>
|
||||||
<input class="w3-input w3-border" id="new-password" name="new-password" type="password" required>
|
<input class="w3-input w3-border" id="new-password" name="new-password" type="password" autocomplete="new-password" required>
|
||||||
<label for="confirm-password">Confirm Password</label>
|
<label for="confirm-password">Confirm Password</label>
|
||||||
<input class="w3-input w3-border" id="confirm-password" name="confirm-password" type="password" required>
|
<input class="w3-input w3-border" id="confirm-password" name="confirm-password" type="password" autocomplete="new-password" required>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="controls" class="w3-center w3-container">
|
<div id="controls" class="w3-center w3-container">
|
||||||
|
|||||||
+2
-2
@@ -114,9 +114,9 @@
|
|||||||
<label class="container-specific none" for="rootfs-size">ROOTFS Size (GiB)</label>
|
<label class="container-specific none" for="rootfs-size">ROOTFS Size (GiB)</label>
|
||||||
<input class="w3-input w3-border container-specific none" name="rootfs-size" id="rootfs-size" type="number" min="0" max="131072" required disabled>
|
<input class="w3-input w3-border container-specific none" name="rootfs-size" id="rootfs-size" type="number" min="0" max="131072" required disabled>
|
||||||
<label class="container-specific none" for="password">Password</label>
|
<label class="container-specific none" for="password">Password</label>
|
||||||
<input class="w3-input w3-border container-specific none" name="password" id="password" type="password" required disabled>
|
<input class="w3-input w3-border container-specific none" name="password" id="password" type="password" autocomplete="new-password" required disabled>
|
||||||
<label class="container-specific none" for="confirm-password">Confirm Password</label>
|
<label class="container-specific none" for="confirm-password">Confirm Password</label>
|
||||||
<input class="w3-input w3-border container-specific none" name="confirm-password" id="confirm-password" type="password" required disabled>
|
<input class="w3-input w3-border container-specific none" name="confirm-password" id="confirm-password" type="password" autocomplete="new-password" required disabled>
|
||||||
</form>
|
</form>
|
||||||
</div>
|
</div>
|
||||||
<div id="controls" class="w3-center w3-container">
|
<div id="controls" class="w3-center w3-container">
|
||||||
|
|||||||
+1
-1
@@ -560,7 +560,7 @@ export default function init (path) {
|
|||||||
const wasm = obj.instance;
|
const wasm = obj.instance;
|
||||||
global.wfa = wasm
|
global.wfa = wasm
|
||||||
go.run(wasm);
|
go.run(wasm);
|
||||||
res()
|
res(wasm)
|
||||||
}
|
}
|
||||||
if ('instantiateStreaming' in WebAssembly) {
|
if ('instantiateStreaming' in WebAssembly) {
|
||||||
WebAssembly.instantiateStreaming(fetch(path), go.importObject).then(function (obj) {
|
WebAssembly.instantiateStreaming(fetch(path), go.importObject).then(function (obj) {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { requestAPI, setAppearance } from "./utils.js";
|
import { requestAPI, setAppearance } from "./utils.js";
|
||||||
import { dialog } from "./dialog.js";
|
import { dialog, error } from "./dialog.js";
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", init);
|
window.addEventListener("DOMContentLoaded", init);
|
||||||
|
|
||||||
@@ -15,7 +15,7 @@ function handlePasswordChangeButton () {
|
|||||||
if (result === "confirm") {
|
if (result === "confirm") {
|
||||||
const result = await requestAPI("/access/password", "POST", { password: form.get("new-password") });
|
const result = await requestAPI("/access/password", "POST", { password: form.get("new-password") });
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to change password but got: ${result.error}`);
|
error(`Attempted to change password but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+7
-11
@@ -1,5 +1,5 @@
|
|||||||
import { requestAPI, getURIData, setAppearance, requestDash } from "./utils.js";
|
import { requestAPI, getURIData, setAppearance, requestDash } from "./utils.js";
|
||||||
import { alert, dialog } from "./dialog.js";
|
import { error, dialog } from "./dialog.js";
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", init);
|
window.addEventListener("DOMContentLoaded", init);
|
||||||
|
|
||||||
@@ -74,7 +74,7 @@ class BackupCard extends HTMLElement {
|
|||||||
};
|
};
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/backup/notes`, "POST", body);
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/backup/notes`, "POST", body);
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to edit backup but got: ${result.error}`);
|
error(`Attempted to edit backup but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshBackups();
|
refreshBackups();
|
||||||
}
|
}
|
||||||
@@ -90,7 +90,7 @@ class BackupCard extends HTMLElement {
|
|||||||
};
|
};
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/backup`, "DELETE", body);
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/backup`, "DELETE", body);
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to delete backup but got: ${result.error}`);
|
error(`Attempted to delete backup but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshBackups();
|
refreshBackups();
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@ class BackupCard extends HTMLElement {
|
|||||||
};
|
};
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/backup/restore`, "POST", body);
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/backup/restore`, "POST", body);
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to delete backup but got: ${result.error}`);
|
error(`Attempted to delete backup but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshBackups();
|
refreshBackups();
|
||||||
}
|
}
|
||||||
@@ -116,14 +116,10 @@ class BackupCard extends HTMLElement {
|
|||||||
|
|
||||||
customElements.define("backup-card", BackupCard);
|
customElements.define("backup-card", BackupCard);
|
||||||
|
|
||||||
async function getBackupsFragment () {
|
|
||||||
return await requestDash(`/backups/backups?node=${node}&type=${type}&vmid=${vmid}`, "GET");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function refreshBackups () {
|
async function refreshBackups () {
|
||||||
let backups = await getBackupsFragment();
|
let backups = await requestDash(`/backups/backups?node=${node}&type=${type}&vmid=${vmid}`, "GET");
|
||||||
if (backups.status !== 200) {
|
if (backups.status !== 200) {
|
||||||
alert("Error fetching backups.");
|
error("Error fetching backups.");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
backups = backups.data;
|
backups = backups.data;
|
||||||
@@ -141,7 +137,7 @@ async function handleBackupAddButton () {
|
|||||||
};
|
};
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/backup`, "POST", body);
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/backup`, "POST", body);
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to create backup but got: ${result.error}`);
|
error(`Attempted to create backup but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshBackups();
|
refreshBackups();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import { getSetting, requestAPI } from "./utils.js";
|
import { getSetting, requestAPI } from "./utils.js";
|
||||||
|
import { error } from "./dialog.js";
|
||||||
|
|
||||||
export async function setupClientSync (callback) {
|
export async function setupClientSync (callback) {
|
||||||
const scheme = getSetting("sync-scheme");
|
const scheme = getSetting("sync-scheme");
|
||||||
@@ -31,12 +32,12 @@ export async function setupClientSync (callback) {
|
|||||||
callback();
|
callback();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.error("clientsync: recieved unexpected message from server, closing socket.");
|
error("clientsync: recieved unexpected message from server, closing socket.");
|
||||||
socket.close();
|
socket.close();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
console.error(`clientsync: unsupported scheme ${scheme} selected.`);
|
error(`clientsync: unsupported scheme ${scheme} selected.`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+21
-21
@@ -1,5 +1,5 @@
|
|||||||
import { requestPVE, requestAPI, goToPage, getURIData, setAppearance, setIconSrc, requestDash } from "./utils.js";
|
import { requestPVE, requestAPI, goToPage, getURIData, setAppearance, setIconSrc, requestDash } from "./utils.js";
|
||||||
import { alert, dialog } from "./dialog.js";
|
import { error, dialog } from "./dialog.js";
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", init);
|
window.addEventListener("DOMContentLoaded", init);
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ class VolumeAction extends HTMLElement {
|
|||||||
this.setStatusLoading();
|
this.setStatusLoading();
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/detach`, "POST");
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/detach`, "POST");
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to detach ${disk} but got: ${result.error}`);
|
error(`Attempted to detach ${disk} but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshVolumes();
|
refreshVolumes();
|
||||||
refreshBoot();
|
refreshBoot();
|
||||||
@@ -80,7 +80,7 @@ class VolumeAction extends HTMLElement {
|
|||||||
const disk = `${prefix}${device}`;
|
const disk = `${prefix}${device}`;
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/attach`, "POST", body);
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/attach`, "POST", body);
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to attach ${this.dataset.volume} to ${disk} but got: ${result.error}`);
|
error(`Attempted to attach ${this.dataset.volume} to ${disk} but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshVolumes();
|
refreshVolumes();
|
||||||
refreshBoot();
|
refreshBoot();
|
||||||
@@ -98,7 +98,7 @@ class VolumeAction extends HTMLElement {
|
|||||||
};
|
};
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/resize`, "POST", body);
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/resize`, "POST", body);
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to resize ${disk} but got: ${result.error}`);
|
error(`Attempted to resize ${disk} but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshVolumes();
|
refreshVolumes();
|
||||||
refreshBoot();
|
refreshBoot();
|
||||||
@@ -117,7 +117,7 @@ class VolumeAction extends HTMLElement {
|
|||||||
};
|
};
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/move`, "POST", body);
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/move`, "POST", body);
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to move ${disk} to ${body.storage} but got: ${result.error}`);
|
error(`Attempted to move ${disk} to ${body.storage} but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshVolumes();
|
refreshVolumes();
|
||||||
refreshBoot();
|
refreshBoot();
|
||||||
@@ -141,7 +141,7 @@ class VolumeAction extends HTMLElement {
|
|||||||
this.setStatusLoading();
|
this.setStatusLoading();
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/delete`, "DELETE");
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/delete`, "DELETE");
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to delete ${disk} but got: ${result.error}`);
|
error(`Attempted to delete ${disk} but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshVolumes();
|
refreshVolumes();
|
||||||
refreshBoot();
|
refreshBoot();
|
||||||
@@ -162,7 +162,7 @@ async function initVolumes () {
|
|||||||
async function refreshVolumes () {
|
async function refreshVolumes () {
|
||||||
let volumes = await requestDash(`/config/volumes?node=${node}&type=${type}&vmid=${vmid}`, "GET");
|
let volumes = await requestDash(`/config/volumes?node=${node}&type=${type}&vmid=${vmid}`, "GET");
|
||||||
if (volumes.status !== 200) {
|
if (volumes.status !== 200) {
|
||||||
alert("Error fetching instance volumes.");
|
error("Error fetching instance volumes.");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
volumes = volumes.data;
|
volumes = volumes.data;
|
||||||
@@ -186,7 +186,7 @@ async function handleDiskAdd () {
|
|||||||
const disk = `${prefix}${id}`;
|
const disk = `${prefix}${id}`;
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/create`, "POST", body);
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/create`, "POST", body);
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to create ${disk} but got: ${result.error}`);
|
error(`Attempted to create ${disk} but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshVolumes();
|
refreshVolumes();
|
||||||
refreshBoot();
|
refreshBoot();
|
||||||
@@ -214,7 +214,7 @@ async function handleCDAdd () {
|
|||||||
const disk = `ide${form.get("device")}`;
|
const disk = `ide${form.get("device")}`;
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/create`, "POST", body);
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/disk/${disk}/create`, "POST", body);
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to mount ${body.iso} to ${disk} but got: result.error`);
|
error(`Attempted to mount ${body.iso} to ${disk} but got: result.error`);
|
||||||
}
|
}
|
||||||
refreshVolumes();
|
refreshVolumes();
|
||||||
refreshBoot();
|
refreshBoot();
|
||||||
@@ -263,7 +263,7 @@ class NetworkAction extends HTMLElement {
|
|||||||
const net = `${netID}`;
|
const net = `${netID}`;
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/net/${net}/modify`, "POST", body);
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/net/${net}/modify`, "POST", body);
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to change ${net} but got: ${result.error}`);
|
error(`Attempted to change ${net} but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshNetworks();
|
refreshNetworks();
|
||||||
refreshBoot();
|
refreshBoot();
|
||||||
@@ -281,7 +281,7 @@ class NetworkAction extends HTMLElement {
|
|||||||
const net = `${netID}`;
|
const net = `${netID}`;
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/net/${net}/delete`, "DELETE");
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/net/${net}/delete`, "DELETE");
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to delete ${net} but got: ${result.error}`);
|
error(`Attempted to delete ${net} but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshNetworks();
|
refreshNetworks();
|
||||||
refreshBoot();
|
refreshBoot();
|
||||||
@@ -299,7 +299,7 @@ async function initNetworks () {
|
|||||||
async function refreshNetworks () {
|
async function refreshNetworks () {
|
||||||
let nets = await requestDash(`/config/nets?node=${node}&type=${type}&vmid=${vmid}`, "GET");
|
let nets = await requestDash(`/config/nets?node=${node}&type=${type}&vmid=${vmid}`, "GET");
|
||||||
if (nets.status !== 200) {
|
if (nets.status !== 200) {
|
||||||
alert("Error fetching instance nets.");
|
error("Error fetching instance nets.");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
nets = nets.data;
|
nets = nets.data;
|
||||||
@@ -324,7 +324,7 @@ async function handleNetworkAdd () {
|
|||||||
const net = `net${id}`;
|
const net = `net${id}`;
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/net/${net}/create`, "POST", body);
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/net/${net}/create`, "POST", body);
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to create ${net} but got: ${result.error}`);
|
error(`Attempted to create ${net} but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshNetworks();
|
refreshNetworks();
|
||||||
refreshBoot();
|
refreshBoot();
|
||||||
@@ -367,7 +367,7 @@ class DeviceAction extends HTMLElement {
|
|||||||
const device = `${deviceID}`;
|
const device = `${deviceID}`;
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/pci/${device}/modify`, "POST", body);
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/pci/${device}/modify`, "POST", body);
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to add ${device} but got: ${result.error}`);
|
error(`Attempted to add ${device} but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshDevices();
|
refreshDevices();
|
||||||
}
|
}
|
||||||
@@ -389,7 +389,7 @@ class DeviceAction extends HTMLElement {
|
|||||||
const device = `${deviceID}`;
|
const device = `${deviceID}`;
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/pci/${device}/delete`, "DELETE");
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/pci/${device}/delete`, "DELETE");
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to delete ${device} but got: ${result.error}`);
|
error(`Attempted to delete ${device} but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshDevices();
|
refreshDevices();
|
||||||
}
|
}
|
||||||
@@ -408,7 +408,7 @@ async function initDevices () {
|
|||||||
async function refreshDevices () {
|
async function refreshDevices () {
|
||||||
let devices = await requestDash(`/config/devices?node=${node}&type=${type}&vmid=${vmid}`, "GET");
|
let devices = await requestDash(`/config/devices?node=${node}&type=${type}&vmid=${vmid}`, "GET");
|
||||||
if (devices.status !== 200) {
|
if (devices.status !== 200) {
|
||||||
alert("Error fetching instance devices.");
|
error("Error fetching instance devices.");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
devices = devices.data;
|
devices = devices.data;
|
||||||
@@ -431,7 +431,7 @@ async function handleDeviceAdd () {
|
|||||||
const deviceID = `hostpci${hostpci}`;
|
const deviceID = `hostpci${hostpci}`;
|
||||||
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/pci/${deviceID}/create`, "POST", body);
|
const result = await requestAPI(`/cluster/${node}/${type}/${vmid}/pci/${deviceID}/create`, "POST", body);
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to add ${body.device} but got: ${result.error}`);
|
error(`Attempted to add ${body.device} but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
refreshDevices();
|
refreshDevices();
|
||||||
}
|
}
|
||||||
@@ -447,12 +447,12 @@ async function handleDeviceAdd () {
|
|||||||
async function refreshBoot () {
|
async function refreshBoot () {
|
||||||
let boot = await requestDash(`/config/boot?node=${node}&type=${type}&vmid=${vmid}`, "GET");
|
let boot = await requestDash(`/config/boot?node=${node}&type=${type}&vmid=${vmid}`, "GET");
|
||||||
if (boot.status !== 200) {
|
if (boot.status !== 200) {
|
||||||
alert("Error fetching instance boot order.");
|
error("Error fetching instance boot order.");
|
||||||
}
|
}
|
||||||
else if (type === "qemu") {
|
else if (type === "qemu") {
|
||||||
boot = boot.data;
|
boot = boot.data;
|
||||||
const order = document.querySelector("#boot-order");
|
const container = document.querySelector("#boot-order");
|
||||||
order.setHTMLUnsafe(boot);
|
container.setHTMLUnsafe(boot);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -474,6 +474,6 @@ async function handleFormExit (event) {
|
|||||||
goToPage("index");
|
goToPage("index");
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
alert(`Attempted to set basic resources but got: ${result.error}`);
|
error(`Attempted to set basic resources but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
+6
-31
@@ -41,34 +41,6 @@ export function dialog (template, onclose = async (_result, _form) => { }) {
|
|||||||
return dialog;
|
return dialog;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function alert (message) {
|
|
||||||
const dialog = document.querySelector("#alert-dialog");
|
|
||||||
if (dialog == null) {
|
|
||||||
const dialog = document.createElement("dialog");
|
|
||||||
dialog.id = "alert-dialog";
|
|
||||||
dialog.innerHTML = `
|
|
||||||
<form method="dialog">
|
|
||||||
<p class="w3-large" id="prompt">Alert</p>
|
|
||||||
<p class="w3-center" style="margin-bottom: 0;">${message}</p>
|
|
||||||
<div class="w3-center">
|
|
||||||
<button class="w3-button w3-margin" id="submit">OK</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
`;
|
|
||||||
dialog.className = "w3-container w3-card w3-border-0";
|
|
||||||
document.body.append(dialog);
|
|
||||||
dialog.showModal();
|
|
||||||
dialog.addEventListener("close", () => {
|
|
||||||
dialog.parentElement.removeChild(dialog);
|
|
||||||
});
|
|
||||||
return dialog;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
console.error("Attempted to create a new alert while one already exists!");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class ErrorDialog extends HTMLElement {
|
class ErrorDialog extends HTMLElement {
|
||||||
shadowRoot = null;
|
shadowRoot = null;
|
||||||
dialog = null;
|
dialog = null;
|
||||||
@@ -87,15 +59,18 @@ class ErrorDialog extends HTMLElement {
|
|||||||
max-height: 20lh;
|
max-height: 20lh;
|
||||||
min-height: 20lh;
|
min-height: 20lh;
|
||||||
overflow-y: scroll;
|
overflow-y: scroll;
|
||||||
|
padding-left: 12px;
|
||||||
|
padding-right: 12px;
|
||||||
}
|
}
|
||||||
#errors * {
|
#errors * {
|
||||||
margin: 0;
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
<dialog class="w3-container w3-card w3-border-0">
|
<dialog class="w3-container w3-card w3-border-0">
|
||||||
<form method="dialog">
|
<form method="dialog">
|
||||||
<p class="w3-large" id="prompt">Error</p>
|
<p class="w3-large" id="prompt">Error</p>
|
||||||
<div id="errors" class="flex column-reverse"></div>
|
<div id="errors" class="flex column"></div>
|
||||||
<div class="w3-center" id="controls">
|
<div class="w3-center" id="controls">
|
||||||
<button class="w3-button w3-margin" type="submit" value="ok">OK</button>
|
<button class="w3-button w3-margin" type="submit" value="ok">OK</button>
|
||||||
<button class="w3-button w3-margin" type="submit" value="copy">Copy</button>
|
<button class="w3-button w3-margin" type="submit" value="copy">Copy</button>
|
||||||
@@ -121,7 +96,7 @@ class ErrorDialog extends HTMLElement {
|
|||||||
}
|
}
|
||||||
navigator.clipboard.writeText(errors);
|
navigator.clipboard.writeText(errors);
|
||||||
}
|
}
|
||||||
this.parentElement.removeChild(this);
|
this.dialog.close();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -141,7 +116,7 @@ customElements.define("error-dialog", ErrorDialog);
|
|||||||
|
|
||||||
export function error (message) {
|
export function error (message) {
|
||||||
let dialog = document.querySelector("error-dialog");
|
let dialog = document.querySelector("error-dialog");
|
||||||
if (dialog == null) {
|
if (dialog === null) {
|
||||||
dialog = document.createElement("error-dialog");
|
dialog = document.createElement("error-dialog");
|
||||||
document.body.append(dialog);
|
document.body.append(dialog);
|
||||||
dialog.appendError(message);
|
dialog.appendError(message);
|
||||||
|
|||||||
+10
-12
@@ -1,14 +1,16 @@
|
|||||||
import { requestPVE, requestAPI, setAppearance, getSetting, requestDash, setIconSrc, setIconAlt } from "./utils.js";
|
import { requestPVE, requestAPI, setAppearance, getSetting, requestDash, setIconSrc, setIconAlt } from "./utils.js";
|
||||||
import { alert, dialog, error } from "./dialog.js";
|
import { dialog, error } from "./dialog.js";
|
||||||
import { setupClientSync } from "./clientsync.js";
|
import { setupClientSync } from "./clientsync.js";
|
||||||
import wfaInit from "../modules/wfa.js";
|
import wfaInit from "../modules/wfa.js";
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", init);
|
window.addEventListener("DOMContentLoaded", init);
|
||||||
|
|
||||||
|
var wfa;
|
||||||
|
|
||||||
async function init () {
|
async function init () {
|
||||||
setAppearance();
|
setAppearance();
|
||||||
|
|
||||||
wfaInit("modules/wfa.wasm");
|
wfa = await wfaInit("modules/wfa.wasm");
|
||||||
initInstances();
|
initInstances();
|
||||||
|
|
||||||
document.querySelector("#instance-add").addEventListener("click", handleInstanceAddButton);
|
document.querySelector("#instance-add").addEventListener("click", handleInstanceAddButton);
|
||||||
@@ -175,7 +177,7 @@ class InstanceCard extends HTMLElement {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else if (taskStatus.data.status === "stopped") { // task stopped but was not successful
|
else if (taskStatus.data.status === "stopped") { // task stopped but was not successful
|
||||||
alert(`Attempted to ${targetAction} ${this.vmid} but got: ${taskStatus.data.exitstatus}`);
|
error(`Attempted to ${targetAction} ${this.vmid} but got: ${taskStatus.data.exitstatus}`);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
else { // task has not stopped
|
else { // task has not stopped
|
||||||
@@ -203,7 +205,7 @@ class InstanceCard extends HTMLElement {
|
|||||||
|
|
||||||
const result = await requestAPI(`/cluster/${this.node.name}/${this.type}/${this.vmid}/delete`, "DELETE");
|
const result = await requestAPI(`/cluster/${this.node.name}/${this.type}/${this.vmid}/delete`, "DELETE");
|
||||||
if (result.status !== 200) {
|
if (result.status !== 200) {
|
||||||
alert(`Attempted to delete ${this.vmid} but got: ${result.error}`);
|
error(`Attempted to delete ${this.vmid} but got: ${result.error}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.actionLock = false;
|
this.actionLock = false;
|
||||||
@@ -216,12 +218,8 @@ class InstanceCard extends HTMLElement {
|
|||||||
|
|
||||||
customElements.define("instance-card", InstanceCard);
|
customElements.define("instance-card", InstanceCard);
|
||||||
|
|
||||||
async function getInstancesFragment () {
|
|
||||||
return await requestDash("/index/instances", "GET");
|
|
||||||
}
|
|
||||||
|
|
||||||
async function refreshInstances () {
|
async function refreshInstances () {
|
||||||
let instances = await getInstancesFragment();
|
let instances = await requestDash("/index/instances", "GET");
|
||||||
if (instances.status !== 200) {
|
if (instances.status !== 200) {
|
||||||
error(`Error fetching instances: ${instances.status} ${instances.error !== undefined ? instances.error : ""}`);
|
error(`Error fetching instances: ${instances.status} ${instances.error !== undefined ? instances.error : ""}`);
|
||||||
}
|
}
|
||||||
@@ -276,8 +274,8 @@ function sortInstances () {
|
|||||||
};
|
};
|
||||||
criteria = (item, query) => {
|
criteria = (item, query) => {
|
||||||
// lower is better
|
// lower is better
|
||||||
const { score, CIGAR } = global.wfa.wfAlign(query, item, penalties, true);
|
const { score, CIGAR } = wfa.wfAlign(query, item, penalties, true);
|
||||||
const alignment = global.wfa.DecodeCIGAR(CIGAR);
|
const alignment = wfa.DecodeCIGAR(CIGAR);
|
||||||
return { score: score / item.length, alignment };
|
return { score: score / item.length, alignment };
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -337,7 +335,7 @@ async function handleInstanceAddButton () {
|
|||||||
refreshInstances();
|
refreshInstances();
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
alert(`Attempted to create new instance ${vmid} but got: ${result.error}`);
|
error(`Attempted to create new instance ${vmid} but got: ${result.error}`);
|
||||||
refreshInstances();
|
refreshInstances();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,11 +1,12 @@
|
|||||||
import { goToPage, setAppearance, requestAPI } from "./utils.js";
|
import { goToPage, setAppearance, requestAPI } from "./utils.js";
|
||||||
import { alert } from "./dialog.js";
|
import { error } from "./dialog.js";
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", init);
|
window.addEventListener("DOMContentLoaded", init);
|
||||||
|
|
||||||
async function init () {
|
async function init () {
|
||||||
await deleteAllCookies();
|
await deleteAllCookies();
|
||||||
setAppearance();
|
setAppearance();
|
||||||
|
|
||||||
const formSubmitButton = document.querySelector("#submit");
|
const formSubmitButton = document.querySelector("#submit");
|
||||||
formSubmitButton.addEventListener("click", async (e) => {
|
formSubmitButton.addEventListener("click", async (e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
@@ -19,18 +20,16 @@ async function init () {
|
|||||||
goToPage("index");
|
goToPage("index");
|
||||||
}
|
}
|
||||||
else if (ticket.status === 401) {
|
else if (ticket.status === 401) {
|
||||||
alert("Authenticaton failed.");
|
error("Authenticaton failed.");
|
||||||
formSubmitButton.innerText = "LOGIN";
|
formSubmitButton.innerText = "LOGIN";
|
||||||
}
|
}
|
||||||
else if (ticket.status === 408) {
|
else if (ticket.status === 408) {
|
||||||
alert("Network error.");
|
error("Network error.");
|
||||||
formSubmitButton.innerText = "LOGIN";
|
formSubmitButton.innerText = "LOGIN";
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
alert("An error occured.");
|
error(`An error occured: ${JSON.stringify(ticket)}`);
|
||||||
console.error(ticket);
|
|
||||||
formSubmitButton.innerText = "LOGIN";
|
formSubmitButton.innerText = "LOGIN";
|
||||||
console.error(ticket.error);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user