31 Commits
Author SHA1 Message Date
alu d6b49f0c85 update README 2026-10-27 21:16:04 +00:00
alu b38c381cf0 remove replace for common-lib 2026-10-26 23:15:38 +00:00
alu 33766d1205 use logging middleware from common-lib
tronnet/workflows: Static Analysis -- Golang / static-go (push) Successful in 18s
2026-10-26 18:45:39 +00:00
alu a13331e5a5 minor cleanup
tronnet/workflows: Static Analysis -- Golang / static-go (push) Successful in 16s
2026-10-26 18:23:43 +00:00
alu 40dc935eea fix path for loading icons
tronnet/workflows: Code Style -- Web Components / style-web (push) Successful in 15s
2026-10-26 05:14:57 +00:00
alu 382d207e0e minor style fix 2026-10-23 22:25:42 +00:00
alu 3eca3da157 use HTTPExecuteHTMLTemplate and HTTPExecuteHTMLFragmentTemplate 2026-10-23 21:56:07 +00:00
alu 4743bcf756 update go mod 2026-10-23 21:33:23 +00:00
alu 73b58a843d re-add logging middlware
tronnet/workflows: Static Analysis -- Golang / static-go (push) Successful in 14s
2026-10-23 21:06:37 +00:00
alu e0ef1b598b update go mod 2026-10-22 21:02:18 +00:00
alu f6164b3096 fix paths to new websrc 2026-10-22 20:57:20 +00:00
alu 5c658db34e use GODEBUG=embedfollowsymlinks=1 to support symlinked web src files
tronnet/workflows: Code Style -- Web Components / style-web (push) Failing after 18s
tronnet/workflows: Static Analysis -- Golang / static-go (push) Failing after 22s
2026-10-22 20:54:23 +00:00
alu 98790dbe3b rewrite using net/http
tronnet/workflows: Static Analysis -- Golang / static-go (push) Failing after 5s
2026-10-22 19:03:37 +00:00
alu db8ed2e830 move all static assets under /websrc/* endpoint
tronnet/workflows: Static Analysis -- Golang / static-go (push) Failing after 8s
tronnet/workflows: Code Style -- Web Components / style-web (push) Successful in 34s
2026-10-20 23:27:28 +00:00
alu f3aa769fb7 adjust fuzzy alignment weights and normalize against larger of input sizes
tronnet/workflows: Code Style -- Web Components / style-web (push) Successful in 23s
2026-10-20 22:25:39 +00:00
alu b92aabb052 update ct-templates and vm-isos endpoint paths
tronnet/workflows: Code Style -- Web Components / style-web (push) Successful in 14s
2026-09-14 18:53:45 +00:00
alu 77f26a3e2c update wfa js with new gzip params 2026-09-09 17:01:09 +00:00
alu 9458840292 cleanup makefile 2026-09-08 22:54:17 +00:00
alu ed4cf064e3 add gzip type to support gziped wfa js
tronnet/workflows: Static Analysis -- Golang / static-go (push) Failing after 9s
tronnet/workflows: Code Style -- Web Components / style-web (push) Successful in 15s
2026-09-08 22:02:10 +00:00
alu c816d15b1a update wfa.wasm 2026-09-02 20:30:13 +00:00
alu 2fced6bd11 cleanup makefile 2026-09-02 20:29:20 +00:00
alu b2bb8ae69e update WFA-JS
tronnet/workflows: Code Style -- Web Components / style-web (push) Successful in 16m16s
2026-09-02 18:46:28 +00:00
alu c5067f1696 remove init hook 2026-09-02 16:25:17 +00:00
alu de8f5368a5 remove .htmlvalidateignore 2026-09-02 16:09:47 +00:00
alu 2a6641f03c update to go 1.27 2026-08-25 18:54:20 +00:00
alu a5b20519d4 minor comment cleanup
tronnet/workflows: Static Analysis -- Golang / static-go (push) Successful in 20s
2026-08-19 23:47:17 +00:00
alu e000a90456 update common lib 2026-08-19 21:12:22 +00:00
alu b5c8576ab5 update go 1.26.6 2026-08-17 19:48:16 +00:00
alu e2039a15b1 minor style fix
tronnet/workflows: Code Style -- Web Components / style-web (push) Successful in 14s
2026-08-11 22:59:46 +00:00
alu 492ae56d48 Update README.md 2026-08-10 21:38:50 +00:00
alu 033017864e Merge pull request 'Dashboard v2.0.0' (#5) from v2.0.0 into main
tronnet/workflows: Static Analysis -- Golang / static-go (push) Successful in 44s
tronnet/workflows: Code Style -- Web Components / style-web (push) Failing after 16s
Reviewed-on: #5
2026-08-10 21:10:15 +00:00
111 changed files with 995 additions and 1018 deletions

No files matched your search

+14 -15
View File
@@ -1,27 +1,26 @@
.PHONY: build build-web build-wfa-js clean clean-wfa-js ensure-dist workflow-init .PHONY: test build build-wfa clean clean-wfa clean-web
build: clean ensure-dist build-web build-wfa-js build: clean ensure-dist build-wfa
@echo "======================== Building Binary =======================" @echo "======================== Building Binary ======================="
# resolve symbolic links in web by copying it into dist/web/ # resolve symbolic links in web by copying it into dist/web/
CGO_ENABLED=0 go build -tags release -ldflags="-s -w" -v -o dist/ . GODEBUG=embedfollowsymlinks=1 CGO_ENABLED=0 go build -tags release -ldflags="-s -w" -v -o dist/ .
build-web: ensure-dist test:
cp -rL web/ dist/web/ GODEBUG=embedfollowsymlinks=1 CGO_ENABLED=0 go run .
build-wfa-js: ensure-dist clean: clean-wfa clean-web
$(MAKE) -C WFA-JS
cp -f WFA-JS/dist/* web/modules
clean: clean-wfa-js
@echo "======================== Cleaning Project ======================" @echo "======================== Cleaning Project ======================"
go clean go clean
rm -rf dist/* rm -rf dist/*
clean-wfa-js:
$(MAKE) clean -C WFA-JS
ensure-dist: ensure-dist:
mkdir -p dist mkdir -p dist
workflow-init: ensure-dist build-web clean-web:
cp -r dev_config/. .
build-wfa:
$(MAKE) -C WFA-JS
cp -f WFA-JS/dist/* app/web/modules
clean-wfa:
$(MAKE) clean -C WFA-JS
+13 -13
View File
@@ -13,19 +13,19 @@ ProxmoxAAS Dashboard provides users of a proxmox based compute on demand service
## ProxmoxAAS System Installation Overview ## ProxmoxAAS System Installation Overview
The ProxmoxAAS project is large and is split into multiple components. There are four required components, the Dashboard, API, Fabric, and access-manager-api. The instalation order should start with the Dashboard and then proceed to the other backend components. This will require some foresight into the setup process. The ProxmoxAAS project is large and is split into multiple components. There are four required components, the Dashboard, API, Fabric, and access-manager-api. The installation order should start with the Dashboard and then proceed to the other backend components. This will require some foresight into the setup process.
The supported setup is to use a reverse proxy to serve both the original Proxmox web interface and ProxmoxAAS components. It is possible other setups can work. Rather than provide specific steps to duplicate a certain setup, the steps included are intended as a guideline of steps required for proper function in most setups. Consequently, the examples provided are only to highlight key settings and do not represent complete working configurations. The instructions also assume you have your own domain name which will substitute `domain.net` in some of the configs. The supported setup is to use a reverse proxy to serve both the original Proxmox web interface and ProxmoxAAS components. It is possible other setups can work. Rather than provide specific steps to duplicate a certain setup, the steps included are intended as a guideline of steps required for proper function in most setups. Consequently, the examples provided are only to highlight key settings and do not represent complete working configurations. The instructions also assume you have your own domain name which will substitute `domain.net` in some of the configs.
We will assume different hosts for each component which are accessible by unique host names and public URL addresses. Below is a table of references in the setup instructions across all components. You will need to substitute real addresses and hostnames for these in configurations. We will assume different hosts for each component which are accessible by unique host names and public URL addresses. Below is a table of references in the setup instructions across all components. You will need to substitute real addresses and hostnames for these in configurations.
| component | internal address | external address | | component | internal address | external address | default port |
| --- | --- | --- | | --- | --- | --- | --- |
| Proxmox VE API | pve.local | pve.domain.net | | Proxmox VE API | pve.local | pve.domain.net | 8006 |
| ProxmoxAAS-Dashboard | dashboard.local | paas.domain.net | | ProxmoxAAS-Dashboard | dashboard.local | paas.domain.net | 8080 |
| ProxmoxAAS-API | api.local | paas.domain.net/api/ | | ProxmoxAAS-API | api.local | paas.domain.net/api | 8081 |
| ProxmoxAAS-Fabric | fabric.local | N/A | | ProxmoxAAS-Fabric | fabric.local | N/A | 8082 |
| access-manager-api | access.local | N/A| | access-manager-api | access.local | N/A| 8083 |
## Prerequisites - Dashboard ## Prerequisites - Dashboard
- Proxmox VE Cluster (v7.0+) - Proxmox VE Cluster (v7.0+)
@@ -47,18 +47,18 @@ After this step, the Dashboard should be available on the `ProxmoxAAS-Dashboard`
## Installation - API ## Installation - API
To install the API component, go to [ProxmoxAAS-API](https://git.tronnet.net/tronnet/ProxmoxAAS-API). This is required for the app to function. The API installation will also have steps for setting up the reverse proxy server. To install the API component, go to [ProxmoxAAS-API](https://git.tronnet.net/tronnet/ProxmoxAAS-API). This is required for the app to function.
## Installation - Fabric ## Installation - Fabric
To install the Fabric component, go to [ProxmoxAAS-Fabric](https://git.tronnet.net/tronnet/ProxmoxAAS-Fabric). This is required for the app to function. The Fabric installation will also have steps for setting up the reverse proxy server. To install the Fabric component, go to [ProxmoxAAS-Fabric](https://git.tronnet.net/tronnet/ProxmoxAAS-Fabric). This is required for the app to function.
## Installation - access-manager-api ## Installation - access-manager-api
To install the access-manager-api component, go to [access-manager-api](https://git.tronnet.net/tronnet/access-manager-api). This is required for the app to function. The access-manager-api installation will also have steps for setting up the reverse proxy server. To install the access-manager-api component, go to [access-manager-api](https://git.tronnet.net/tronnet/access-manager-api). This is required for the app to function.
## Installation - Reverse Proxy ## Installation - Reverse Proxy
1. Configure nginx or preferred reverse proxy to reverse proxy the dashboard. The configuration should include at least the following, ensuring that the configured ports are adjusted appropriately: 1. Configure nginx or preferred reverse proxy to reverse proxy the dashboard. A snippet is show for the minimal configuration assuming the default ports.
``` ```
server { server {
listen 443 ssl; listen 443 ssl;
@@ -75,4 +75,4 @@ server {
``` ```
2. Start nginx with the new configurations 2. Start nginx with the new configurations
The dashboard, API, and Fabric should be avaliable and fully functional. The PAAS suite should be avaliable and fully functional.
+1 -1
Submodule WFA-JS updated: f53f344fb3...b0aacd5f9c.
+36 -52
View File
@@ -4,71 +4,55 @@ 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"
"web" // go will complain here until the first build "proxmoxaas-dashboard/app/web"
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.LoadHTMLToGin(router, html) common.TMPL = common.LoadHTML(html)
// dynamic routes for pages and page fragments // dynamic routes for pages and page fragments
router.GET("/", routes.HandleGETIndex) router.HandleFunc("GET /", routes.HandleGETIndex)
router.GET("/index", routes.HandleGETIndex) router.HandleFunc("GET /index", routes.HandleGETIndex)
router.GET("/index/instances", routes.HandleGETInstancesFragment) router.HandleFunc("GET /index/instances", routes.HandleGETInstancesFragment)
router.GET("/account", routes.HandleGETAccount) router.HandleFunc("GET /account", routes.HandleGETAccount)
router.GET("/config", routes.HandleGETConfig) router.HandleFunc("GET /config", routes.HandleGETConfig)
router.GET("/config/volumes", routes.HandleGETConfigVolumesFragment) router.HandleFunc("GET /config/volumes", routes.HandleGETConfigVolumesFragment)
router.GET("/config/nets", routes.HandleGETConfigNetsFragment) router.HandleFunc("GET /config/nets", routes.HandleGETConfigNetsFragment)
router.GET("/config/devices", routes.HandleGETConfigDevicesFragment) router.HandleFunc("GET /config/devices", routes.HandleGETConfigDevicesFragment)
router.GET("/config/boot", routes.HandleGETConfigBootFragment) router.HandleFunc("GET /config/boot", routes.HandleGETConfigBootFragment)
router.GET("/backups", routes.HandleGETBackups) router.HandleFunc("GET /backups", routes.HandleGETBackups)
router.GET("/backups/backups", routes.HandleGETBackupsFragment) router.HandleFunc("GET /backups/backups", routes.HandleGETBackupsFragment)
router.GET("/login", routes.HandleGETLogin) router.HandleFunc("GET /login", routes.HandleGETLogin)
router.GET("/settings", routes.HandleGETSettings) router.HandleFunc("GET /settings", routes.HandleGETSettings)
// run on all interfaces with port server := paas.LoggingMiddleware(router)
log.Fatal("[ERR ] starting gin router: ", router.Run(fmt.Sprintf("0.0.0.0:%d", common.Global.Port)))
}
// setup static resources under web (css, images, modules, scripts) log.Printf("[INFO] starting server listening on 0.0.0.0:%d", common.Global.Port)
func ServeStatic(router *gin.Engine, m *minify.M) { log.Fatalf("[ERR ] starting gin router: %s", http.ListenAndServe(fmt.Sprintf("0.0.0.0:%d", common.Global.Port), server))
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,6 +11,7 @@ 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
} }
@@ -44,6 +45,11 @@ 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,6 +15,7 @@ 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
} }
@@ -48,6 +49,11 @@ 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,
+42 -25
View File
@@ -16,10 +16,9 @@ import (
"strconv" "strconv"
"strings" "strings"
"github.com/gin-gonic/gin"
"github.com/tdewolff/minify/v2" "github.com/tdewolff/minify/v2"
paas "proxmoxaas-common-lib" paas "proxmoxaas-dashboard/proxmoxaas-common-lib"
) )
// get config file from configPath // get config file from configPath
@@ -103,9 +102,8 @@ func MinifyStatic(m *minify.M, files embed.FS) map[string]StaticFile {
} }
} }
func LoadHTMLToGin(engine *gin.Engine, html map[string]StaticFile) *template.Template { func LoadHTML(html map[string]StaticFile) *template.Template {
root := template.New("") funcMap := template.FuncMap{
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()
@@ -133,9 +131,11 @@ func LoadHTMLToGin(engine *gin.Engine, html map[string]StaticFile) *template.Tem
return dict, nil return dict, nil
}, },
} }
tmpl := template.Must(root, LoadAndAddToRoot(engine.FuncMap, root, html)) root := template.New("").Funcs(funcMap)
engine.SetHTMLTemplate(root) if err := LoadAndAddToRoot(funcMap, root, html); err != nil {
return tmpl log.Fatal(err)
}
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(c *gin.Context, err error) { func HandleNonFatalError(w http.ResponseWriter, err error) {
log.Printf("[WARN] encountered a non-fatal error: %s", err.Error()) log.Printf("[WARN] encountered a non-fatal error: %s", err.Error())
c.Status(http.StatusInternalServerError) w.WriteHeader(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,25 +221,41 @@ func RequestGetAPI(path string, auth *paas.Auth, body any) (*http.Response, int,
return response, response.StatusCode, nil return response, response.StatusCode, nil
} }
func GetAuthFromRequest(c *gin.Context) (paas.Auth, error) { func GetAuthFromRequest(r *http.Request) (paas.Auth, error) {
_, errAuth := c.Cookie("auth") _, errAuth := r.Cookie("auth")
username, errUsername := c.Cookie("username") username, errUsername := r.Cookie("username")
token, errToken := c.Cookie("PVEAuthCookie") token, errToken := r.Cookie("PVEAuthCookie")
csrf, errCSRF := c.Cookie("CSRFPreventionToken") csrf, errCSRF := r.Cookie("CSRFPreventionToken")
access, errAccess := c.Cookie("PAASAccessManagerTicket") access, errAccess := r.Cookie("PAASAccessManagerTicket")
if errUsername != nil || errAuth != nil || errToken != nil || errCSRF != nil || errAccess != nil {
return paas.Auth{}, fmt.Errorf("error occured getting user cookies: (auth: %s, token: %s, csrf: %s)", errAuth, errToken, errCSRF) if errUsername != nil || errAuth != nil || errToken != nil ||
} else { errCSRF != nil || errAccess != nil {
return paas.Auth{Username: username, Token: token, CSRF: csrf, AccessManagerTicket: access}, nil return paas.Auth{}, fmt.Errorf(
"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(c *gin.Context) (paas.InstancePath, error) { func GetInstancePathFromRequest(r *http.Request) (paas.InstancePath, error) {
req_node := c.Query("node") query := r.URL.Query()
req_type := c.Query("type")
req_vmid := c.Query("vmid") req_node := query.Get("node")
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("request missing required values: (node: %s, type: %s, vmid: %s)", req_node, req_type, req_vmid) return paas.InstancePath{}, fmt.Errorf(
"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)
@@ -252,6 +268,7 @@ func GetInstancePathFromRequest(c *gin.Context) (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,11 +3,10 @@ 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"
) )
@@ -40,29 +39,30 @@ var Green = color.RGB{
B: 0, B: 0,
} }
func HandleGETAccount(c *gin.Context) { func HandleGETAccount(w http.ResponseWriter, r *http.Request) {
auth, err := common.GetAuthFromRequest(c) auth, err := common.GetAuthFromRequest(r)
if err == nil { if err == nil {
user, err := GetUserBasic(auth) user, err := GetUserBasic(auth)
if err != nil { if err != nil {
common.HandleNonFatalError(c, err) common.HandleNonFatalError(w, err)
return return
} }
pools, err := GetUserPools(auth) pools, err := GetUserPools(auth)
if err != nil { if err != nil {
common.HandleNonFatalError(c, err) common.HandleNonFatalError(w, err)
return return
} }
c.HTML(http.StatusOK, "html/account.html", gin.H{ paas.HTTPExecuteHTMLTemplate(w, common.TMPL, "html/account.html", map[string]any{
"global": common.Global, "global": common.Global,
"page": "account", "page": "account",
"user": user, "user": user,
"pools": pools, "pools": pools,
}) })
} else { } else {
c.Redirect(http.StatusFound, "/login") // if user is not authed, redirect user to login page http.Redirect(w, r, "/login", http.StatusFound) // if user is not authed, redirect user to login page
return
} }
} }
+21 -23
View File
@@ -3,11 +3,10 @@ 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"
) )
@@ -20,61 +19,60 @@ type InstanceBackup struct {
TimeFormatted string TimeFormatted string
} }
func HandleGETBackups(c *gin.Context) { func HandleGETBackups(w http.ResponseWriter, r *http.Request) {
auth, err := common.GetAuthFromRequest(c) auth, err := common.GetAuthFromRequest(r)
if err == nil { if err == nil {
vm_path, err := common.GetInstancePathFromRequest(c) vm_path, err := common.GetInstancePathFromRequest(r)
if err != nil { if err != nil {
common.HandleNonFatalError(c, err) common.HandleNonFatalError(w, err)
return return
} }
backups, err := GetInstanceBackups(vm_path, auth) backups, err := GetInstanceBackups(vm_path, auth)
if err != nil { if err != nil {
common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance backups: %s", err.Error())) common.HandleNonFatalError(w, 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(c, fmt.Errorf("error encountered getting instance config: %s", err.Error())) common.HandleNonFatalError(w, fmt.Errorf("error encountered getting instance config: %s", err.Error()))
return
} }
c.HTML(http.StatusOK, "html/backups.html", gin.H{ paas.HTTPExecuteHTMLTemplate(w, common.TMPL, "html/backups.html", map[string]any{
"global": common.Global, "global": common.Global,
"page": "backups", "page": "backups",
"backups": backups, "backups": backups,
"config": config, "config": config,
}) })
} else { } else {
c.Redirect(http.StatusFound, "/login") http.Redirect(w, r, "/login", http.StatusFound)
return
} }
} }
func HandleGETBackupsFragment(c *gin.Context) { func HandleGETBackupsFragment(w http.ResponseWriter, r *http.Request) {
auth, err := common.GetAuthFromRequest(c) auth, err := common.GetAuthFromRequest(r)
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(c) vm_path, err := common.GetInstancePathFromRequest(r)
if err != nil { if err != nil {
common.HandleNonFatalError(c, err) common.HandleNonFatalError(w, err)
return return
} }
backups, err := GetInstanceBackups(vm_path, auth) backups, err := GetInstanceBackups(vm_path, auth)
if err != nil { if err != nil {
common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance backups: %s", err.Error())) common.HandleNonFatalError(w, fmt.Errorf("error encountered getting instance backups: %s", err.Error()))
return
} }
c.Header("Content-Type", "text/plain") paas.HTTPExecuteHTMLFragmentTemplate(w, common.TMPL, "html/backups-backups.go.tmpl", map[string]any{
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
c.Status(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
return
} }
} }
+51 -65
View File
@@ -3,12 +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"
"slices" "slices"
"sort" "sort"
"github.com/gin-gonic/gin"
"github.com/go-viper/mapstructure/v2" "github.com/go-viper/mapstructure/v2"
) )
@@ -31,24 +30,26 @@ type PoolConfig struct {
} }
} }
func HandleGETConfig(c *gin.Context) { func HandleGETConfig(w http.ResponseWriter, r *http.Request) {
auth, err := common.GetAuthFromRequest(c) auth, err := common.GetAuthFromRequest(r)
if err == nil { if err == nil {
vm_path, err := common.GetInstancePathFromRequest(c) vm_path, err := common.GetInstancePathFromRequest(r)
if err != nil { if err != nil {
common.HandleNonFatalError(c, err) common.HandleNonFatalError(w, err)
return return
} }
config, err := GetInstanceConfig(vm_path, auth) config, err := GetInstanceConfig(vm_path, auth)
if err != nil { if err != nil {
common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance config: %s", err.Error())) common.HandleNonFatalError(w, 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(c, fmt.Errorf("error encountered getting proctypes: %s", err.Error())) common.HandleNonFatalError(w, fmt.Errorf("error encountered getting proctypes: %s", err.Error()))
return
} }
} }
for i, cpu := range config.ProctypeSelect.Options { for i, cpu := range config.ProctypeSelect.Options {
@@ -57,125 +58,110 @@ func HandleGETConfig(c *gin.Context) {
} }
} }
c.HTML(http.StatusOK, "html/config.html", gin.H{ paas.HTTPExecuteHTMLTemplate(w, common.TMPL, "html/config.html", map[string]any{
"global": common.Global, "global": common.Global,
"page": "config", "page": "config",
"config": config, "config": config,
}) })
} else { } else {
c.Redirect(http.StatusFound, "/login") http.Redirect(w, r, "/login", http.StatusFound)
return
} }
} }
func HandleGETConfigVolumesFragment(c *gin.Context) { func HandleGETConfigVolumesFragment(w http.ResponseWriter, r *http.Request) {
auth, err := common.GetAuthFromRequest(c) auth, err := common.GetAuthFromRequest(r)
if err == nil { if err == nil {
vm_path, err := common.GetInstancePathFromRequest(c) vm_path, err := common.GetInstancePathFromRequest(r)
if err != nil { if err != nil {
common.HandleNonFatalError(c, err) common.HandleNonFatalError(w, err)
return return
} }
config, err := GetInstanceConfig(vm_path, auth) config, err := GetInstanceConfig(vm_path, auth)
if err != nil { if err != nil {
common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance config: %s", err.Error())) common.HandleNonFatalError(w, fmt.Errorf("error encountered getting instance config: %s", err.Error()))
return
} }
c.Header("Content-Type", "text/plain") paas.HTTPExecuteHTMLFragmentTemplate(w, common.TMPL, "html/config-volumes.go.tmpl", map[string]any{
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 {
c.Status(http.StatusUnauthorized) w.WriteHeader(http.StatusUnauthorized)
return
} }
} }
func HandleGETConfigNetsFragment(c *gin.Context) { func HandleGETConfigNetsFragment(w http.ResponseWriter, r *http.Request) {
auth, err := common.GetAuthFromRequest(c) auth, err := common.GetAuthFromRequest(r)
if err == nil { if err == nil {
vm_path, err := common.GetInstancePathFromRequest(c) vm_path, err := common.GetInstancePathFromRequest(r)
if err != nil { if err != nil {
common.HandleNonFatalError(c, err) common.HandleNonFatalError(w, err)
return return
} }
config, err := GetInstanceConfig(vm_path, auth) config, err := GetInstanceConfig(vm_path, auth)
if err != nil { if err != nil {
common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance config: %s", err.Error())) common.HandleNonFatalError(w, fmt.Errorf("error encountered getting instance config: %s", err.Error()))
return
} }
c.Header("Content-Type", "text/plain") paas.HTTPExecuteHTMLFragmentTemplate(w, common.TMPL, "html/config-nets.go.tmpl", map[string]any{
err = common.TMPL.ExecuteTemplate(c.Writer, "html/config-nets.go.tmpl", gin.H{
"config": config, "config": config,
}) })
if err != nil { } else { // return 401
c.Status(http.StatusInternalServerError) w.WriteHeader(http.StatusUnauthorized)
} else { return
c.Status(http.StatusOK)
}
} else {
c.Status(http.StatusUnauthorized)
} }
} }
func HandleGETConfigDevicesFragment(c *gin.Context) { func HandleGETConfigDevicesFragment(w http.ResponseWriter, r *http.Request) {
auth, err := common.GetAuthFromRequest(c) auth, err := common.GetAuthFromRequest(r)
if err == nil { if err == nil {
vm_path, err := common.GetInstancePathFromRequest(c) vm_path, err := common.GetInstancePathFromRequest(r)
if err != nil { if err != nil {
common.HandleNonFatalError(c, err) common.HandleNonFatalError(w, err)
return return
} }
config, err := GetInstanceConfig(vm_path, auth) config, err := GetInstanceConfig(vm_path, auth)
if err != nil { if err != nil {
common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance config: %s", err.Error())) common.HandleNonFatalError(w, fmt.Errorf("error encountered getting instance config: %s", err.Error()))
return
} }
c.Header("Content-Type", "text/plain") paas.HTTPExecuteHTMLFragmentTemplate(w, common.TMPL, "html/config-devices.go.tmpl", map[string]any{
err = common.TMPL.ExecuteTemplate(c.Writer, "html/config-devices.go.tmpl", gin.H{
"config": config, "config": config,
}) })
if err != nil { } else { // return 401
c.Status(http.StatusInternalServerError) w.WriteHeader(http.StatusUnauthorized)
} else { return
c.Status(http.StatusOK)
}
} else {
c.Status(http.StatusUnauthorized)
} }
} }
func HandleGETConfigBootFragment(c *gin.Context) { func HandleGETConfigBootFragment(w http.ResponseWriter, r *http.Request) {
auth, err := common.GetAuthFromRequest(c) auth, err := common.GetAuthFromRequest(r)
if err == nil { if err == nil {
vm_path, err := common.GetInstancePathFromRequest(c) vm_path, err := common.GetInstancePathFromRequest(r)
if err != nil { if err != nil {
common.HandleNonFatalError(c, err) common.HandleNonFatalError(w, err)
return return
} }
config, err := GetInstanceConfig(vm_path, auth) config, err := GetInstanceConfig(vm_path, auth)
if err != nil { if err != nil {
common.HandleNonFatalError(c, fmt.Errorf("error encountered getting instance config: %s", err.Error())) common.HandleNonFatalError(w, fmt.Errorf("error encountered getting instance config: %s", err.Error()))
return
} }
c.Header("Content-Type", "text/plain") paas.HTTPExecuteHTMLFragmentTemplate(w, common.TMPL, "html/config-boot.go.tmpl", map[string]any{
err = common.TMPL.ExecuteTemplate(c.Writer, "html/config-boot.go.tmpl", gin.H{
"config": config, "config": config,
}) })
if err != nil { } else { // return 401
c.Status(http.StatusInternalServerError) w.WriteHeader(http.StatusUnauthorized)
} else { return
c.Status(http.StatusOK)
}
} else {
c.Status(http.StatusUnauthorized)
} }
} }
+16 -20
View File
@@ -3,11 +3,10 @@ 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"
) )
@@ -45,46 +44,43 @@ type InstanceStatus struct {
Status string Status string
} }
func HandleGETIndex(c *gin.Context) { func HandleGETIndex(w http.ResponseWriter, r *http.Request) {
auth, err := common.GetAuthFromRequest(c) auth, err := common.GetAuthFromRequest(r)
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(c, err) common.HandleNonFatalError(w, err)
return return
} }
page := gin.H{ page := map[string]any{
"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
c.Redirect(http.StatusFound, "/login") // if user is not authed, redirect user to login page http.Redirect(w, r, "/login", http.StatusFound)
return
} }
} }
func HandleGETInstancesFragment(c *gin.Context) { func HandleGETInstancesFragment(w http.ResponseWriter, r *http.Request) {
auth, err := common.GetAuthFromRequest(c) auth, err := common.GetAuthFromRequest(r)
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(c, err) common.HandleNonFatalError(w, err)
return return
} }
c.Header("Content-Type", "text/plain")
err = common.TMPL.ExecuteTemplate(c.Writer, "html/index-instances.go.tmpl", gin.H{ paas.HTTPExecuteHTMLFragmentTemplate(w, common.TMPL, "html/index-instances.go.tmpl", map[string]any{
"instances": instances, "instances": instances,
}) })
if err != nil {
c.Status(http.StatusInternalServerError)
} else {
c.Status(http.StatusOK)
}
} else { // return 401 } else { // return 401
c.Status(http.StatusUnauthorized) w.WriteHeader(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) {
+5 -4
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,10 +41,11 @@ func GetLoginRealms() ([]Realm, error) {
return realms, nil return realms, nil
} }
func HandleGETLogin(c *gin.Context) { func HandleGETLogin(w http.ResponseWriter, r *http.Request) {
realms, err := GetLoginRealms() realms, err := GetLoginRealms()
if err != nil { if err != nil {
common.HandleNonFatalError(c, err) common.HandleNonFatalError(w, err)
return
} }
sel := common.Select{ sel := common.Select{
@@ -60,7 +61,7 @@ func HandleGETLogin(c *gin.Context) {
}) })
} }
c.HTML(http.StatusOK, "html/login.html", gin.H{ paas.HTTPExecuteHTMLTemplate(w, common.TMPL, "html/login.html", map[string]any{
"global": common.Global, "global": common.Global,
"page": "login", "page": "login",
"realms": sel, "realms": sel,
+12 -6
View File
@@ -3,18 +3,24 @@ 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(c *gin.Context) { func HandleGETSettings(w http.ResponseWriter, r *http.Request) {
_, err := common.GetAuthFromRequest(c) auth, err := common.GetAuthFromRequest(r)
if err == nil { if err == nil {
c.HTML(http.StatusOK, "html/settings.html", gin.H{ _, err := GetUserBasic(auth)
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 {
c.Redirect(http.StatusFound, "/login") http.Redirect(w, r, "/login", http.StatusFound)
return
} }
} }
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) { @media screen and (width <= 601px) and (width >= 440px) {
.hide-large {display: none !important;} .hide-large {display: none !important;}
.hide-medium {display:none !important} .hide-medium {display:none !important}
.hide-small {display:none !important} .hide-small {display:none !important}
+2 -7
View File
@@ -5,16 +5,11 @@ import (
) )
//go:embed css/* //go:embed css/*
var CSS_fs embed.FS
//go:embed images/* //go:embed images/*
var Images_fs embed.FS //go:embed images/*/*/*
//go:embed modules/* //go:embed modules/*
var Modules_fs embed.FS
//go:embed scripts/* //go:embed scripts/*
var Scripts_fs embed.FS var Static 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="scripts/account.js" type="module"></script> <script src="websrc/scripts/account.js" type="module"></script>
<link rel="modulepreload" href="scripts/utils.js"> <link rel="modulepreload" href="websrc/scripts/utils.js">
<link rel="modulepreload" href="scripts/dialog.js"> <link rel="modulepreload" href="websrc/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="modules/w3.css"> <link rel="stylesheet" href="websrc/modules/w3.css">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<link rel="stylesheet" href="css/form.css"> <link rel="stylesheet" href="websrc/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
@@ -65,7 +65,7 @@
</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="scripts/backups.js" type="module"></script> <script src="websrc/scripts/backups.js" type="module"></script>
<link rel="modulepreload" href="scripts/utils.js"> <link rel="modulepreload" href="websrc/scripts/utils.js">
<link rel="modulepreload" href="scripts/dialog.js"> <link rel="modulepreload" href="websrc/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="scripts/config.js" type="module"></script> <script src="websrc/scripts/config.js" type="module"></script>
<script src="scripts/draggable.js" type="module"></script> <script src="websrc/scripts/draggable.js" type="module"></script>
<script src="modules/Sortable.min.js"></script> <script src="websrc/modules/Sortable.min.js"></script>
<link rel="modulepreload" href="scripts/utils.js"> <link rel="modulepreload" href="websrc/scripts/utils.js">
<link rel="modulepreload" href="scripts/dialog.js"> <link rel="modulepreload" href="websrc/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="scripts/index.js" type="module"></script> <script src="websrc/scripts/index.js" type="module"></script>
<script src="modules/wfa.js" type="module"></script> <script src="websrc/modules/wfa.js" type="module"></script>
<link rel="modulepreload" href="scripts/utils.js"> <link rel="modulepreload" href="websrc/scripts/utils.js">
<link rel="modulepreload" href="scripts/dialog.js"> <link rel="modulepreload" href="websrc/scripts/dialog.js">
<link rel="modulepreload" href="scripts/clientsync.js"> <link rel="modulepreload" href="websrc/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="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="websrc/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="images/actions/instance/add.svg#symb"> <img class="small" style="height: 1lh; width: 1lh;" alt="Create Instance" aria-label="Create Instance" src="websrc/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">
@@ -2,9 +2,9 @@
<html lang="en"> <html lang="en">
<head> <head>
{{template "head" .}} {{template "head" .}}
<script src="scripts/login.js" type="module"></script> <script src="websrc/scripts/login.js" type="module"></script>
<link rel="modulepreload" href="scripts/utils.js"> <link rel="modulepreload" href="websrc/scripts/utils.js">
<link rel="modulepreload" href="scripts/dialog.js"> <link rel="modulepreload" href="websrc/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="scripts/settings.js" type="module"></script> <script src="websrc/scripts/settings.js" type="module"></script>
<link rel="modulepreload" href="scripts/utils.js"> <link rel="modulepreload" href="websrc/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.
+604
View File
@@ -0,0 +1,604 @@
// 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)
})
)
}
})
}
BIN
View File
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 } from "./utils.js"; import { requestPVE, requestAPI, goToPage, getURIData, setAppearance, setIconSrc, requestDash, getCookie } 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, "images/status/loading.svg"); setIconSrc(icon, "websrc/images/status/loading.svg");
} }
async handleDiskDetach () { async handleDiskDetach () {
@@ -221,7 +221,7 @@ async function handleCDAdd () {
} }
}); });
const isos = await requestAPI("/user/vm-isos", "GET"); const isos = await requestAPI(`/access/users/${getCookie("username")}/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, "images/status/loading.svg"); setIconSrc(icon, "websrc/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}"]`), "images/status/loading.svg"); setIconSrc(document.querySelector(`svg[data-network="${netID}"]`), "websrc/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, "images/status/loading.svg"); setIconSrc(icon, "websrc/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="modules/w3.css"> <link rel="stylesheet" href="websrc/modules/w3.css">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<link rel="stylesheet" href="css/form.css"> <link rel="stylesheet" href="websrc/css/form.css">
<style> <style>
#errors { #errors {
margin-bottom: 0; margin-bottom: 0;
@@ -1,5 +1,5 @@
const blank = document.createElement("img"); const blank = document.createElement("img");
blank.src = "images/common/blank.png"; // for whatever reason an svg does NOT work here blank.src = "websrc/images/common/blank.png"; // for whatever reason an svg does NOT work here
class DraggableContainer extends HTMLElement { class DraggableContainer extends HTMLElement {
shadowRoot = null; shadowRoot = null;
@@ -1,4 +1,4 @@
import { requestPVE, requestAPI, setAppearance, getSetting, requestDash, setIconSrc, setIconAlt } from "./utils.js"; import { requestPVE, requestAPI, setAppearance, getSetting, requestDash, setIconSrc, setIconAlt, getCookie } from "./utils.js";
import { 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";
@@ -10,7 +10,7 @@ var wfa;
async function init () { async function init () {
setAppearance(); setAppearance();
wfa = await wfaInit("modules/wfa.wasm"); wfa = await wfaInit("websrc/modules/wfa.wasm.gz");
initInstances(); initInstances();
document.querySelector("#instance-add").addEventListener("click", handleInstanceAddButton); document.querySelector("#instance-add").addEventListener("click", handleInstanceAddButton);
@@ -152,9 +152,9 @@ class InstanceCard extends HTMLElement {
this.status = "loading"; this.status = "loading";
const statusicon = this.shadowRoot.querySelector("#status"); const statusicon = this.shadowRoot.querySelector("#status");
const powerbtn = this.shadowRoot.querySelector("#power-btn"); const powerbtn = this.shadowRoot.querySelector("#power-btn");
setIconSrc(statusicon, "images/status/loading.svg"); setIconSrc(statusicon, "websrc/images/status/loading.svg");
setIconAlt(statusicon, "instance is loading"); setIconAlt(statusicon, "websrc/instance is loading");
setIconSrc(powerbtn, "images/status/loading.svg"); setIconSrc(powerbtn, "websrc/images/status/loading.svg");
setIconAlt(powerbtn, ""); setIconAlt(powerbtn, "");
} }
@@ -268,15 +268,16 @@ function sortInstances () {
else if (searchCriteria === "fuzzy") { else if (searchCriteria === "fuzzy") {
const penalties = { const penalties = {
m: 0, m: 0,
x: 1, x: 2,
o: 0, o: 3,
e: 1 e: 1
}; };
criteria = (item, query) => { criteria = (item, query) => {
// lower is better // lower is better
const { score, CIGAR } = wfa.wfAlign(query, item, penalties, true); const { score, CIGAR } = wfa.wfAlign(query, item, penalties, true);
const alignment = wfa.DecodeCIGAR(CIGAR); const alignment = wfa.DecodeCIGAR(CIGAR);
return { score: score / item.length, alignment }; const maxlength = Math.max(item.length, query.length);
return { score: score / maxlength, alignment };
}; };
} }
@@ -418,7 +419,7 @@ async function handleInstanceAddButton () {
// setup templateImage depending on selected image storage // setup templateImage depending on selected image storage
const templateImage = d.querySelector("#template-image"); const templateImage = d.querySelector("#template-image");
// add template images to selector // add template images to selector
const templates = await requestAPI("/user/ct-templates", "GET"); const templates = await requestAPI(`/access/users/${getCookie("username")}/ct-templates`, "GET");
for (const template of templates.data) { for (const template of templates.data) {
templateImage.append(new Option(template.name, template.volid)); templateImage.append(new Option(template.name, template.volid));
} }
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -1,8 +1,8 @@
{{define "backup-card"}} {{define "backup-card"}}
<backup-card data-volid="{{.Volid}}"> <backup-card data-volid="{{.Volid}}">
<template shadowrootmode="open"> <template shadowrootmode="open">
<link rel="stylesheet" href="modules/w3.css"> <link rel="stylesheet" href="websrc/modules/w3.css">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<style> <style>
* { * {
margin: 0; margin: 0;
@@ -19,15 +19,15 @@
<p class="w3-col l6 m6 hide-small">{{.Notes}}</p> <p class="w3-col l6 m6 hide-small">{{.Notes}}</p>
<p class="w3-col l2 hide-medium">{{.SizeFormatted}}</p> <p class="w3-col l2 hide-medium">{{.SizeFormatted}}</p>
<div class="w3-col l2 m2 s4 flex row nowrap" style="height: 1lh;"> <div class="w3-col l2 m2 s4 flex row nowrap" style="height: 1lh;">
<img id="edit-btn" class="clickable" alt="change notes" src="images/actions/backups/config.svg#symb"> <img id="edit-btn" class="clickable" alt="change notes" src="websrc/images/actions/backups/config.svg#symb">
<img id="delete-btn" class="clickable" alt="delete backup" role="button" tabindex=0 src="images/actions/backups/delete-active.svg#symb"> <img id="delete-btn" class="clickable" alt="delete backup" role="button" tabindex=0 src="websrc/images/actions/backups/delete-active.svg#symb">
<img id="restore-btn" class="clickable" alt="restore from backup" role="button" tabindex=0 src="images/actions/backups/restore.svg#symb"> <img id="restore-btn" class="clickable" alt="restore from backup" role="button" tabindex=0 src="websrc/images/actions/backups/restore.svg#symb">
</div> </div>
</div> </div>
<template id="edit-dialog"> <template id="edit-dialog">
<link rel="stylesheet" href="modules/w3.css"> <link rel="stylesheet" href="websrc/modules/w3.css">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<link rel="stylesheet" href="css/form.css"> <link rel="stylesheet" href="websrc/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">
Edit Backup Edit Backup
@@ -45,9 +45,9 @@
</dialog> </dialog>
</template> </template>
<template id="delete-dialog"> <template id="delete-dialog">
<link rel="stylesheet" href="modules/w3.css"> <link rel="stylesheet" href="websrc/modules/w3.css">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<link rel="stylesheet" href="css/form.css"> <link rel="stylesheet" href="websrc/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">
Delete Backup Delete Backup
@@ -66,9 +66,9 @@
</dialog> </dialog>
</template> </template>
<template id="restore-dialog"> <template id="restore-dialog">
<link rel="stylesheet" href="modules/w3.css"> <link rel="stylesheet" href="websrc/modules/w3.css">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<link rel="stylesheet" href="css/form.css"> <link rel="stylesheet" href="websrc/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">
Restore From Backup? Restore From Backup?
@@ -96,12 +96,12 @@
{{define "backups-add-backup"}} {{define "backups-add-backup"}}
<button type="button" id="backup-add" class="w3-button" aria-label="Create Backup"> <button type="button" id="backup-add" class="w3-button" aria-label="Create Backup">
<span class="large" style="margin: 0;">Create Backup</span> <span class="large" style="margin: 0;">Create Backup</span>
<img class="small" role="img" style="height: 1lh; width: 1lh;" alt="Create Backup" src="images/actions/network/add.svg#symb"> <img class="small" role="img" style="height: 1lh; width: 1lh;" alt="Create Backup" src="websrc/images/actions/network/add.svg#symb">
</button> </button>
<template id="create-backup-dialog"> <template id="create-backup-dialog">
<link rel="stylesheet" href="modules/w3.css"> <link rel="stylesheet" href="websrc/modules/w3.css">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<link rel="stylesheet" href="css/form.css"> <link rel="stylesheet" href="websrc/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">
Create Backup Create Backup
@@ -1,21 +1,21 @@
{{/* <head> common across all pages*/}} {{/* <head> common across all pages */}}
{{define "head"}} {{define "head"}}
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>{{.global.Organization}} - dashboard</title> <title>{{.global.Organization}} - dashboard</title>
<link rel="icon" href="images/favicon.svg" sizes="any" type="image/svg+xml"> <link rel="icon" href="websrc/images/favicon.svg" sizes="any" type="image/svg+xml">
<link rel="stylesheet" href="modules/w3.css"> <link rel="stylesheet" href="websrc/modules/w3.css">
<link rel="stylesheet" href="websrc/css/style.css">
<link rel="stylesheet" href="websrc/css/nav.css">
<link rel="stylesheet" href="websrc/css/form.css">
<script> <script>
window.PVE = "{{.global.PVE}}"; window.PVE = "{{.global.PVE}}";
window.API = "{{.global.API}}"; window.API = "{{.global.API}}";
window.DASH = "{{.global.DASH}}"; window.DASH = "{{.global.DASH}}";
</script> </script>
<link rel="stylesheet" href="css/style.css">
<link rel="stylesheet" href="css/nav.css">
<link rel="stylesheet" href="css/form.css">
{{end}} {{end}}
{{/* <header> common across all pages*/}} {{/* <header> common across all pages */}}
{{define "header"}} {{define "header"}}
<header> <header>
<h1>{{.global.Organization}}</h1> <h1>{{.global.Organization}}</h1>
@@ -1,26 +1,26 @@
{{define "proctype-input"}} {{define "proctype-input"}}
<img alt="CPU Type" src="images/resources/cpu.svg#symb"> <img alt="CPU Type" src="websrc/images/resources/cpu.svg#symb">
<label for="proctype">CPU Type</label> <label for="proctype">CPU Type</label>
{{template "select" .}} {{template "select" .}}
<div></div> <div></div>
{{end}} {{end}}
{{define "cores-input"}} {{define "cores-input"}}
<img alt="CPU Amount" src="images/resources/cpu.svg#symb"> <img alt="CPU Amount" src="websrc/images/resources/cpu.svg#symb">
<label for="cores">CPU Amount</label> <label for="cores">CPU Amount</label>
<input id="cores" name="cores" class="w3-input w3-border" type="number" required value="{{.}}"> <input id="cores" name="cores" class="w3-input w3-border" type="number" required value="{{.}}">
<p>Cores</p> <p>Cores</p>
{{end}} {{end}}
{{define "memory-input"}} {{define "memory-input"}}
<img alt="Memory Amount" src="images/resources/ram.svg#symb"> <img alt="Memory Amount" src="websrc/images/resources/ram.svg#symb">
<label for="ram">Memory</label> <label for="ram">Memory</label>
<input id="ram" name="ram" class="w3-input w3-border" type="number" required value="{{.}}"> <input id="ram" name="ram" class="w3-input w3-border" type="number" required value="{{.}}">
<p>MiB</p> <p>MiB</p>
{{end}} {{end}}
{{define "swap-input"}} {{define "swap-input"}}
<img alt="Swap Amount" src="images/resources/swap.svg#symb"> <img alt="Swap Amount" src="websrc/images/resources/swap.svg#symb">
<label for="swap">Swap</label> <label for="swap">Swap</label>
<input id="swap" name="swap" class="w3-input w3-border" type="number" required value="{{.}}"> <input id="swap" name="swap" class="w3-input w3-border" type="number" required value="{{.}}">
<p>MiB</p> <p>MiB</p>
@@ -46,7 +46,7 @@
{{define "volumes-add-disk"}} {{define "volumes-add-disk"}}
<button type="button" id="disk-add" class="w3-button" aria-label="Add New Disk"> <button type="button" id="disk-add" class="w3-button" aria-label="Add New Disk">
<span class="large" style="margin: 0;">Add Disk</span> <span class="large" style="margin: 0;">Add Disk</span>
<img class="small" style="height: 1lh; width: 1lh;" alt="Add New Disk" src="images/actions/disk/add-disk.svg#symb"> <img class="small" style="height: 1lh; width: 1lh;" alt="Add New Disk" src="websrc/images/actions/disk/add-disk.svg#symb">
</button> </button>
<template id="add-disk-dialog"> <template id="add-disk-dialog">
<dialog class="w3-container w3-card w3-border-0"> <dialog class="w3-container w3-card w3-border-0">
@@ -75,7 +75,7 @@
{{define "volumes-add-cd"}} {{define "volumes-add-cd"}}
<button type="button" id="cd-add" class="w3-button" aria-label="Add New CD"> <button type="button" id="cd-add" class="w3-button" aria-label="Add New CD">
<span class="large" style="margin: 0;">Mount CD</span> <span class="large" style="margin: 0;">Mount CD</span>
<img class="small" style="height: 1lh; width: 1lh;" alt="Add New CDROM" src="images/actions/disk/add-cd.svg#symb"> <img class="small" style="height: 1lh; width: 1lh;" alt="Add New CDROM" src="websrc/images/actions/disk/add-cd.svg#symb">
</button> </button>
<template id="add-cd-dialog"> <template id="add-cd-dialog">
<dialog class="w3-container w3-card w3-border-0"> <dialog class="w3-container w3-card w3-border-0">
@@ -97,7 +97,7 @@
{{end}} {{end}}
{{define "volume-rootfs"}} {{define "volume-rootfs"}}
<img data-volume={{.Name}} alt="Drive" src="images/resources/drive.svg#symb"> <img data-volume={{.Name}} alt="Drive" src="websrc/images/resources/drive.svg#symb">
<p>{{.Name}}</p> <p>{{.Name}}</p>
<p>{{.Volume.File}}</p> <p>{{.Volume.File}}</p>
<div> <div>
@@ -109,7 +109,7 @@
{{end}} {{end}}
{{define "volume-mp"}} {{define "volume-mp"}}
<img data-volume={{.Name}} alt="Drive" src="images/resources/drive.svg#symb"> <img data-volume={{.Name}} alt="Drive" src="websrc/images/resources/drive.svg#symb">
<p>{{.Name}}</p> <p>{{.Name}}</p>
<p>{{.Volume.File}}</p> <p>{{.Volume.File}}</p>
<div> <div>
@@ -121,7 +121,7 @@
{{end}} {{end}}
{{define "volume-ide"}} {{define "volume-ide"}}
<img data-volume={{.Name}} alt="Drive" src="images/resources/drive.svg#symb"> <img data-volume={{.Name}} alt="Drive" src="websrc/images/resources/drive.svg#symb">
<p>{{.Name}}</p> <p>{{.Name}}</p>
<p>{{.Volume.File}}</p> <p>{{.Volume.File}}</p>
<div> <div>
@@ -133,7 +133,7 @@
{{end}} {{end}}
{{define "volume-scsi"}} {{define "volume-scsi"}}
<img data-volume={{.Name}} alt="Drive" src="images/resources/drive.svg#symb"> <img data-volume={{.Name}} alt="Drive" src="websrc/images/resources/drive.svg#symb">
<p>{{.Name}}</p> <p>{{.Name}}</p>
<p>{{.Volume.File}}</p> <p>{{.Volume.File}}</p>
<div> <div>
@@ -145,7 +145,7 @@
{{end}} {{end}}
{{define "volume-unused"}} {{define "volume-unused"}}
<img data-volume={{.Name}} alt="Drive" src="images/resources/drive.svg#symb"> <img data-volume={{.Name}} alt="Drive" src="websrc/images/resources/drive.svg#symb">
<p>{{.Name}}</p> <p>{{.Name}}</p>
<p>{{.Volume.File}}</p> <p>{{.Volume.File}}</p>
<div> <div>
@@ -159,8 +159,8 @@
{{define "volume-action-move"}} {{define "volume-action-move"}}
<volume-action data-type="move" data-volume="{{.Name}}"> <volume-action data-type="move" data-volume="{{.Name}}">
<template shadowrootmode="open"> <template shadowrootmode="open">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<img class="clickable" alt="Move {{.Name}}" src="images/actions/disk/move-active.svg#symb"> <img class="clickable" alt="Move {{.Name}}" src="websrc/images/actions/disk/move-active.svg#symb">
<template id="dialog-template"> <template id="dialog-template">
<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">
@@ -185,8 +185,8 @@
{{define "volume-action-move-inactive"}} {{define "volume-action-move-inactive"}}
<volume-action data-type="none" data-volume="{{.Name}}"> <volume-action data-type="none" data-volume="{{.Name}}">
<template shadowrootmode="open"> <template shadowrootmode="open">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<img alt="" src="images/actions/disk/move-inactive.svg#symb"> <img alt="" src="websrc/images/actions/disk/move-inactive.svg#symb">
</template> </template>
</volume-action> </volume-action>
{{end}} {{end}}
@@ -194,8 +194,8 @@
{{define "volume-action-resize"}} {{define "volume-action-resize"}}
<volume-action data-type="resize" data-volume="{{.Name}}"> <volume-action data-type="resize" data-volume="{{.Name}}">
<template shadowrootmode="open"> <template shadowrootmode="open">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<img class="clickable" alt="Resize {{.Name}}" src="images/actions/disk/resize-active.svg#symb"> <img class="clickable" alt="Resize {{.Name}}" src="websrc/images/actions/disk/resize-active.svg#symb">
<template id="dialog-template"> <template id="dialog-template">
<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">
@@ -220,8 +220,8 @@
{{define "volume-action-resize-inactive"}} {{define "volume-action-resize-inactive"}}
<volume-action data-type="none" data-volume="{{.Name}}"> <volume-action data-type="none" data-volume="{{.Name}}">
<template shadowrootmode="open"> <template shadowrootmode="open">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<img alt="" src="images/actions/disk/resize-inactive.svg#symb"> <img alt="" src="websrc/images/actions/disk/resize-inactive.svg#symb">
</template> </template>
</volume-action> </volume-action>
{{end}} {{end}}
@@ -229,8 +229,8 @@
{{define "volume-action-delete"}} {{define "volume-action-delete"}}
<volume-action data-type="delete" data-volume="{{.Name}}"> <volume-action data-type="delete" data-volume="{{.Name}}">
<template shadowrootmode="open"> <template shadowrootmode="open">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<img class="clickable" alt="Delete {{.Name}}" src="images/actions/disk/delete-active.svg#symb"> <img class="clickable" alt="Delete {{.Name}}" src="websrc/images/actions/disk/delete-active.svg#symb">
<template id="dialog-template"> <template id="dialog-template">
<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">
@@ -254,8 +254,8 @@
{{define "volume-action-delete-inactive"}} {{define "volume-action-delete-inactive"}}
<volume-action data-type="none" data-volume="{{.Name}}"> <volume-action data-type="none" data-volume="{{.Name}}">
<template shadowrootmode="open"> <template shadowrootmode="open">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<img alt="" src="images/actions/disk/delete-inactive.svg#symb"> <img alt="" src="websrc/images/actions/disk/delete-inactive.svg#symb">
</template> </template>
</volume-action> </volume-action>
{{end}} {{end}}
@@ -263,8 +263,8 @@
{{define "volume-action-attach"}} {{define "volume-action-attach"}}
<volume-action data-type="attach" data-volume="{{.Name}}"> <volume-action data-type="attach" data-volume="{{.Name}}">
<template shadowrootmode="open"> <template shadowrootmode="open">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<img class="clickable" alt="Attach {{.Name}}" src="images/actions/disk/attach.svg#symb"> <img class="clickable" alt="Attach {{.Name}}" src="websrc/images/actions/disk/attach.svg#symb">
<template id="dialog-template"> <template id="dialog-template">
<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">
@@ -296,8 +296,8 @@
{{define "volume-action-detach"}} {{define "volume-action-detach"}}
<volume-action data-type="detach" data-volume="{{.Name}}"> <volume-action data-type="detach" data-volume="{{.Name}}">
<template shadowrootmode="open"> <template shadowrootmode="open">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<img class="clickable" alt="Detach {{.Name}}" src="images/actions/disk/detach.svg#symb"> <img class="clickable" alt="Detach {{.Name}}" src="websrc/images/actions/disk/detach.svg#symb">
<template id="dialog-template"> <template id="dialog-template">
<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">
@@ -321,8 +321,8 @@
{{define "volume-action-none"}} {{define "volume-action-none"}}
<volume-action data-type="none"> <volume-action data-type="none">
<template shadowrootmode="open"> <template shadowrootmode="open">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<img alt="" src="images/common/blank.svg"> <img alt="" src="websrc/images/common/blank.svg">
</template> </template>
</volume-action> </volume-action>
{{end}} {{end}}
@@ -336,7 +336,7 @@
{{define "nets-add-net"}} {{define "nets-add-net"}}
<button type="button" id="network-add" class="w3-button" aria-label="Add New Network Interface"> <button type="button" id="network-add" class="w3-button" aria-label="Add New Network Interface">
<span class="large" style="margin: 0;">Add Network</span> <span class="large" style="margin: 0;">Add Network</span>
<img class="small" style="height: 1lh; width: 1lh;" alt="Add New Network Interface" src="images/actions/network/add.svg#symb"> <img class="small" style="height: 1lh; width: 1lh;" alt="Add New Network Interface" src="websrc/images/actions/network/add.svg#symb">
</button> </button>
<template id="add-net-dialog"> <template id="add-net-dialog">
<dialog class="w3-container w3-card w3-border-0"> <dialog class="w3-container w3-card w3-border-0">
@@ -361,14 +361,14 @@
{{end}} {{end}}
{{define "net"}} {{define "net"}}
<img data-network="{{.Net_ID}}" alt="Net {{.Net_ID}}" src="images/resources/network.svg#symb"> <img data-network="{{.Net_ID}}" alt="Net {{.Net_ID}}" src="websrc/images/resources/network.svg#symb">
<p>{{.Net_ID}}</p> <p>{{.Net_ID}}</p>
<p>{{.Value}}</p> <p>{{.Value}}</p>
<div> <div>
<network-action data-type="config" data-network="{{.Net_ID}}" data-value="{{.Value}}"> <network-action data-type="config" data-network="{{.Net_ID}}" data-value="{{.Value}}">
<template shadowrootmode="open"> <template shadowrootmode="open">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<img class="clickable" alt="Configure Net {{.Net_ID}}" src="images/actions/network/config.svg#symb"> <img class="clickable" alt="Configure Net {{.Net_ID}}" src="websrc/images/actions/network/config.svg#symb">
<template id="dialog-template"> <template id="dialog-template">
<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">
@@ -389,8 +389,8 @@
</network-action> </network-action>
<network-action data-type="delete" data-network="{{.Net_ID}}" data-value="{{.Value}}"> <network-action data-type="delete" data-network="{{.Net_ID}}" data-value="{{.Value}}">
<template shadowrootmode="open"> <template shadowrootmode="open">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<img class="clickable" alt="Delete Net {{.Net_ID}}" src="images/actions/network/delete-active.svg#symb"> <img class="clickable" alt="Delete Net {{.Net_ID}}" src="websrc/images/actions/network/delete-active.svg#symb">
<template id="dialog-template"> <template id="dialog-template">
<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">
@@ -421,7 +421,7 @@
{{define "devices-add-device"}} {{define "devices-add-device"}}
<button type="button" id="device-add" class="w3-button" aria-label="Add New PCIe Device"> <button type="button" id="device-add" class="w3-button" aria-label="Add New PCIe Device">
<span class="large" style="margin: 0;">Add Device</span> <span class="large" style="margin: 0;">Add Device</span>
<img class="small" style="height: 1lh; width: 1lh;" alt="Add New PCIe Device" src="images/actions/device/add.svg#symb"> <img class="small" style="height: 1lh; width: 1lh;" alt="Add New PCIe Device" src="websrc/images/actions/device/add.svg#symb">
</button> </button>
<template id="add-device-dialog"> <template id="add-device-dialog">
<dialog class="w3-container w3-card w3-border-0"> <dialog class="w3-container w3-card w3-border-0">
@@ -444,14 +444,14 @@
{{end}} {{end}}
{{define "device"}} {{define "device"}}
<img data-device="{{.Device_ID}}" alt="Device {{.Device_ID}}" src="images/resources/device.svg#symb"> <img data-device="{{.Device_ID}}" alt="Device {{.Device_ID}}" src="websrc/images/resources/device.svg#symb">
<p>{{.Device_ID}}</p> <p>{{.Device_ID}}</p>
<p>{{.Device_Name}}</p> <p>{{.Device_Name}}</p>
<div> <div>
<device-action data-type="config" data-device="{{.Device_ID}}" data-value="{{.Device_ID}}"> <device-action data-type="config" data-device="{{.Device_ID}}" data-value="{{.Device_ID}}">
<template shadowrootmode="open"> <template shadowrootmode="open">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<img class="clickable" alt="Configure Device {{.Device_ID}}" src="images/actions/device/config.svg#symb"> <img class="clickable" alt="Configure Device {{.Device_ID}}" src="websrc/images/actions/device/config.svg#symb">
<template id="dialog-template"> <template id="dialog-template">
<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">
@@ -472,8 +472,8 @@
</device-action> </device-action>
<device-action data-type="delete" data-device="{{.Device_ID}}" data-value="{{.Device_ID}}"> <device-action data-type="delete" data-device="{{.Device_ID}}" data-value="{{.Device_ID}}">
<template shadowrootmode="open"> <template shadowrootmode="open">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<img class="clickable" alt="Delete Device {{.Device_ID}}" src="images/actions/device/delete-active.svg#symb"> <img class="clickable" alt="Delete Device {{.Device_ID}}" src="websrc/images/actions/device/delete-active.svg#symb">
<template id="dialog-template"> <template id="dialog-template">
<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">
@@ -544,15 +544,15 @@
{{define "boot-target"}} {{define "boot-target"}}
{{if .volume_id}} {{if .volume_id}}
<div class="draggable-item" data-value="{{.volume_id}}" style="display: grid; grid-template-columns: auto auto 8ch 1fr; column-gap: 0.5em; align-items: center;"> <div class="draggable-item" data-value="{{.volume_id}}" style="display: grid; grid-template-columns: auto auto 8ch 1fr; column-gap: 0.5em; align-items: center;">
<img alt="Drag" src="images/actions/drag.svg#symb"> <img alt="Drag" src="websrc/images/actions/drag.svg#symb">
<img alt="Volume" src="images/resources/drive.svg#symb"> <img alt="Volume" src="websrc/images/resources/drive.svg#symb">
<p class="volume-id">{{.volume_id}}</p> <p class="volume-id">{{.volume_id}}</p>
<p class="volume-file">{{.file}}</p> <p class="volume-file">{{.file}}</p>
</div> </div>
{{else if .net_id}} {{else if .net_id}}
<div class="draggable-item" data-value="{{.net_id}}" style="display: grid; grid-template-columns: auto auto 8ch 1fr; column-gap: 0.5em; align-items: center;"> <div class="draggable-item" data-value="{{.net_id}}" style="display: grid; grid-template-columns: auto auto 8ch 1fr; column-gap: 0.5em; align-items: center;">
<img alt="Drag" src="images/actions/drag.svg#symb"> <img alt="Drag" src="websrc/images/actions/drag.svg#symb">
<img alt="Net" src="images/resources/network.svg#symb"> <img alt="Net" src="websrc/images/resources/network.svg#symb">
<p class="volume-id">{{.net_id}}</p> <p class="volume-id">{{.net_id}}</p>
<p class="volume-file">{{.value}}</p> <p class="volume-file">{{.value}}</p>
</div> </div>
@@ -1,8 +1,8 @@
{{define "instance-card"}} {{define "instance-card"}}
<instance-card data-type="{{.Type}}" data-status="{{.Status}}" data-vmid="{{.VMID}}" data-name="{{.Name}}" data-node="{{.Node}}" data-nodestatus="{{.NodeStatus}}"> <instance-card data-type="{{.Type}}" data-status="{{.Status}}" data-vmid="{{.VMID}}" data-name="{{.Name}}" data-node="{{.Node}}" data-nodestatus="{{.NodeStatus}}">
<template shadowrootmode="open"> <template shadowrootmode="open">
<link rel="stylesheet" href="modules/w3.css"> <link rel="stylesheet" href="websrc/modules/w3.css">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<style> <style>
* { * {
margin: 0; margin: 0;
@@ -60,60 +60,60 @@
<p class="hide-small">{{.Type}}</p> <p class="hide-small">{{.Type}}</p>
<div class="flex row nowrap hide-tiny"> <div class="flex row nowrap hide-tiny">
{{if eq .Status "running"}} {{if eq .Status "running"}}
<img id="status" alt="instance is running" src="images/status/active.svg#symb"> <img id="status" alt="instance is running" src="websrc/images/status/active.svg#symb">
{{else if eq .Status "stopped"}} {{else if eq .Status "stopped"}}
<img id="status" alt="instance is stopped" src="images/status/inactive.svg#symb"> <img id="status" alt="instance is stopped" src="websrc/images/status/inactive.svg#symb">
{{else if eq .Status "loading"}} {{else if eq .Status "loading"}}
<img id="status" alt="instance is loading" src="images/status/loading.svg#symb"> <img id="status" alt="instance is loading" src="websrc/images/status/loading.svg#symb">
{{else}} {{else}}
<img id="status" alt="instance is loading" src="images/status/loading.svg#symb"> <img id="status" alt="instance is loading" src="websrc/images/status/loading.svg#symb">
{{end}} {{end}}
<p>{{.Status}}</p> <p>{{.Status}}</p>
</div> </div>
<p class="hide-medium">{{.Node}}</p> <p class="hide-medium">{{.Node}}</p>
<div class="flex row nowrap hide-medium"> <div class="flex row nowrap hide-medium">
{{if eq .NodeStatus "online"}} {{if eq .NodeStatus "online"}}
<img alt="node is online" src="images/status/active.svg#symb"> <img alt="node is online" src="websrc/images/status/active.svg#symb">
{{else if eq .NodeStatus "offline"}} {{else if eq .NodeStatus "offline"}}
<img alt="node is offline" src="images/status/inactive.svg#symb"> <img alt="node is offline" src="websrc/images/status/inactive.svg#symb">
{{else if eq .NodeStatus "unknown"}} {{else if eq .NodeStatus "unknown"}}
<img alt="node is offline" src="images/status/inactive.svg#symb"> <img alt="node is offline" src="websrc/images/status/inactive.svg#symb">
{{else}} {{else}}
{{end}} {{end}}
<p>{{.NodeStatus}}</p> <p>{{.NodeStatus}}</p>
</div> </div>
<div class="flex row nowrap"> <div class="flex row nowrap">
{{if and (eq .NodeStatus "online") (eq .Status "running")}} {{if and (eq .NodeStatus "online") (eq .Status "running")}}
<img id="power-btn" class="clickable" alt="shutdown instance" role="button" tabindex=0 src="images/actions/instance/stop.svg#symb"> <img id="power-btn" class="clickable" alt="shutdown instance" role="button" tabindex=0 src="websrc/images/actions/instance/stop.svg#symb">
<img id="configure-btn" alt="" aria-disabled="true" role="none" src="images/actions/instance/config-inactive.svg#symb"> <img id="configure-btn" alt="" aria-disabled="true" role="none" src="websrc/images/actions/instance/config-inactive.svg#symb">
<img id="backup-btn" alt="" aria-disabled="true" role="none" src="images/actions/instance/backup-inactive.svg#symb"> <img id="backup-btn" alt="" aria-disabled="true" role="none" src="websrc/images/actions/instance/backup-inactive.svg#symb">
<a href="{{.ConsolePath}}" target="_blank"> <a href="{{.ConsolePath}}" target="_blank">
<img id="console-btn" class="clickable" alt="open console" src="images/actions/instance/console-active.svg#symb"> <img id="console-btn" class="clickable" alt="open console" src="websrc/images/actions/instance/console-active.svg#symb">
</a> </a>
<img id="delete-btn" alt="" aria-disabled="true" role="none" src="images/actions/instance/delete-inactive.svg#symb"> <img id="delete-btn" alt="" aria-disabled="true" role="none" src="websrc/images/actions/instance/delete-inactive.svg#symb">
{{else if and (eq .NodeStatus "online") (eq .Status "stopped")}} {{else if and (eq .NodeStatus "online") (eq .Status "stopped")}}
<img id="power-btn" class="clickable" alt="start instance" role="button" tabindex=0 src="images/actions/instance/start.svg#symb"> <img id="power-btn" class="clickable" alt="start instance" role="button" tabindex=0 src="websrc/images/actions/instance/start.svg#symb">
<a href="{{.ConfigPath}}"> <a href="{{.ConfigPath}}">
<img id="configure-btn" class="clickable" alt="change configuration" src="images/actions/instance/config-active.svg#symb"> <img id="configure-btn" class="clickable" alt="change configuration" src="websrc/images/actions/instance/config-active.svg#symb">
</a> </a>
<a href="{{.BackupsPath}}"> <a href="{{.BackupsPath}}">
<img id="backup-btn" class="clickable" alt="manage backups" src="images/actions/instance/backup-active.svg#symb"> <img id="backup-btn" class="clickable" alt="manage backups" src="websrc/images/actions/instance/backup-active.svg#symb">
</a> </a>
<img id="console-btn" alt="" aria-disabled="true" role="none" src="images/actions/instance/console-inactive.svg#symb"> <img id="console-btn" alt="" aria-disabled="true" role="none" src="websrc/images/actions/instance/console-inactive.svg#symb">
<img id="delete-btn" class="clickable" alt="delete instance" role="button" tabindex=0 src="images/actions/instance/delete-active.svg#symb"> <img id="delete-btn" class="clickable" alt="delete instance" role="button" tabindex=0 src="websrc/images/actions/instance/delete-active.svg#symb">
{{else if and (eq .NodeStatus "online") (eq .Status "loading")}} {{else if and (eq .NodeStatus "online") (eq .Status "loading")}}
<img id="power-btn" alt="" aria-disabled="true" role="none" src="images/actions/instance/loading.svg#symb"> <img id="power-btn" alt="" aria-disabled="true" role="none" src="websrc/images/actions/instance/loading.svg#symb">
<img id="configure-btn" alt="" aria-disabled="true" role="none" src="images/actions/instance/config-inactive.svg#symb"> <img id="configure-btn" alt="" aria-disabled="true" role="none" src="websrc/images/actions/instance/config-inactive.svg#symb">
<img id="backup-btn" alt="" aria-disabled="true" role="none" src="images/actions/instance/backup-inactive.svg#symb"> <img id="backup-btn" alt="" aria-disabled="true" role="none" src="websrc/images/actions/instance/backup-inactive.svg#symb">
<img id="console-btn" alt="" aria-disabled="true" role="none" src="images/actions/instance/console-inactive.svg#symb"> <img id="console-btn" alt="" aria-disabled="true" role="none" src="websrc/images/actions/instance/console-inactive.svg#symb">
<img id="delete-btn" alt="" aria-disabled="true" role="none" src="images/actions/instance/delete-inactive.svg#symb"> <img id="delete-btn" alt="" aria-disabled="true" role="none" src="websrc/images/actions/instance/delete-inactive.svg#symb">
{{else}} {{else}}
{{end}} {{end}}
</div> </div>
<template id="power-dialog"> <template id="power-dialog">
<link rel="stylesheet" href="modules/w3.css"> <link rel="stylesheet" href="websrc/modules/w3.css">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<link rel="stylesheet" href="css/form.css"> <link rel="stylesheet" href="websrc/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">
{{if eq .Status "running"}} {{if eq .Status "running"}}
@@ -140,9 +140,9 @@
</dialog> </dialog>
</template> </template>
<template id="delete-dialog"> <template id="delete-dialog">
<link rel="stylesheet" href="modules/w3.css"> <link rel="stylesheet" href="websrc/modules/w3.css">
<link rel="stylesheet" href="css/style.css"> <link rel="stylesheet" href="websrc/css/style.css">
<link rel="stylesheet" href="css/form.css"> <link rel="stylesheet" href="websrc/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">
Delete {{.VMID}} Delete {{.VMID}}
Loaded 100 of 111 files, more files were not shown because too many files have changed in this diff. Show more