1 Commits
Author SHA1 Message Date
alu 27e7e2771f add lint workflow
ProxmoxAAS-Dashboard Code Style Check / lint-check (push) Failing after 10s
2026-07-29 02:53:55 +00:00
112 changed files with 1076 additions and 1026 deletions

No files matched your search

+32
View File
@@ -0,0 +1,32 @@
name: ProxmoxAAS-Dashboard Code Style Check
run-name: Code style check on ${{gitea.repository}} ${{gitea.ref}} ${{gitea.sha}}
on: [push]
jobs:
lint-check:
runs-on: ubuntu-latest
steps:
- name: Initialize ${{runner.os}} ${{runner.arch}}
uses: actions/setup-node@v4
- name: Install linters
run: |
npm install -g \
@eslint/eslintrc \
@eslint/js \
eslint \
globals \
html-validate \
stylelint \
stylelint-config-standard
- name: Verify linters
run: |
eslint --version
stylelint --version
html-validate --version
- name: Check out ${{gitea.repository}} ${{gitea.ref}} ${{gitea.sha}}
uses: actions/checkout@v4
- name: Run Linters
run: |
html-validate --config dev_config/.htmlvalidate.json web/html/*; \
stylelint --config dev_config/.stylelintrc.json --formatter verbose web/css/*.css; \
eslint --config dev_config/eslint.config.mjs web/scripts/
+2
View File
@@ -1,3 +1,5 @@
**/config.json **/config.json
**/package-lock.json
**/node_modules
dist/* dist/*
go.sum go.sum
+10 -16
View File
@@ -1,26 +1,20 @@
.PHONY: test build build-wfa clean clean-wfa clean-web .PHONY: build test clean wfa-js
build: clean ensure-dist build-wfa build: clean 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/
GODEBUG=embedfollowsymlinks=1 CGO_ENABLED=0 go build -tags release -ldflags="-s -w" -v -o dist/ . cp -rL web/ dist/web/
CGO_ENABLED=0 go build -tags release -ldflags="-s -w" -v -o dist/ .
test: build-wfa-js:
GODEBUG=embedfollowsymlinks=1 CGO_ENABLED=0 go run . $(MAKE) -C WFA-JS
cp -f WFA-JS/dist/* web/modules
clean: clean-wfa clean-web clean: clean-wfa-js
@echo "======================== Cleaning Project ======================" @echo "======================== Cleaning Project ======================"
go clean go clean
rm -rf dist/* rm -rf dist/*
ensure-dist: clean-wfa-js:
mkdir -p dist
clean-web:
build-wfa:
$(MAKE) -C WFA-JS
cp -f WFA-JS/dist/* app/web/modules
clean-wfa:
$(MAKE) clean -C WFA-JS $(MAKE) clean -C WFA-JS
+1 -1
View File
@@ -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 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 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 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: b0aacd5f9c...f8f8636cc3.
+53 -37
View File
@@ -4,55 +4,71 @@ import (
"flag" "flag"
"fmt" "fmt"
"log" "log"
"net/http"
"proxmoxaas-dashboard/app/common" "proxmoxaas-dashboard/app/common"
"proxmoxaas-dashboard/app/routes" "proxmoxaas-dashboard/app/routes"
"proxmoxaas-dashboard/app/web" "proxmoxaas-dashboard/dist/web" // go will complain here until the first build
paas "proxmoxaas-dashboard/proxmoxaas-common-lib"
"github.com/gin-gonic/gin"
"github.com/tdewolff/minify/v2"
) )
func Run() { func Run() {
configPath := flag.String("config", "config.json", "path to config.json file") configPath := flag.String("config", "config.json", "path to config.json file")
flag.Parse() flag.Parse()
common.Global = common.GetConfig(*configPath) common.Global = common.GetConfig(*configPath)
log.Printf("[INFO] Read in config from %s\n", *configPath)
// setup http router
router := http.NewServeMux()
// setup static resources
gin.SetMode(gin.ReleaseMode)
router := gin.Default()
m := common.InitMinify() m := common.InitMinify()
ServeStatic(router, m)
// setup static resources under web (css, images, modules, scripts)
static := common.MinifyStatic(m, web.Static)
router.HandleFunc("GET /websrc/{path...}", func(w http.ResponseWriter, r *http.Request) {
path := r.PathValue("path")
data := static[path]
w.Header().Set("Content-Encoding", data.MimeType.Encoding)
w.Header().Set("Content-Type", data.MimeType.Type)
w.Write([]byte(data.Data))
})
// setup template resources
html := common.MinifyStatic(m, web.Templates) html := common.MinifyStatic(m, web.Templates)
common.TMPL = common.LoadHTML(html) common.TMPL = common.LoadHTMLToGin(router, html)
// dynamic routes for pages and page fragments // dynamic routes for pages and page fragments
router.HandleFunc("GET /", routes.HandleGETIndex) router.GET("/", routes.HandleGETIndex)
router.HandleFunc("GET /index", routes.HandleGETIndex) router.GET("/index", routes.HandleGETIndex)
router.HandleFunc("GET /index/instances", routes.HandleGETInstancesFragment) router.GET("/index/instances", routes.HandleGETInstancesFragment)
router.HandleFunc("GET /account", routes.HandleGETAccount) router.GET("/account", routes.HandleGETAccount)
router.HandleFunc("GET /config", routes.HandleGETConfig) router.GET("/config", routes.HandleGETConfig)
router.HandleFunc("GET /config/volumes", routes.HandleGETConfigVolumesFragment) router.GET("/config/volumes", routes.HandleGETConfigVolumesFragment)
router.HandleFunc("GET /config/nets", routes.HandleGETConfigNetsFragment) router.GET("/config/nets", routes.HandleGETConfigNetsFragment)
router.HandleFunc("GET /config/devices", routes.HandleGETConfigDevicesFragment) router.GET("/config/devices", routes.HandleGETConfigDevicesFragment)
router.HandleFunc("GET /config/boot", routes.HandleGETConfigBootFragment) router.GET("/config/boot", routes.HandleGETConfigBootFragment)
router.HandleFunc("GET /backups", routes.HandleGETBackups) router.GET("/backups", routes.HandleGETBackups)
router.HandleFunc("GET /backups/backups", routes.HandleGETBackupsFragment) router.GET("/backups/backups", routes.HandleGETBackupsFragment)
router.HandleFunc("GET /login", routes.HandleGETLogin) router.GET("/login", routes.HandleGETLogin)
router.HandleFunc("GET /settings", routes.HandleGETSettings) router.GET("/settings", routes.HandleGETSettings)
server := paas.LoggingMiddleware(router) // run on all interfaces with port
log.Fatal("[ERR ] starting gin router: ", router.Run(fmt.Sprintf("0.0.0.0:%d", common.Global.Port)))
log.Printf("[INFO] starting server listening on 0.0.0.0:%d", common.Global.Port) }
log.Fatalf("[ERR ] starting gin router: %s", http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", common.Global.Port), server))
// setup static resources under web (css, images, modules, scripts)
func ServeStatic(router *gin.Engine, m *minify.M) {
css := common.MinifyStatic(m, web.CSS_fs)
router.GET("/css/*css", func(c *gin.Context) {
path, _ := c.Params.Get("css")
data := css[fmt.Sprintf("css%s", path)]
c.Data(200, data.MimeType.Type, []byte(data.Data))
})
images := common.MinifyStatic(m, web.Images_fs)
router.GET("/images/*image", func(c *gin.Context) {
path, _ := c.Params.Get("image")
data := images[fmt.Sprintf("images%s", path)]
c.Data(200, data.MimeType.Type, []byte(data.Data))
})
modules := common.MinifyStatic(m, web.Modules_fs)
router.GET("/modules/*module", func(c *gin.Context) {
path, _ := c.Params.Get("module")
data := modules[fmt.Sprintf("modules%s", path)]
c.Data(200, data.MimeType.Type, []byte(data.Data))
})
scripts := common.MinifyStatic(m, web.Scripts_fs)
router.GET("/scripts/*script", func(c *gin.Context) {
path, _ := c.Params.Get("script")
data := scripts[fmt.Sprintf("scripts%s", path)]
c.Data(200, data.MimeType.Type, []byte(data.Data))
})
} }
-6
View File
@@ -11,7 +11,6 @@ import (
// defines mime type and associated minifier // defines mime type and associated minifier
type MimeType struct { type MimeType struct {
Type string Type string
Encoding string
Minifier func(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error Minifier func(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error
} }
@@ -45,11 +44,6 @@ var MimeTypes = map[string]MimeType{
Type: "application/wasm", Type: "application/wasm",
Minifier: nil, Minifier: nil,
}, },
"gz": {
Type: "application/wasm",
Encoding: "gzip",
Minifier: nil,
},
"*": { "*": {
Type: "text/plain", Type: "text/plain",
Minifier: nil, Minifier: nil,
-6
View File
@@ -15,7 +15,6 @@ import (
// defines mime type and associated minifier // defines mime type and associated minifier
type MimeType struct { type MimeType struct {
Type string Type string
Encoding string
Minifier func(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error Minifier func(m *minify.M, w io.Writer, r io.Reader, params map[string]string) error
} }
@@ -49,11 +48,6 @@ var MimeTypes = map[string]MimeType{
Type: "application/wasm", Type: "application/wasm",
Minifier: nil, Minifier: nil,
}, },
"gz": {
Type: "application/wasm",
Encoding: "gzip",
Minifier: nil,
},
"*": { "*": {
Type: "text/plain", Type: "text/plain",
Minifier: nil, Minifier: nil,
+25 -42
View File
@@ -16,9 +16,10 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/gin-gonic/gin"
"github.com/tdewolff/minify/v2" "github.com/tdewolff/minify/v2"
paas "proxmoxaas-dashboard/proxmoxaas-common-lib" paas "proxmoxaas-common-lib"
) )
// get config file from configPath // get config file from configPath
@@ -102,8 +103,9 @@ func MinifyStatic(m *minify.M, files embed.FS) map[string]StaticFile {
} }
} }
func LoadHTML(html map[string]StaticFile) *template.Template { func LoadHTMLToGin(engine *gin.Engine, html map[string]StaticFile) *template.Template {
funcMap := template.FuncMap{ root := template.New("")
engine.FuncMap = template.FuncMap{
"MapKeys": func(x any, sep string) string { "MapKeys": func(x any, sep string) string {
v := reflect.ValueOf(x) v := reflect.ValueOf(x)
keys := v.MapKeys() keys := v.MapKeys()
@@ -131,11 +133,9 @@ func LoadHTML(html map[string]StaticFile) *template.Template {
return dict, nil return dict, nil
}, },
} }
root := template.New("").Funcs(funcMap) tmpl := template.Must(root, LoadAndAddToRoot(engine.FuncMap, root, html))
if err := LoadAndAddToRoot(funcMap, root, html); err != nil { engine.SetHTMLTemplate(root)
log.Fatal(err) return tmpl
}
return root
} }
func LoadAndAddToRoot(FuncMap template.FuncMap, root *template.Template, html map[string]StaticFile) error { func LoadAndAddToRoot(FuncMap template.FuncMap, root *template.Template, html map[string]StaticFile) error {
@@ -170,9 +170,9 @@ func TemplateMinifier(m *minify.M, w io.Writer, r io.Reader, _ map[string]string
return nil return nil
} }
func HandleNonFatalError(w http.ResponseWriter, err error) { func HandleNonFatalError(c *gin.Context, err error) {
log.Printf("[WARN] encountered a non-fatal error: %s", err.Error()) log.Printf("[WARN] encountered a non-fatal error: %s", err.Error())
w.WriteHeader(http.StatusInternalServerError) c.Status(http.StatusInternalServerError)
} }
func RequestGetAPI(path string, auth *paas.Auth, body any) (*http.Response, int, error) { func RequestGetAPI(path string, auth *paas.Auth, body any) (*http.Response, int, error) {
@@ -221,41 +221,25 @@ func RequestGetAPI(path string, auth *paas.Auth, body any) (*http.Response, int,
return response, response.StatusCode, nil return response, response.StatusCode, nil
} }
func GetAuthFromRequest(r *http.Request) (paas.Auth, error) { func GetAuthFromRequest(c *gin.Context) (paas.Auth, error) {
_, errAuth := r.Cookie("auth") _, errAuth := c.Cookie("auth")
username, errUsername := r.Cookie("username") username, errUsername := c.Cookie("username")
token, errToken := r.Cookie("PVEAuthCookie") token, errToken := c.Cookie("PVEAuthCookie")
csrf, errCSRF := r.Cookie("CSRFPreventionToken") csrf, errCSRF := c.Cookie("CSRFPreventionToken")
access, errAccess := r.Cookie("PAASAccessManagerTicket") access, errAccess := c.Cookie("PAASAccessManagerTicket")
if errUsername != nil || errAuth != nil || errToken != nil || errCSRF != nil || errAccess != nil {
if errUsername != nil || errAuth != nil || errToken != nil || return paas.Auth{}, fmt.Errorf("error occured getting user cookies: (auth: %s, token: %s, csrf: %s)", errAuth, errToken, errCSRF)
errCSRF != nil || errAccess != nil { } else {
return paas.Auth{}, fmt.Errorf( return paas.Auth{Username: username, Token: token, CSRF: csrf, AccessManagerTicket: access}, nil
"error occurred getting user cookies: (auth: %v, token: %v, csrf: %v)",
errAuth, errToken, errCSRF,
)
} }
return paas.Auth{
Username: username.Value,
Token: token.Value,
CSRF: csrf.Value,
AccessManagerTicket: access.Value,
}, nil
} }
func GetInstancePathFromRequest(r *http.Request) (paas.InstancePath, error) { func GetInstancePathFromRequest(c *gin.Context) (paas.InstancePath, error) {
query := r.URL.Query() req_node := c.Query("node")
req_type := c.Query("type")
req_node := query.Get("node") req_vmid := c.Query("vmid")
req_type := query.Get("type")
req_vmid := query.Get("vmid")
if req_node == "" || req_type == "" || req_vmid == "" { if req_node == "" || req_type == "" || req_vmid == "" {
return paas.InstancePath{}, fmt.Errorf( return paas.InstancePath{}, fmt.Errorf("request missing required values: (node: %s, type: %s, vmid: %s)", req_node, req_type, req_vmid)
"request missing required values: (node: %s, type: %s, vmid: %s)",
req_node, req_type, req_vmid,
)
} }
vmid_int, err := strconv.ParseUint(req_vmid, 10, 64) vmid_int, err := strconv.ParseUint(req_vmid, 10, 64)
@@ -268,7 +252,6 @@ func GetInstancePathFromRequest(r *http.Request) (paas.InstancePath, error) {
InstanceType: paas.InstanceType(req_type), InstanceType: paas.InstanceType(req_type),
InstanceID: paas.InstanceID(vmid_int), InstanceID: paas.InstanceID(vmid_int),
} }
return vm_path, nil return vm_path, nil
} }
+8 -8
View File
@@ -3,10 +3,11 @@ package routes
import ( import (
"fmt" "fmt"
"net/http" "net/http"
paas "proxmoxaas-common-lib"
"proxmoxaas-dashboard/app/common" "proxmoxaas-dashboard/app/common"
paas "proxmoxaas-dashboard/proxmoxaas-common-lib"
"github.com/gerow/go-color" "github.com/gerow/go-color"
"github.com/gin-gonic/gin"
"github.com/go-viper/mapstructure/v2" "github.com/go-viper/mapstructure/v2"
) )
@@ -39,30 +40,29 @@ var Green = color.RGB{
B: 0, B: 0,
} }
func HandleGETAccount(w http.ResponseWriter, r *http.Request) { func HandleGETAccount(c *gin.Context) {
auth, err := common.GetAuthFromRequest(r) auth, err := common.GetAuthFromRequest(c)
if err == nil { if err == nil {
user, err := GetUserBasic(auth) user, err := GetUserBasic(auth)
if err != nil { if err != nil {
common.HandleNonFatalError(w, err) common.HandleNonFatalError(c, err)
return return
} }
pools, err := GetUserPools(auth) pools, err := GetUserPools(auth)
if err != nil { if err != nil {
common.HandleNonFatalError(w, err) common.HandleNonFatalError(c, err)
return return
} }
paas.HTTPExecuteHTMLTemplate(w, common.TMPL, "html/account.html", map[string]any{ c.HTML(http.StatusOK, "html/account.html", gin.H{
"global": common.Global, "global": common.Global,
"page": "account", "page": "account",
"user": user, "user": user,
"pools": pools, "pools": pools,
}) })
} else { } else {
http.Redirect(w, r, "/login", http.StatusFound) // 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
return
} }
} }
+23 -21
View File
@@ -3,10 +3,11 @@ package routes
import ( import (
"fmt" "fmt"
"net/http" "net/http"
paas "proxmoxaas-common-lib"
"proxmoxaas-dashboard/app/common" "proxmoxaas-dashboard/app/common"
paas "proxmoxaas-dashboard/proxmoxaas-common-lib"
"time" "time"
"github.com/gin-gonic/gin"
"github.com/go-viper/mapstructure/v2" "github.com/go-viper/mapstructure/v2"
) )
@@ -19,60 +20,61 @@ type InstanceBackup struct {
TimeFormatted string TimeFormatted string
} }
func HandleGETBackups(w http.ResponseWriter, r *http.Request) { func HandleGETBackups(c *gin.Context) {
auth, err := common.GetAuthFromRequest(r) auth, err := common.GetAuthFromRequest(c)
if err == nil { if err == nil {
vm_path, err := common.GetInstancePathFromRequest(r) vm_path, err := common.GetInstancePathFromRequest(c)
if err != nil { if err != nil {
common.HandleNonFatalError(w, err) common.HandleNonFatalError(c, err)
return return
} }
backups, err := GetInstanceBackups(vm_path, auth) backups, err := GetInstanceBackups(vm_path, auth)
if err != nil { if err != nil {
common.HandleNonFatalError(w, fmt.Errorf("error encountered getting instance backups: %s", err.Error())) common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance backups: %s", err.Error()))
return
} }
config, err := GetInstanceConfig(vm_path, auth) // only used for the VM's name config, err := GetInstanceConfig(vm_path, auth) // only used for the VM's name
if err != nil { if err != nil {
common.HandleNonFatalError(w, fmt.Errorf("error encountered getting instance config: %s", err.Error())) common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance config: %s", err.Error()))
return
} }
paas.HTTPExecuteHTMLTemplate(w, common.TMPL, "html/backups.html", map[string]any{ c.HTML(http.StatusOK, "html/backups.html", gin.H{
"global": common.Global, "global": common.Global,
"page": "backups", "page": "backups",
"backups": backups, "backups": backups,
"config": config, "config": config,
}) })
} else { } else {
http.Redirect(w, r, "/login", http.StatusFound) c.Redirect(http.StatusFound, "/login")
return
} }
} }
func HandleGETBackupsFragment(w http.ResponseWriter, r *http.Request) { func HandleGETBackupsFragment(c *gin.Context) {
auth, err := common.GetAuthFromRequest(r) auth, err := common.GetAuthFromRequest(c)
if err == nil { // user should be authed, try to return index with population if err == nil { // user should be authed, try to return index with population
vm_path, err := common.GetInstancePathFromRequest(r) vm_path, err := common.GetInstancePathFromRequest(c)
if err != nil { if err != nil {
common.HandleNonFatalError(w, err) common.HandleNonFatalError(c, err)
return return
} }
backups, err := GetInstanceBackups(vm_path, auth) backups, err := GetInstanceBackups(vm_path, auth)
if err != nil { if err != nil {
common.HandleNonFatalError(w, fmt.Errorf("error encountered getting instance backups: %s", err.Error())) common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance backups: %s", err.Error()))
return
} }
paas.HTTPExecuteHTMLFragmentTemplate(w, common.TMPL, "html/backups-backups.go.tmpl", map[string]any{ c.Header("Content-Type", "text/plain")
err = common.TMPL.ExecuteTemplate(c.Writer, "html/backups-backups.go.tmpl", gin.H{
"backups": backups, "backups": backups,
}) })
if err != nil {
c.Status(http.StatusInternalServerError)
} else {
c.Status(http.StatusOK)
}
} else { // return 401 } else { // return 401
w.WriteHeader(http.StatusUnauthorized) c.Status(http.StatusUnauthorized)
return
} }
} }
+65 -51
View File
@@ -3,11 +3,12 @@ package routes
import ( import (
"fmt" "fmt"
"net/http" "net/http"
paas "proxmoxaas-common-lib"
"proxmoxaas-dashboard/app/common" "proxmoxaas-dashboard/app/common"
paas "proxmoxaas-dashboard/proxmoxaas-common-lib"
"slices" "slices"
"sort" "sort"
"github.com/gin-gonic/gin"
"github.com/go-viper/mapstructure/v2" "github.com/go-viper/mapstructure/v2"
) )
@@ -30,26 +31,24 @@ type PoolConfig struct {
} }
} }
func HandleGETConfig(w http.ResponseWriter, r *http.Request) { func HandleGETConfig(c *gin.Context) {
auth, err := common.GetAuthFromRequest(r) auth, err := common.GetAuthFromRequest(c)
if err == nil { if err == nil {
vm_path, err := common.GetInstancePathFromRequest(r) vm_path, err := common.GetInstancePathFromRequest(c)
if err != nil { if err != nil {
common.HandleNonFatalError(w, err) common.HandleNonFatalError(c, err)
return return
} }
config, err := GetInstanceConfig(vm_path, auth) config, err := GetInstanceConfig(vm_path, auth)
if err != nil { if err != nil {
common.HandleNonFatalError(w, fmt.Errorf("error encountered getting instance config: %s", err.Error())) common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance config: %s", err.Error()))
return
} }
if config.Type == "VM" { // if VM, fetch CPU types from node if config.Type == "VM" { // if VM, fetch CPU types from node
config.ProctypeSelect, err = GetCPUTypes(vm_path, config.Pool, auth) config.ProctypeSelect, err = GetCPUTypes(vm_path, config.Pool, auth)
if err != nil { if err != nil {
common.HandleNonFatalError(w, fmt.Errorf("error encountered getting proctypes: %s", err.Error())) common.HandleNonFatalError(c, fmt.Errorf("error encountered getting proctypes: %s", err.Error()))
return
} }
} }
for i, cpu := range config.ProctypeSelect.Options { for i, cpu := range config.ProctypeSelect.Options {
@@ -58,110 +57,125 @@ func HandleGETConfig(w http.ResponseWriter, r *http.Request) {
} }
} }
paas.HTTPExecuteHTMLTemplate(w, common.TMPL, "html/config.html", map[string]any{ c.HTML(http.StatusOK, "html/config.html", gin.H{
"global": common.Global, "global": common.Global,
"page": "config", "page": "config",
"config": config, "config": config,
}) })
} else { } else {
http.Redirect(w, r, "/login", http.StatusFound) c.Redirect(http.StatusFound, "/login")
return
} }
} }
func HandleGETConfigVolumesFragment(w http.ResponseWriter, r *http.Request) { func HandleGETConfigVolumesFragment(c *gin.Context) {
auth, err := common.GetAuthFromRequest(r) auth, err := common.GetAuthFromRequest(c)
if err == nil { if err == nil {
vm_path, err := common.GetInstancePathFromRequest(r) vm_path, err := common.GetInstancePathFromRequest(c)
if err != nil { if err != nil {
common.HandleNonFatalError(w, err) common.HandleNonFatalError(c, err)
return return
} }
config, err := GetInstanceConfig(vm_path, auth) config, err := GetInstanceConfig(vm_path, auth)
if err != nil { if err != nil {
common.HandleNonFatalError(w, fmt.Errorf("error encountered getting instance config: %s", err.Error())) common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance config: %s", err.Error()))
return
} }
paas.HTTPExecuteHTMLFragmentTemplate(w, common.TMPL, "html/config-volumes.go.tmpl", map[string]any{ c.Header("Content-Type", "text/plain")
err = common.TMPL.ExecuteTemplate(c.Writer, "html/config-volumes.go.tmpl", gin.H{
"config": config, "config": config,
}) })
if err != nil {
c.Status(http.StatusInternalServerError)
} else {
c.Status(http.StatusOK)
}
} else { } else {
w.WriteHeader(http.StatusUnauthorized) c.Status(http.StatusUnauthorized)
return
} }
} }
func HandleGETConfigNetsFragment(w http.ResponseWriter, r *http.Request) { func HandleGETConfigNetsFragment(c *gin.Context) {
auth, err := common.GetAuthFromRequest(r) auth, err := common.GetAuthFromRequest(c)
if err == nil { if err == nil {
vm_path, err := common.GetInstancePathFromRequest(r) vm_path, err := common.GetInstancePathFromRequest(c)
if err != nil { if err != nil {
common.HandleNonFatalError(w, err) common.HandleNonFatalError(c, err)
return return
} }
config, err := GetInstanceConfig(vm_path, auth) config, err := GetInstanceConfig(vm_path, auth)
if err != nil { if err != nil {
common.HandleNonFatalError(w, fmt.Errorf("error encountered getting instance config: %s", err.Error())) common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance config: %s", err.Error()))
return
} }
paas.HTTPExecuteHTMLFragmentTemplate(w, common.TMPL, "html/config-nets.go.tmpl", map[string]any{ c.Header("Content-Type", "text/plain")
err = common.TMPL.ExecuteTemplate(c.Writer, "html/config-nets.go.tmpl", gin.H{
"config": config, "config": config,
}) })
} else { // return 401 if err != nil {
w.WriteHeader(http.StatusUnauthorized) c.Status(http.StatusInternalServerError)
return } else {
c.Status(http.StatusOK)
}
} else {
c.Status(http.StatusUnauthorized)
} }
} }
func HandleGETConfigDevicesFragment(w http.ResponseWriter, r *http.Request) { func HandleGETConfigDevicesFragment(c *gin.Context) {
auth, err := common.GetAuthFromRequest(r) auth, err := common.GetAuthFromRequest(c)
if err == nil { if err == nil {
vm_path, err := common.GetInstancePathFromRequest(r) vm_path, err := common.GetInstancePathFromRequest(c)
if err != nil { if err != nil {
common.HandleNonFatalError(w, err) common.HandleNonFatalError(c, err)
return return
} }
config, err := GetInstanceConfig(vm_path, auth) config, err := GetInstanceConfig(vm_path, auth)
if err != nil { if err != nil {
common.HandleNonFatalError(w, fmt.Errorf("error encountered getting instance config: %s", err.Error())) common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance config: %s", err.Error()))
return
} }
paas.HTTPExecuteHTMLFragmentTemplate(w, common.TMPL, "html/config-devices.go.tmpl", map[string]any{ c.Header("Content-Type", "text/plain")
err = common.TMPL.ExecuteTemplate(c.Writer, "html/config-devices.go.tmpl", gin.H{
"config": config, "config": config,
}) })
} else { // return 401 if err != nil {
w.WriteHeader(http.StatusUnauthorized) c.Status(http.StatusInternalServerError)
return } else {
c.Status(http.StatusOK)
}
} else {
c.Status(http.StatusUnauthorized)
} }
} }
func HandleGETConfigBootFragment(w http.ResponseWriter, r *http.Request) { func HandleGETConfigBootFragment(c *gin.Context) {
auth, err := common.GetAuthFromRequest(r) auth, err := common.GetAuthFromRequest(c)
if err == nil { if err == nil {
vm_path, err := common.GetInstancePathFromRequest(r) vm_path, err := common.GetInstancePathFromRequest(c)
if err != nil { if err != nil {
common.HandleNonFatalError(w, err) common.HandleNonFatalError(c, err)
return return
} }
config, err := GetInstanceConfig(vm_path, auth) config, err := GetInstanceConfig(vm_path, auth)
if err != nil { if err != nil {
common.HandleNonFatalError(w, fmt.Errorf("error encountered getting instance config: %s", err.Error())) common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance config: %s", err.Error()))
return
} }
paas.HTTPExecuteHTMLFragmentTemplate(w, common.TMPL, "html/config-boot.go.tmpl", map[string]any{ c.Header("Content-Type", "text/plain")
err = common.TMPL.ExecuteTemplate(c.Writer, "html/config-boot.go.tmpl", gin.H{
"config": config, "config": config,
}) })
} else { // return 401 if err != nil {
w.WriteHeader(http.StatusUnauthorized) c.Status(http.StatusInternalServerError)
return } else {
c.Status(http.StatusOK)
}
} else {
c.Status(http.StatusUnauthorized)
} }
} }
+20 -16
View File
@@ -3,10 +3,11 @@ package routes
import ( import (
"fmt" "fmt"
"net/http" "net/http"
paas "proxmoxaas-common-lib"
"proxmoxaas-dashboard/app/common" "proxmoxaas-dashboard/app/common"
paas "proxmoxaas-dashboard/proxmoxaas-common-lib"
"strconv" "strconv"
"github.com/gin-gonic/gin"
"github.com/go-viper/mapstructure/v2" "github.com/go-viper/mapstructure/v2"
) )
@@ -44,43 +45,46 @@ type InstanceStatus struct {
Status string Status string
} }
func HandleGETIndex(w http.ResponseWriter, r *http.Request) { func HandleGETIndex(c *gin.Context) {
auth, err := common.GetAuthFromRequest(r) auth, err := common.GetAuthFromRequest(c)
if err == nil { // user should be authed, try to return index with population if err == nil { // user should be authed, try to return index with population
instances, _, err := GetClusterResources(auth) instances, _, err := GetClusterResources(auth)
if err != nil { if err != nil {
common.HandleNonFatalError(w, err) common.HandleNonFatalError(c, err)
return return
} }
page := map[string]any{ page := gin.H{
"global": common.Global, "global": common.Global,
"page": "index", "page": "index",
"instances": instances, "instances": instances,
} }
c.HTML(http.StatusOK, "html/index.html", page)
paas.HTTPExecuteHTMLTemplate(w, common.TMPL, "html/index.html", page)
} else { // return index without populating } else { // return index without populating
http.Redirect(w, r, "/login", http.StatusFound) c.Redirect(http.StatusFound, "/login") // if user is not authed, redirect user to login page
return
} }
} }
func HandleGETInstancesFragment(w http.ResponseWriter, r *http.Request) { func HandleGETInstancesFragment(c *gin.Context) {
auth, err := common.GetAuthFromRequest(r) auth, err := common.GetAuthFromRequest(c)
if err == nil { // user should be authed, try to return index with population if err == nil { // user should be authed, try to return index with population
instances, _, err := GetClusterResources(auth) instances, _, err := GetClusterResources(auth)
if err != nil { if err != nil {
common.HandleNonFatalError(w, err) common.HandleNonFatalError(c, err)
return return
} }
c.Header("Content-Type", "text/plain")
paas.HTTPExecuteHTMLFragmentTemplate(w, common.TMPL, "html/index-instances.go.tmpl", map[string]any{ err = common.TMPL.ExecuteTemplate(c.Writer, "html/index-instances.go.tmpl", gin.H{
"instances": instances, "instances": instances,
}) })
if err != nil {
c.Status(http.StatusInternalServerError)
} else {
c.Status(http.StatusOK)
}
} else { // return 401 } else { // return 401
w.WriteHeader(http.StatusUnauthorized) c.Status(http.StatusUnauthorized)
return
} }
} }
func GetClusterResources(auth paas.Auth) (map[uint]InstanceCard, map[string]Node, error) { func GetClusterResources(auth paas.Auth) (map[uint]InstanceCard, map[string]Node, error) {
+4 -5
View File
@@ -4,8 +4,8 @@ import (
"fmt" "fmt"
"net/http" "net/http"
"proxmoxaas-dashboard/app/common" "proxmoxaas-dashboard/app/common"
paas "proxmoxaas-dashboard/proxmoxaas-common-lib"
"github.com/gin-gonic/gin"
"github.com/go-viper/mapstructure/v2" "github.com/go-viper/mapstructure/v2"
) )
@@ -41,11 +41,10 @@ func GetLoginRealms() ([]Realm, error) {
return realms, nil return realms, nil
} }
func HandleGETLogin(w http.ResponseWriter, r *http.Request) { func HandleGETLogin(c *gin.Context) {
realms, err := GetLoginRealms() realms, err := GetLoginRealms()
if err != nil { if err != nil {
common.HandleNonFatalError(w, err) common.HandleNonFatalError(c, err)
return
} }
sel := common.Select{ sel := common.Select{
@@ -61,7 +60,7 @@ func HandleGETLogin(w http.ResponseWriter, r *http.Request) {
}) })
} }
paas.HTTPExecuteHTMLTemplate(w, common.TMPL, "html/login.html", map[string]any{ c.HTML(http.StatusOK, "html/login.html", gin.H{
"global": common.Global, "global": common.Global,
"page": "login", "page": "login",
"realms": sel, "realms": sel,
+6 -12
View File
@@ -3,24 +3,18 @@ package routes
import ( import (
"net/http" "net/http"
"proxmoxaas-dashboard/app/common" "proxmoxaas-dashboard/app/common"
paas "proxmoxaas-dashboard/proxmoxaas-common-lib"
"github.com/gin-gonic/gin"
) )
func HandleGETSettings(w http.ResponseWriter, r *http.Request) { func HandleGETSettings(c *gin.Context) {
auth, err := common.GetAuthFromRequest(r) _, err := common.GetAuthFromRequest(c)
if err == nil { if err == nil {
_, err := GetUserBasic(auth) c.HTML(http.StatusOK, "html/settings.html", gin.H{
if err != nil {
common.HandleNonFatalError(w, err)
return
}
paas.HTTPExecuteHTMLTemplate(w, common.TMPL, "html/settings.html", map[string]any{
"global": common.Global, "global": common.Global,
"page": "settings", "page": "settings",
}) })
} else { } else {
http.Redirect(w, r, "/login", http.StatusFound) c.Redirect(http.StatusFound, "/login")
return
} }
} }
-604
View File
@@ -1,604 +0,0 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
"use strict";
(() => {
const enosys = () => {
const err = new Error("not implemented");
err.code = "ENOSYS";
return err;
};
if (!globalThis.fs) {
let outputBuf = "";
globalThis.fs = {
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1, O_DIRECTORY: -1 }, // unused
writeSync(fd, buf) {
outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) {
console.log(outputBuf.substring(0, nl));
outputBuf = outputBuf.substring(nl + 1);
}
return buf.length;
},
write(fd, buf, offset, length, position, callback) {
if (offset !== 0 || length !== buf.length || position !== null) {
callback(enosys());
return;
}
const n = this.writeSync(fd, buf);
callback(null, n);
},
chmod(path, mode, callback) { callback(enosys()); },
chown(path, uid, gid, callback) { callback(enosys()); },
close(fd, callback) { callback(enosys()); },
fchmod(fd, mode, callback) { callback(enosys()); },
fchown(fd, uid, gid, callback) { callback(enosys()); },
fstat(fd, callback) { callback(enosys()); },
fsync(fd, callback) { callback(null); },
ftruncate(fd, length, callback) { callback(enosys()); },
lchown(path, uid, gid, callback) { callback(enosys()); },
link(path, link, callback) { callback(enosys()); },
lstat(path, callback) { callback(enosys()); },
mkdir(path, perm, callback) { callback(enosys()); },
open(path, flags, mode, callback) { callback(enosys()); },
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
readdir(path, callback) { callback(enosys()); },
readlink(path, callback) { callback(enosys()); },
rename(from, to, callback) { callback(enosys()); },
rmdir(path, callback) { callback(enosys()); },
stat(path, callback) { callback(enosys()); },
symlink(path, link, callback) { callback(enosys()); },
truncate(path, length, callback) { callback(enosys()); },
unlink(path, callback) { callback(enosys()); },
utimes(path, atime, mtime, callback) { callback(enosys()); },
};
}
if (!globalThis.process) {
globalThis.process = {
getuid() { return -1; },
getgid() { return -1; },
geteuid() { return -1; },
getegid() { return -1; },
getgroups() { throw enosys(); },
pid: -1,
ppid: -1,
umask() { throw enosys(); },
cwd() { throw enosys(); },
chdir() { throw enosys(); },
}
}
if (!globalThis.path) {
globalThis.path = {
resolve(...pathSegments) {
return pathSegments.join("/");
}
}
}
if (!globalThis.crypto) {
throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
}
if (!globalThis.performance) {
throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
}
if (!globalThis.TextEncoder) {
throw new Error("globalThis.TextEncoder is not available, polyfill required");
}
if (!globalThis.TextDecoder) {
throw new Error("globalThis.TextDecoder is not available, polyfill required");
}
const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder("utf-8");
globalThis.Go = class {
constructor() {
this.argv = ["js"];
this.env = {};
this.exit = (code) => {
if (code !== 0) {
console.warn("exit code:", code);
}
};
this._exitPromise = new Promise((resolve) => {
this._resolveExitPromise = resolve;
});
this._pendingEvent = null;
this._scheduledTimeouts = new Map();
this._nextCallbackTimeoutID = 1;
const setInt64 = (addr, v) => {
this.mem.setUint32(addr + 0, v, true);
this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
}
const setInt32 = (addr, v) => {
this.mem.setUint32(addr + 0, v, true);
}
const getInt64 = (addr) => {
const low = this.mem.getUint32(addr + 0, true);
const high = this.mem.getInt32(addr + 4, true);
return low + high * 4294967296;
}
const loadValue = (addr) => {
const f = this.mem.getFloat64(addr, true);
if (f === 0) {
return undefined;
}
if (!isNaN(f)) {
return f;
}
const id = this.mem.getUint32(addr, true);
return this._values[id];
}
const storeValue = (addr, v) => {
const nanHead = 0x7FF80000;
if (typeof v === "number" && v !== 0) {
if (isNaN(v)) {
this.mem.setUint32(addr + 4, nanHead, true);
this.mem.setUint32(addr, 0, true);
return;
}
this.mem.setFloat64(addr, v, true);
return;
}
if (v === undefined) {
this.mem.setFloat64(addr, 0, true);
return;
}
let id = this._ids.get(v);
if (id === undefined) {
id = this._idPool.pop();
if (id === undefined) {
id = this._values.length;
}
this._values[id] = v;
this._goRefCounts[id] = 0;
this._ids.set(v, id);
}
this._goRefCounts[id]++;
let typeFlag = 0;
switch (typeof v) {
case "object":
if (v !== null) {
typeFlag = 1;
}
break;
case "string":
typeFlag = 2;
break;
case "symbol":
typeFlag = 3;
break;
case "function":
typeFlag = 4;
break;
}
this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
this.mem.setUint32(addr, id, true);
}
const loadSlice = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
}
const loadSliceOfValues = (addr) => {
const array = getInt64(addr + 0);
const len = getInt64(addr + 8);
const a = new Array(len);
for (let i = 0; i < len; i++) {
a[i] = loadValue(array + i * 8);
}
return a;
}
const loadString = (addr) => {
const saddr = getInt64(addr + 0);
const len = getInt64(addr + 8);
return decoder.decode(new DataView(this._inst.exports.mem.buffer, saddr, len));
}
const testCallExport = (a, b) => {
this._inst.exports.testExport0();
return this._inst.exports.testExport(a, b);
}
const timeOrigin = Date.now() - performance.now();
this.importObject = {
_gotest: {
add: (a, b) => a + b,
callExport: testCallExport,
},
gojs: {
// Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
// may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
// function. A goroutine can switch to a new stack if the current stack is too small (see morestack function).
// This changes the SP, thus we have to update the SP used by the imported function.
// func wasmExit(code int32)
"runtime.wasmExit": (sp) => {
sp >>>= 0;
const code = this.mem.getInt32(sp + 8, true);
this.exited = true;
delete this._inst;
delete this._values;
delete this._goRefCounts;
delete this._ids;
delete this._idPool;
this.exit(code);
},
// func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
"runtime.wasmWrite": (sp) => {
sp >>>= 0;
const fd = getInt64(sp + 8);
const p = getInt64(sp + 16);
const n = this.mem.getInt32(sp + 24, true);
fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
},
// func resetMemoryDataView()
"runtime.resetMemoryDataView": (sp) => {
sp >>>= 0;
this.mem = new DataView(this._inst.exports.mem.buffer);
},
// func nanotime1() int64
"runtime.nanotime1": (sp) => {
sp >>>= 0;
setInt64(sp + 8, (timeOrigin + performance.now()) * 1000000);
},
// func walltime() (sec int64, nsec int32)
"runtime.walltime": (sp) => {
sp >>>= 0;
const msec = (new Date).getTime();
setInt64(sp + 8, msec / 1000);
this.mem.setInt32(sp + 16, (msec % 1000) * 1000000, true);
},
// func scheduleTimeoutEvent(delay int64) int32
"runtime.scheduleTimeoutEvent": (sp) => {
sp >>>= 0;
const id = this._nextCallbackTimeoutID;
this._nextCallbackTimeoutID++;
this._scheduledTimeouts.set(id, setTimeout(
() => {
this._resume();
while (this._scheduledTimeouts.has(id)) {
// for some reason Go failed to register the timeout event, log and try again
// (temporary workaround for https://github.com/golang/go/issues/28975)
console.warn("scheduleTimeoutEvent: missed timeout event");
this._resume();
}
},
getInt64(sp + 8),
));
this.mem.setInt32(sp + 16, id, true);
},
// func clearTimeoutEvent(id int32)
"runtime.clearTimeoutEvent": (sp) => {
sp >>>= 0;
const id = this.mem.getInt32(sp + 8, true);
clearTimeout(this._scheduledTimeouts.get(id));
this._scheduledTimeouts.delete(id);
},
// func getRandomData(r []byte)
"runtime.getRandomData": (sp) => {
sp >>>= 0;
crypto.getRandomValues(loadSlice(sp + 8));
},
// func finalizeRef(v ref)
"syscall/js.finalizeRef": (sp) => {
sp >>>= 0;
const id = this.mem.getUint32(sp + 8, true);
this._goRefCounts[id]--;
if (this._goRefCounts[id] === 0) {
const v = this._values[id];
this._values[id] = null;
this._ids.delete(v);
this._idPool.push(id);
}
},
// func stringVal(value string) ref
"syscall/js.stringVal": (sp) => {
sp >>>= 0;
storeValue(sp + 24, loadString(sp + 8));
},
// func valueGet(v ref, p string) ref
"syscall/js.valueGet": (sp) => {
sp >>>= 0;
const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 32, result);
},
// func valueSet(v ref, p string, x ref)
"syscall/js.valueSet": (sp) => {
sp >>>= 0;
Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
},
// func valueDelete(v ref, p string)
"syscall/js.valueDelete": (sp) => {
sp >>>= 0;
Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
},
// func valueIndex(v ref, i int) ref
"syscall/js.valueIndex": (sp) => {
sp >>>= 0;
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
},
// valueSetIndex(v ref, i int, x ref)
"syscall/js.valueSetIndex": (sp) => {
sp >>>= 0;
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
},
// func valueCall(v ref, m string, args []ref) (ref, bool)
"syscall/js.valueCall": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const m = Reflect.get(v, loadString(sp + 16));
const args = loadSliceOfValues(sp + 32);
const result = Reflect.apply(m, v, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 56, result);
this.mem.setUint8(sp + 64, 1);
} catch (err) {
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 56, err);
this.mem.setUint8(sp + 64, 0);
}
},
// func valueInvoke(v ref, args []ref) (ref, bool)
"syscall/js.valueInvoke": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.apply(v, undefined, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, result);
this.mem.setUint8(sp + 48, 1);
} catch (err) {
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
}
},
// func valueNew(v ref, args []ref) (ref, bool)
"syscall/js.valueNew": (sp) => {
sp >>>= 0;
try {
const v = loadValue(sp + 8);
const args = loadSliceOfValues(sp + 16);
const result = Reflect.construct(v, args);
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, result);
this.mem.setUint8(sp + 48, 1);
} catch (err) {
sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
}
},
// func valueLength(v ref) int
"syscall/js.valueLength": (sp) => {
sp >>>= 0;
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
},
// valuePrepareString(v ref) (ref, int)
"syscall/js.valuePrepareString": (sp) => {
sp >>>= 0;
const str = encoder.encode(String(loadValue(sp + 8)));
storeValue(sp + 16, str);
setInt64(sp + 24, str.length);
},
// valueLoadString(v ref, b []byte)
"syscall/js.valueLoadString": (sp) => {
sp >>>= 0;
const str = loadValue(sp + 8);
loadSlice(sp + 16).set(str);
},
// func valueInstanceOf(v ref, t ref) bool
"syscall/js.valueInstanceOf": (sp) => {
sp >>>= 0;
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
},
// func copyBytesToGo(dst []byte, src ref) (int, bool)
"syscall/js.copyBytesToGo": (sp) => {
sp >>>= 0;
const dst = loadSlice(sp + 8);
const src = loadValue(sp + 32);
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
this.mem.setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
this.mem.setUint8(sp + 48, 1);
},
// func copyBytesToJS(dst ref, src []byte) (int, bool)
"syscall/js.copyBytesToJS": (sp) => {
sp >>>= 0;
const dst = loadValue(sp + 8);
const src = loadSlice(sp + 16);
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
this.mem.setUint8(sp + 48, 0);
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
setInt64(sp + 40, toCopy.length);
this.mem.setUint8(sp + 48, 1);
},
"debug": (value) => {
console.log(value);
},
}
};
}
async run(instance) {
if (!(instance instanceof WebAssembly.Instance)) {
throw new Error("Go.run: WebAssembly.Instance expected");
}
this._inst = instance;
this.mem = new DataView(this._inst.exports.mem.buffer);
this._values = [ // JS values that Go currently has references to, indexed by reference id
NaN,
0,
null,
true,
false,
globalThis,
this,
];
this._goRefCounts = new Array(this._values.length).fill(Infinity); // number of references that Go has to a JS value, indexed by reference id
this._ids = new Map([ // mapping from JS values to reference ids
[0, 1],
[null, 2],
[true, 3],
[false, 4],
[globalThis, 5],
[this, 6],
]);
this._idPool = []; // unused ids that have been garbage collected
this.exited = false; // whether the Go program has exited
// Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
let offset = 4096;
const strPtr = (str) => {
const ptr = offset;
const bytes = encoder.encode(str + "\0");
new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
offset += bytes.length;
if (offset % 8 !== 0) {
offset += 8 - (offset % 8);
}
return ptr;
};
const argc = this.argv.length;
const argvPtrs = [];
this.argv.forEach((arg) => {
argvPtrs.push(strPtr(arg));
});
argvPtrs.push(0);
const keys = Object.keys(this.env).sort();
keys.forEach((key) => {
argvPtrs.push(strPtr(`${key}=${this.env[key]}`));
});
argvPtrs.push(0);
const argv = offset;
argvPtrs.forEach((ptr) => {
this.mem.setUint32(offset, ptr, true);
this.mem.setUint32(offset + 4, 0, true);
offset += 8;
});
// The linker guarantees global data starts from at least wasmMinDataAddr.
// Keep in sync with cmd/link/internal/ld/data.go:wasmMinDataAddr.
const wasmMinDataAddr = 4096 + 8192;
if (offset >= wasmMinDataAddr) {
throw new Error("total length of command line and environment variables exceeds limit");
}
this._inst.exports.run(argc, argv);
if (this.exited) {
this._resolveExitPromise();
}
await this._exitPromise;
}
_resume() {
if (this.exited) {
throw new Error("Go program has already exited");
}
this._inst.exports.resume();
if (this.exited) {
this._resolveExitPromise();
}
}
_makeFuncWrapper(id) {
const go = this;
return function () {
const event = { id: id, this: this, args: arguments };
go._pendingEvent = event;
go._resume();
return event.result;
};
}
}
})();
// wasm setup code
export default function init (path) {
if (typeof global === "undefined") {
var global = globalThis;
}
return new Promise ((res) => {
const go = new Go();
const run = (obj) => {
const wasm = obj.instance;
global.wfa = wasm
go.run(wasm);
res(wasm)
}
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(path), go.importObject).then(function (obj) {
run(obj)
})
} else {
fetch(path).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
run(obj)
})
)
}
})
}
Binary file not shown.
-3
View File
@@ -1,3 +0,0 @@
app/web/modules/*
WFA-JS/*
dist/*
+25 -29
View File
@@ -1,33 +1,29 @@
import { defineConfig, globalIgnores } from "eslint/config"; import { defineConfig } from "eslint/config";
import globals from "globals"; import globals from "globals";
import js from "@eslint/js"; import js from "@eslint/js";
export default defineConfig([ export default defineConfig([js.configs.recommended,{
js.configs.recommended, languageOptions: {
globalIgnores(["dist/", "app/web/modules", "WFA-JS"]), globals: {
{ ...globals.browser,
languageOptions: {
globals: {
...globals.browser,
},
ecmaVersion: "latest",
sourceType: "module",
}, },
rules: { ecmaVersion: "latest",
"no-tabs": ["error", { sourceType: "module",
allowIndentationTabs: true, },
}], rules: {
indent: ["error", "tab"], "no-tabs": ["error", {
"linebreak-style": ["error", "unix"], allowIndentationTabs: true,
quotes: ["error", "double"], }],
semi: ["error", "always"], indent: ["error", "tab"],
"brace-style": ["error", "stroustrup", { allowSingleLine: false }], "linebreak-style": ["error", "unix"],
"no-unused-vars": ["warn", { quotes: ["error", "double"],
"argsIgnorePattern": "^_", semi: ["error", "always"],
"varsIgnorePattern": "^_", "brace-style": ["error", "stroustrup", { allowSingleLine: false }],
"caughtErrorsIgnorePattern": "^_" "no-unused-vars": ["warn", {
}], "argsIgnorePattern": "^_",
"prefer-const": ["error"] "varsIgnorePattern": "^_",
}, "caughtErrorsIgnorePattern": "^_"
} }],
]); "prefer-const": ["error"]
},
}]);
+38 -3
View File
@@ -1,11 +1,46 @@
module proxmoxaas-dashboard module proxmoxaas-dashboard
go 1.27.3 go 1.26.5
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/go-viper/mapstructure/v2 v2.5.0 github.com/go-viper/mapstructure/v2 v2.5.0
github.com/tdewolff/minify/v2 v2.24.23 github.com/tdewolff/minify/v2 v2.24.13
proxmoxaas-common-lib v0.0.0
) )
require github.com/tdewolff/parse/v2 v2.8.20 // indirect replace proxmoxaas-common-lib => ./proxmoxaas-common-lib
require (
github.com/bytedance/gopkg v0.1.4 // indirect
github.com/bytedance/sonic v1.15.2 // indirect
github.com/bytedance/sonic/loader v0.5.1 // indirect
github.com/cloudwego/base64x v0.1.7 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect
github.com/gin-contrib/sse v1.1.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/validator/v10 v10.30.3 // indirect
github.com/goccy/go-json v0.10.6 // indirect
github.com/goccy/go-yaml v1.19.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.4.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect
github.com/mattn/go-isatty v0.0.23 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.4.3 // indirect
github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.60.0 // indirect
github.com/tdewolff/parse/v2 v2.8.13 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect
go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect
golang.org/x/arch v0.29.0 // indirect
golang.org/x/crypto v0.54.0 // indirect
golang.org/x/net v0.57.0 // indirect
golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.40.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect
)
+18
View File
@@ -0,0 +1,18 @@
{
"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"
}
}
File renamed without changes.
File renamed without changes.
+3 -3
View File
@@ -191,18 +191,18 @@ hr {
} }
/* add hide large class similar to w3-hide-medium and w3-hide-small */ /* add hide large class similar to w3-hide-medium and w3-hide-small */
@media screen and (width >= 993px) { @media screen and (width >=993px) {
.hide-large {display: none !important;} .hide-large {display: none !important;}
} }
/* fixes edge case in w3-hide-medium where width between 992 and 993 */ /* fixes edge case in w3-hide-medium where width between 992 and 993 */
@media screen and (width <= 993px) and (width >= 601px){ @media screen and (width <=993px) and (width >=601px){
.hide-large {display: none !important;} .hide-large {display: none !important;}
.hide-medium {display:none !important} .hide-medium {display:none !important}
} }
/* 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) and (width >= 440px) { @media screen and (width <=601px) {
.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}
+7 -2
View File
@@ -5,11 +5,16 @@ import (
) )
//go:embed css/* //go:embed css/*
var CSS_fs embed.FS
//go:embed images/* //go:embed images/*
//go:embed images/*/*/* var Images_fs embed.FS
//go:embed modules/* //go:embed modules/*
var Modules_fs embed.FS
//go:embed scripts/* //go:embed scripts/*
var Static embed.FS var Scripts_fs embed.FS
//go:embed html/* //go:embed html/*
//go:embed templates/* //go:embed templates/*
@@ -2,9 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
{{template "head" .}} {{template "head" .}}
<script src="websrc/scripts/account.js" type="module"></script> <script src="scripts/account.js" type="module"></script>
<link rel="modulepreload" href="websrc/scripts/utils.js"> <link rel="modulepreload" href="scripts/utils.js">
<link rel="modulepreload" href="websrc/scripts/dialog.js"> <link rel="modulepreload" href="scripts/dialog.js">
<style> <style>
@media screen and (width >= 1264px){ @media screen and (width >= 1264px){
.resource-container { .resource-container {
@@ -48,9 +48,9 @@
{{end}} {{end}}
</main> </main>
<template id="change-password-dialog"> <template id="change-password-dialog">
<link rel="stylesheet" href="websrc/modules/w3.css"> <link rel="stylesheet" href="modules/w3.css">
<link rel="stylesheet" href="websrc/css/style.css"> <link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="websrc/css/form.css"> <link rel="stylesheet" href="css/form.css">
<dialog class="w3-container w3-card w3-border-0"> <dialog class="w3-container w3-card w3-border-0">
<p class="w3-large" id="prompt"> <p class="w3-large" id="prompt">
Change Password Change Password
@@ -58,14 +58,14 @@
<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" autocomplete="new-password" required> <input class="w3-input w3-border" id="new-password" name="new-password" type="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" autocomplete="new-password" required> <input class="w3-input w3-border" id="confirm-password" name="confirm-password" type="password" required>
</form> </form>
</div> </div>
<div id="controls" class="w3-center w3-container"> <div id="controls" class="w3-center w3-container">
<button id="cancel" type="submit" value="cancel" form="form" class="w3-button w3-margin" formnovalidate>CANCEL</button> <button id="cancel" type="submit" value="cancel" form="form" class="w3-button w3-margin" formnovalidate>CANCEL</button>
<button id="confirm" type="submit" value="confirm" form="form" class="w3-button w3-margin">CONFIRM</button> <button id="confirm" type="submit" value="confirm" form="form" class="w3-button w3-margin" >CONFIRM</button>
</div> </div>
</dialog> </dialog>
</template> </template>
File renamed without changes.
@@ -2,9 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
{{template "head" .}} {{template "head" .}}
<script src="websrc/scripts/backups.js" type="module"></script> <script src="scripts/backups.js" type="module"></script>
<link rel="modulepreload" href="websrc/scripts/utils.js"> <link rel="modulepreload" href="scripts/utils.js">
<link rel="modulepreload" href="websrc/scripts/dialog.js"> <link rel="modulepreload" href="scripts/dialog.js">
<style> <style>
</style> </style>
</head> </head>
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -2,11 +2,11 @@
<html lang="en"> <html lang="en">
<head> <head>
{{template "head" .}} {{template "head" .}}
<script src="websrc/scripts/config.js" type="module"></script> <script src="scripts/config.js" type="module"></script>
<script src="websrc/scripts/draggable.js" type="module"></script> <script src="scripts/draggable.js" type="module"></script>
<script src="websrc/modules/Sortable.min.js"></script> <script src="modules/Sortable.min.js"></script>
<link rel="modulepreload" href="websrc/scripts/utils.js"> <link rel="modulepreload" href="scripts/utils.js">
<link rel="modulepreload" href="websrc/scripts/dialog.js"> <link rel="modulepreload" href="scripts/dialog.js">
<style> <style>
.input-grid p, .input-grid div { .input-grid p, .input-grid div {
margin-top: 0; margin-top: 0;
File renamed without changes.
@@ -2,11 +2,11 @@
<html lang="en"> <html lang="en">
<head> <head>
{{template "head" .}} {{template "head" .}}
<script src="websrc/scripts/index.js" type="module"></script> <script src="scripts/index.js" type="module"></script>
<script src="websrc/modules/wfa.js" type="module"></script> <script src="modules/wfa.js" type="module"></script>
<link rel="modulepreload" href="websrc/scripts/utils.js"> <link rel="modulepreload" href="scripts/utils.js">
<link rel="modulepreload" href="websrc/scripts/dialog.js"> <link rel="modulepreload" href="scripts/dialog.js">
<link rel="modulepreload" href="websrc/scripts/clientsync.js"> <link rel="modulepreload" href="scripts/clientsync.js">
<style> <style>
@media screen and (width >= 440px) { @media screen and (width >= 440px) {
#vm-search { #vm-search {
@@ -72,13 +72,13 @@
<div class="w3-card w3-padding"> <div class="w3-card w3-padding">
<div class="flex row nowrap" style="margin-top: 1em; justify-content: space-between;"> <div class="flex row nowrap" style="margin-top: 1em; justify-content: space-between;">
<form id="vm-search" role="search" class="flex row nowrap" tabindex="0"> <form id="vm-search" role="search" class="flex row nowrap" tabindex="0">
<button type="submit" id="submit" class="w3-button" style="padding: 0; width: 1em; height: 1em; line-height: 1em;"><img alt="Search Instances" aria-label="Search Instances" src="websrc/images/common/search.svg#symb"></button> <button type="submit" id="submit" class="w3-button" style="padding: 0; width: 1em; height: 1em; line-height: 1em;"><img alt="Search Instances" aria-label="Search Instances" src="images/common/search.svg#symb"></button>
<input type="search" id="search" class="w3-input w3-border" style="height: 1lh; max-width: fit-content;" aria-label="search instances by name"> <input type="search" id="search" class="w3-input w3-border" style="height: 1lh; max-width: fit-content;" aria-label="search instances by name">
</form> </form>
<!--Add Instance Button & Dialog Template--> <!--Add Instance Button & Dialog Template-->
<button type="button" id="instance-add" class="w3-button" aria-label="create new instance"> <button type="button" id="instance-add" class="w3-button" aria-label="create new instance">
<span class="large" style="margin: 0;">Create Instance</span> <span class="large" style="margin: 0;">Create Instance</span>
<img class="small" style="height: 1lh; width: 1lh;" alt="Create Instance" aria-label="Create Instance" src="websrc/images/actions/instance/add.svg#symb"> <img class="small" style="height: 1lh; width: 1lh;" alt="Create Instance" aria-label="Create Instance" src="images/actions/instance/add.svg#symb">
</button> </button>
<template id="create-instance-dialog"> <template id="create-instance-dialog">
<dialog class="w3-container w3-card w3-border-0"> <dialog class="w3-container w3-card w3-border-0">
@@ -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" autocomplete="new-password" required disabled> <input class="w3-input w3-border container-specific none" name="password" id="password" type="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" autocomplete="new-password" required disabled> <input class="w3-input w3-border container-specific none" name="confirm-password" id="confirm-password" type="password" required disabled>
</form> </form>
</div> </div>
<div id="controls" class="w3-center w3-container"> <div id="controls" class="w3-center w3-container">
@@ -2,9 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
{{template "head" .}} {{template "head" .}}
<script src="websrc/scripts/login.js" type="module"></script> <script src="scripts/login.js" type="module"></script>
<link rel="modulepreload" href="websrc/scripts/utils.js"> <link rel="modulepreload" href="scripts/utils.js">
<link rel="modulepreload" href="websrc/scripts/dialog.js"> <link rel="modulepreload" href="scripts/dialog.js">
</head> </head>
<body> <body>
{{template "header" .}} {{template "header" .}}
@@ -2,8 +2,8 @@
<html lang="en"> <html lang="en">
<head> <head>
{{template "head" .}} {{template "head" .}}
<script src="websrc/scripts/settings.js" type="module"></script> <script src="scripts/settings.js" type="module"></script>
<link rel="modulepreload" href="websrc/scripts/utils.js"> <link rel="modulepreload" href="scripts/utils.js">
<style> <style>
label { label {
display: flex; display: flex;
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+579
View File
@@ -0,0 +1,579 @@
// Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
//
// This file has been modified for use by the TinyGo compiler.
(() => {
// Map multiple JavaScript environments to a single common API,
// preferring web standards over Node.js API.
//
// Environments considered:
// - Browsers
// - Node.js
// - Electron
// - Parcel
if (typeof global !== "undefined") {
// global already exists
} else if (typeof window !== "undefined") {
window.global = window;
} else if (typeof self !== "undefined") {
self.global = self;
} else {
throw new Error("cannot export Go (neither global, window nor self is defined)");
}
if (!global.require && typeof require !== "undefined") {
global.require = require;
}
if (!global.fs && global.require) {
global.fs = require("node:fs");
}
const enosys = () => {
const err = new Error("not implemented");
err.code = "ENOSYS";
return err;
};
if (!global.fs) {
let outputBuf = "";
global.fs = {
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused
writeSync(fd, buf) {
outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) {
console.log(outputBuf.substr(0, nl));
outputBuf = outputBuf.substr(nl + 1);
}
return buf.length;
},
write(fd, buf, offset, length, position, callback) {
if (offset !== 0 || length !== buf.length || position !== null) {
callback(enosys());
return;
}
const n = this.writeSync(fd, buf);
callback(null, n);
},
chmod(path, mode, callback) { callback(enosys()); },
chown(path, uid, gid, callback) { callback(enosys()); },
close(fd, callback) { callback(enosys()); },
fchmod(fd, mode, callback) { callback(enosys()); },
fchown(fd, uid, gid, callback) { callback(enosys()); },
fstat(fd, callback) { callback(enosys()); },
fsync(fd, callback) { callback(null); },
ftruncate(fd, length, callback) { callback(enosys()); },
lchown(path, uid, gid, callback) { callback(enosys()); },
link(path, link, callback) { callback(enosys()); },
lstat(path, callback) { callback(enosys()); },
mkdir(path, perm, callback) { callback(enosys()); },
open(path, flags, mode, callback) { callback(enosys()); },
read(fd, buffer, offset, length, position, callback) { callback(enosys()); },
readdir(path, callback) { callback(enosys()); },
readlink(path, callback) { callback(enosys()); },
rename(from, to, callback) { callback(enosys()); },
rmdir(path, callback) { callback(enosys()); },
stat(path, callback) { callback(enosys()); },
symlink(path, link, callback) { callback(enosys()); },
truncate(path, length, callback) { callback(enosys()); },
unlink(path, callback) { callback(enosys()); },
utimes(path, atime, mtime, callback) { callback(enosys()); },
};
}
if (!global.process) {
global.process = {
getuid() { return -1; },
getgid() { return -1; },
geteuid() { return -1; },
getegid() { return -1; },
getgroups() { throw enosys(); },
pid: -1,
ppid: -1,
umask() { throw enosys(); },
cwd() { throw enosys(); },
chdir() { throw enosys(); },
}
}
if (!global.crypto) {
const nodeCrypto = require("node:crypto");
global.crypto = {
getRandomValues(b) {
nodeCrypto.randomFillSync(b);
},
};
}
if (!global.performance) {
global.performance = {
now() {
const [sec, nsec] = process.hrtime();
return sec * 1000 + nsec / 1000000;
},
};
}
if (!global.TextEncoder) {
global.TextEncoder = require("node:util").TextEncoder;
}
if (!global.TextDecoder) {
global.TextDecoder = require("node:util").TextDecoder;
}
// End of polyfills for common API.
const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder("utf-8");
let reinterpretBuf = new DataView(new ArrayBuffer(8));
var logLine = [];
const wasmExit = {}; // thrown to exit via proc_exit (not an error)
global.Go = class {
constructor() {
this._callbackTimeouts = new Map();
this._nextCallbackTimeoutID = 1;
const mem = () => {
// The buffer may change when requesting more memory.
return new DataView(this._inst.exports.memory.buffer);
}
const unboxValue = (v_ref) => {
reinterpretBuf.setBigInt64(0, v_ref, true);
const f = reinterpretBuf.getFloat64(0, true);
if (f === 0) {
return undefined;
}
if (!isNaN(f)) {
return f;
}
const id = v_ref & 0xffffffffn;
return this._values[id];
}
const loadValue = (addr) => {
let v_ref = mem().getBigUint64(addr, true);
return unboxValue(v_ref);
}
const boxValue = (v) => {
const nanHead = 0x7FF80000n;
if (typeof v === "number") {
if (isNaN(v)) {
return nanHead << 32n;
}
if (v === 0) {
return (nanHead << 32n) | 1n;
}
reinterpretBuf.setFloat64(0, v, true);
return reinterpretBuf.getBigInt64(0, true);
}
switch (v) {
case undefined:
return 0n;
case null:
return (nanHead << 32n) | 2n;
case true:
return (nanHead << 32n) | 3n;
case false:
return (nanHead << 32n) | 4n;
}
let id = this._ids.get(v);
if (id === undefined) {
id = this._idPool.pop();
if (id === undefined) {
id = BigInt(this._values.length);
}
this._values[id] = v;
this._goRefCounts[id] = 0;
this._ids.set(v, id);
}
this._goRefCounts[id]++;
let typeFlag = 1n;
switch (typeof v) {
case "string":
typeFlag = 2n;
break;
case "symbol":
typeFlag = 3n;
break;
case "function":
typeFlag = 4n;
break;
}
return id | ((nanHead | typeFlag) << 32n);
}
const storeValue = (addr, v) => {
let v_ref = boxValue(v);
mem().setBigUint64(addr, v_ref, true);
}
const loadSlice = (array, len, cap) => {
return new Uint8Array(this._inst.exports.memory.buffer, array, len);
}
const loadSliceOfValues = (array, len, cap) => {
const a = new Array(len);
for (let i = 0; i < len; i++) {
a[i] = loadValue(array + i * 8);
}
return a;
}
const loadString = (ptr, len) => {
return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len));
}
const timeOrigin = Date.now() - performance.now();
this.importObject = {
wasi_snapshot_preview1: {
// https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write
fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) {
let nwritten = 0;
if (fd == 1) {
for (let iovs_i=0; iovs_i<iovs_len;iovs_i++) {
let iov_ptr = iovs_ptr+iovs_i*8; // assuming wasm32
let ptr = mem().getUint32(iov_ptr + 0, true);
let len = mem().getUint32(iov_ptr + 4, true);
nwritten += len;
for (let i=0; i<len; i++) {
let c = mem().getUint8(ptr+i);
if (c == 13) { // CR
// ignore
} else if (c == 10) { // LF
// write line
let line = decoder.decode(new Uint8Array(logLine));
logLine = [];
console.log(line);
} else {
logLine.push(c);
}
}
}
} else {
console.error('invalid file descriptor:', fd);
}
mem().setUint32(nwritten_ptr, nwritten, true);
return 0;
},
fd_close: () => 0, // dummy
fd_fdstat_get: () => 0, // dummy
fd_seek: () => 0, // dummy
proc_exit: (code) => {
this.exited = true;
this.exitCode = code;
this._resolveExitPromise();
throw wasmExit;
},
random_get: (bufPtr, bufLen) => {
crypto.getRandomValues(loadSlice(bufPtr, bufLen));
return 0;
},
},
gojs: {
// func ticks() int64
"runtime.ticks": () => {
return BigInt((timeOrigin + performance.now()) * 1e6);
},
// func sleepTicks(timeout int64)
"runtime.sleepTicks": (timeout) => {
// Do not sleep, only reactivate scheduler after the given timeout.
setTimeout(() => {
if (this.exited) return;
try {
this._inst.exports.go_scheduler();
} catch (e) {
if (e !== wasmExit) throw e;
}
}, Number(timeout)/1e6);
},
// func finalizeRef(v ref)
"syscall/js.finalizeRef": (v_ref) => {
// Note: TinyGo does not support finalizers so this is only called
// for one specific case, by js.go:jsString. and can/might leak memory.
const id = v_ref & 0xffffffffn;
if (this._goRefCounts?.[id] !== undefined) {
this._goRefCounts[id]--;
if (this._goRefCounts[id] === 0) {
const v = this._values[id];
this._values[id] = null;
this._ids.delete(v);
this._idPool.push(id);
}
} else {
console.error("syscall/js.finalizeRef: unknown id", id);
}
},
// func stringVal(value string) ref
"syscall/js.stringVal": (value_ptr, value_len) => {
value_ptr >>>= 0;
const s = loadString(value_ptr, value_len);
return boxValue(s);
},
// func valueGet(v ref, p string) ref
"syscall/js.valueGet": (v_ref, p_ptr, p_len) => {
let prop = loadString(p_ptr, p_len);
let v = unboxValue(v_ref);
let result = Reflect.get(v, prop);
return boxValue(result);
},
// func valueSet(v ref, p string, x ref)
"syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => {
const v = unboxValue(v_ref);
const p = loadString(p_ptr, p_len);
const x = unboxValue(x_ref);
Reflect.set(v, p, x);
},
// func valueDelete(v ref, p string)
"syscall/js.valueDelete": (v_ref, p_ptr, p_len) => {
const v = unboxValue(v_ref);
const p = loadString(p_ptr, p_len);
Reflect.deleteProperty(v, p);
},
// func valueIndex(v ref, i int) ref
"syscall/js.valueIndex": (v_ref, i) => {
return boxValue(Reflect.get(unboxValue(v_ref), i));
},
// valueSetIndex(v ref, i int, x ref)
"syscall/js.valueSetIndex": (v_ref, i, x_ref) => {
Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref));
},
// func valueCall(v ref, m string, args []ref) (ref, bool)
"syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => {
const v = unboxValue(v_ref);
const name = loadString(m_ptr, m_len);
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
try {
const m = Reflect.get(v, name);
storeValue(ret_addr, Reflect.apply(m, v, args));
mem().setUint8(ret_addr + 8, 1);
} catch (err) {
storeValue(ret_addr, err);
mem().setUint8(ret_addr + 8, 0);
}
},
// func valueInvoke(v ref, args []ref) (ref, bool)
"syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => {
try {
const v = unboxValue(v_ref);
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
storeValue(ret_addr, Reflect.apply(v, undefined, args));
mem().setUint8(ret_addr + 8, 1);
} catch (err) {
storeValue(ret_addr, err);
mem().setUint8(ret_addr + 8, 0);
}
},
// func valueNew(v ref, args []ref) (ref, bool)
"syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => {
const v = unboxValue(v_ref);
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
try {
storeValue(ret_addr, Reflect.construct(v, args));
mem().setUint8(ret_addr + 8, 1);
} catch (err) {
storeValue(ret_addr, err);
mem().setUint8(ret_addr+ 8, 0);
}
},
// func valueLength(v ref) int
"syscall/js.valueLength": (v_ref) => {
return unboxValue(v_ref).length;
},
// valuePrepareString(v ref) (ref, int)
"syscall/js.valuePrepareString": (ret_addr, v_ref) => {
const s = String(unboxValue(v_ref));
const str = encoder.encode(s);
storeValue(ret_addr, str);
mem().setInt32(ret_addr + 8, str.length, true);
},
// valueLoadString(v ref, b []byte)
"syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => {
const str = unboxValue(v_ref);
loadSlice(slice_ptr, slice_len, slice_cap).set(str);
},
// func valueInstanceOf(v ref, t ref) bool
"syscall/js.valueInstanceOf": (v_ref, t_ref) => {
return unboxValue(v_ref) instanceof unboxValue(t_ref);
},
// func copyBytesToGo(dst []byte, src ref) (int, bool)
"syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => {
let num_bytes_copied_addr = ret_addr;
let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable
const dst = loadSlice(dest_addr, dest_len);
const src = unboxValue(src_ref);
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
mem().setUint8(returned_status_addr, 0); // Return "not ok" status
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
mem().setUint32(num_bytes_copied_addr, toCopy.length, true);
mem().setUint8(returned_status_addr, 1); // Return "ok" status
},
// copyBytesToJS(dst ref, src []byte) (int, bool)
// Originally copied from upstream Go project, then modified:
// https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416
"syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => {
let num_bytes_copied_addr = ret_addr;
let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable
const dst = unboxValue(dst_ref);
const src = loadSlice(src_addr, src_len);
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
mem().setUint8(returned_status_addr, 0); // Return "not ok" status
return;
}
const toCopy = src.subarray(0, dst.length);
dst.set(toCopy);
mem().setUint32(num_bytes_copied_addr, toCopy.length, true);
mem().setUint8(returned_status_addr, 1); // Return "ok" status
},
}
};
// Go 1.20 uses 'env'. Go 1.21 uses 'gojs'.
// For compatibility, we use both as long as Go 1.20 is supported.
this.importObject.env = this.importObject.gojs;
}
async run(instance) {
this._inst = instance;
this._values = [ // JS values that Go currently has references to, indexed by reference id
NaN,
0,
null,
true,
false,
global,
this,
];
this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id
this._ids = new Map(); // mapping from JS values to reference ids
this._idPool = []; // unused ids that have been garbage collected
this.exited = false; // whether the Go program has exited
this.exitCode = 0;
if (this._inst.exports._start) {
let exitPromise = new Promise((resolve, reject) => {
this._resolveExitPromise = resolve;
});
// Run program, but catch the wasmExit exception that's thrown
// to return back here.
try {
this._inst.exports._start();
} catch (e) {
if (e !== wasmExit) throw e;
}
await exitPromise;
return this.exitCode;
} else {
this._inst.exports._initialize();
}
}
_resume() {
if (this.exited) {
throw new Error("Go program has already exited");
}
try {
this._inst.exports.resume();
} catch (e) {
if (e !== wasmExit) throw e;
}
if (this.exited) {
this._resolveExitPromise();
}
}
_makeFuncWrapper(id) {
const go = this;
return function () {
const event = { id: id, this: this, args: arguments };
go._pendingEvent = event;
go._resume();
return event.result;
};
}
}
if (
global.require &&
global.require.main === module &&
global.process &&
global.process.versions &&
!global.process.versions.electron
) {
if (process.argv.length != 3) {
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
process.exit(1);
}
const go = new Go();
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => {
let exitCode = await go.run(result.instance);
process.exit(exitCode);
}).catch((err) => {
console.error(err);
process.exit(1);
});
}
})();
// wasm setup code
export default function init (path) {
return new Promise ((res) => {
const go = new Go();
const run = (obj) => {
const wasm = obj.instance;
global.wfa = wasm
go.run(wasm);
res()
}
if ('instantiateStreaming' in WebAssembly) {
WebAssembly.instantiateStreaming(fetch(path), go.importObject).then(function (obj) {
run(obj)
})
} else {
fetch(path).then(resp =>
resp.arrayBuffer()
).then(bytes =>
WebAssembly.instantiate(bytes, go.importObject).then(function (obj) {
run(obj)
})
)
}
})
}
Binary file not shown.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,4 +1,4 @@
import { requestPVE, requestAPI, goToPage, getURIData, setAppearance, setIconSrc, requestDash, getCookie } from "./utils.js"; import { requestPVE, requestAPI, goToPage, getURIData, setAppearance, setIconSrc, requestDash } from "./utils.js";
import { error, dialog } from "./dialog.js"; import { error, dialog } from "./dialog.js";
window.addEventListener("DOMContentLoaded", init); window.addEventListener("DOMContentLoaded", init);
@@ -49,7 +49,7 @@ class VolumeAction extends HTMLElement {
async setStatusLoading () { async setStatusLoading () {
const icon = document.querySelector(`img[data-volume="${this.dataset.volume}"]`); const icon = document.querySelector(`img[data-volume="${this.dataset.volume}"]`);
setIconSrc(icon, "websrc/images/status/loading.svg"); setIconSrc(icon, "images/status/loading.svg");
} }
async handleDiskDetach () { async handleDiskDetach () {
@@ -221,7 +221,7 @@ async function handleCDAdd () {
} }
}); });
const isos = await requestAPI(`/access/users/${getCookie("username")}/vm-isos`, "GET"); const isos = await requestAPI("/user/vm-isos", "GET");
const select = d.querySelector("#iso-select"); const select = d.querySelector("#iso-select");
for (const iso of isos.data) { for (const iso of isos.data) {
@@ -248,7 +248,7 @@ class NetworkAction extends HTMLElement {
async setStatusLoading () { async setStatusLoading () {
const icon = document.querySelector(`img[data-network="${this.dataset.network}"]`); const icon = document.querySelector(`img[data-network="${this.dataset.network}"]`);
setIconSrc(icon, "websrc/images/status/loading.svg"); setIconSrc(icon, "images/status/loading.svg");
} }
async handleNetworkConfig () { async handleNetworkConfig () {
@@ -277,7 +277,7 @@ class NetworkAction extends HTMLElement {
const netID = this.dataset.network; const netID = this.dataset.network;
dialog(this.template, async (result, _form) => { dialog(this.template, async (result, _form) => {
if (result === "confirm") { if (result === "confirm") {
setIconSrc(document.querySelector(`svg[data-network="${netID}"]`), "websrc/images/status/loading.svg"); setIconSrc(document.querySelector(`svg[data-network="${netID}"]`), "images/status/loading.svg");
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) {
@@ -350,7 +350,7 @@ class DeviceAction extends HTMLElement {
async setStatusLoading () { async setStatusLoading () {
const icon = document.querySelector(`img[data-device="${this.dataset.device}"]`); const icon = document.querySelector(`img[data-device="${this.dataset.device}"]`);
setIconSrc(icon, "websrc/images/status/loading.svg"); setIconSrc(icon, "images/status/loading.svg");
} }
async handleDeviceConfig () { async handleDeviceConfig () {
@@ -50,9 +50,9 @@ class ErrorDialog extends HTMLElement {
super(); super();
this.shadowRoot = this.attachShadow({ mode: "open" }); this.shadowRoot = this.attachShadow({ mode: "open" });
this.shadowRoot.innerHTML = ` this.shadowRoot.innerHTML = `
<link rel="stylesheet" href="websrc/modules/w3.css"> <link rel="stylesheet" href="modules/w3.css">
<link rel="stylesheet" href="websrc/css/style.css"> <link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="websrc/css/form.css"> <link rel="stylesheet" href="css/form.css">
<style> <style>
#errors { #errors {
margin-bottom: 0; margin-bottom: 0;
@@ -96,7 +96,7 @@ class ErrorDialog extends HTMLElement {
} }
navigator.clipboard.writeText(errors); navigator.clipboard.writeText(errors);
} }
this.dialog.close(); this.parentElement.removeChild(this);
}); });
} }
Loaded 100 of 112 files, more files were not shown because too many files have changed in this diff. Show more