Compare commits
13
Commits
973afb4073
...
v2.0.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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/. .
|
||||||
+1
-1
Submodule WFA-JS updated: f8f8636cc3...f53f344fb3
+1
-1
@@ -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"
|
||||||
|
|||||||
@@ -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,
|
||||||
|
|||||||
+20
-81
@@ -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,29 +1,33 @@
|
|||||||
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([
|
||||||
languageOptions: {
|
js.configs.recommended,
|
||||||
globals: {
|
globalIgnores(["dist/", "web/modules", "WFA-JS"]),
|
||||||
...globals.browser,
|
{
|
||||||
|
languageOptions: {
|
||||||
|
globals: {
|
||||||
|
...globals.browser,
|
||||||
|
},
|
||||||
|
ecmaVersion: "latest",
|
||||||
|
sourceType: "module",
|
||||||
},
|
},
|
||||||
ecmaVersion: "latest",
|
rules: {
|
||||||
sourceType: "module",
|
"no-tabs": ["error", {
|
||||||
},
|
allowIndentationTabs: true,
|
||||||
rules: {
|
}],
|
||||||
"no-tabs": ["error", {
|
indent: ["error", "tab"],
|
||||||
allowIndentationTabs: true,
|
"linebreak-style": ["error", "unix"],
|
||||||
}],
|
quotes: ["error", "double"],
|
||||||
indent: ["error", "tab"],
|
semi: ["error", "always"],
|
||||||
"linebreak-style": ["error", "unix"],
|
"brace-style": ["error", "stroustrup", { allowSingleLine: false }],
|
||||||
quotes: ["error", "double"],
|
"no-unused-vars": ["warn", {
|
||||||
semi: ["error", "always"],
|
"argsIgnorePattern": "^_",
|
||||||
"brace-style": ["error", "stroustrup", { allowSingleLine: false }],
|
"varsIgnorePattern": "^_",
|
||||||
"no-unused-vars": ["warn", {
|
"caughtErrorsIgnorePattern": "^_"
|
||||||
"argsIgnorePattern": "^_",
|
}],
|
||||||
"varsIgnorePattern": "^_",
|
"prefer-const": ["error"]
|
||||||
"caughtErrorsIgnorePattern": "^_"
|
},
|
||||||
}],
|
}
|
||||||
"prefer-const": ["error"]
|
]);
|
||||||
},
|
|
||||||
}]);
|
|
||||||
@@ -8,9 +8,11 @@ require (
|
|||||||
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.13
|
||||||
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
|
||||||
|
|||||||
@@ -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...f4deeb1ab6
@@ -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;
|
||||||
|
|||||||
@@ -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