15 Commits
Author SHA1 Message Date
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
17 changed files with 396 additions and 356 deletions
+14 -15
View File
@@ -1,27 +1,26 @@
.PHONY: build build-web build-wfa-js clean clean-wfa-js ensure-dist workflow-init .PHONY: build build-web build-wfa clean clean-wfa clean-web
build: clean ensure-dist build-web build-wfa-js build: clean ensure-dist build-wfa build-web
@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/ . CGO_ENABLED=0 go build -tags release -ldflags="-s -w" -v -o dist/ .
build-web: ensure-dist clean: clean-wfa clean-web
cp -rL web/ dist/web/
build-wfa-js: ensure-dist
$(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 build-web: ensure-dist
cp -r dev_config/. . cp -rL web/ dist/web/
clean-web:
build-wfa:
$(MAKE) -C WFA-JS
cp -f WFA-JS/dist/* web/modules
clean-wfa:
$(MAKE) clean -C WFA-JS
+1 -1
View File
@@ -13,7 +13,7 @@ ProxmoxAAS Dashboard provides users of a proxmox based compute on demand service
## ProxmoxAAS System Installation Overview ## ProxmoxAAS System Installation Overview
The ProxmoxAAS project is large and is split into multiple components. There are four required components, the Dashboard, API, Fabric, and access-manager-api. The instalation order should start with the Dashboard and then proceed to the other backend components. This will require some foresight into the setup process. The ProxmoxAAS project is large and is split into multiple components. There are four required components, the Dashboard, API, Fabric, and access-manager-api. The installation order should start with the Dashboard and then proceed to the other backend components. This will require some foresight into the setup process.
The supported setup is to use a reverse proxy to serve both the original Proxmox web interface and ProxmoxAAS components. It is possible other setups can work. Rather than provide specific steps to duplicate a certain setup, the steps included are intended as a guideline of steps required for proper function in most setups. Consequently, the examples provided are only to highlight key settings and do not represent complete working configurations. The instructions also assume you have your own domain name which will substitute `domain.net` in some of the configs. The supported setup is to use a reverse proxy to serve both the original Proxmox web interface and ProxmoxAAS components. It is possible other setups can work. Rather than provide specific steps to duplicate a certain setup, the steps included are intended as a guideline of steps required for proper function in most setups. Consequently, the examples provided are only to highlight key settings and do not represent complete working configurations. The instructions also assume you have your own domain name which will substitute `domain.net` in some of the configs.
+1 -1
Submodule WFA-JS updated: f53f344fb3...b0aacd5f9c
+7 -1
View File
@@ -18,9 +18,11 @@ func Run() {
common.Global = common.GetConfig(*configPath) common.Global = common.GetConfig(*configPath)
// setup static resources // setup gin
gin.SetMode(gin.ReleaseMode) gin.SetMode(gin.ReleaseMode)
router := gin.Default() router := gin.Default()
// setup static resources
m := common.InitMinify() m := common.InitMinify()
ServeStatic(router, m) ServeStatic(router, m)
html := common.MinifyStatic(m, web.Templates) html := common.MinifyStatic(m, web.Templates)
@@ -51,24 +53,28 @@ func ServeStatic(router *gin.Engine, m *minify.M) {
router.GET("/css/*css", func(c *gin.Context) { router.GET("/css/*css", func(c *gin.Context) {
path, _ := c.Params.Get("css") path, _ := c.Params.Get("css")
data := css[fmt.Sprintf("css%s", path)] data := css[fmt.Sprintf("css%s", path)]
c.Header("Content-Encoding", data.MimeType.Encoding)
c.Data(200, data.MimeType.Type, []byte(data.Data)) c.Data(200, data.MimeType.Type, []byte(data.Data))
}) })
images := common.MinifyStatic(m, web.Images_fs) images := common.MinifyStatic(m, web.Images_fs)
router.GET("/images/*image", func(c *gin.Context) { router.GET("/images/*image", func(c *gin.Context) {
path, _ := c.Params.Get("image") path, _ := c.Params.Get("image")
data := images[fmt.Sprintf("images%s", path)] data := images[fmt.Sprintf("images%s", path)]
c.Header("Content-Encoding", data.MimeType.Encoding)
c.Data(200, data.MimeType.Type, []byte(data.Data)) c.Data(200, data.MimeType.Type, []byte(data.Data))
}) })
modules := common.MinifyStatic(m, web.Modules_fs) modules := common.MinifyStatic(m, web.Modules_fs)
router.GET("/modules/*module", func(c *gin.Context) { router.GET("/modules/*module", func(c *gin.Context) {
path, _ := c.Params.Get("module") path, _ := c.Params.Get("module")
data := modules[fmt.Sprintf("modules%s", path)] data := modules[fmt.Sprintf("modules%s", path)]
c.Header("Content-Encoding", data.MimeType.Encoding)
c.Data(200, data.MimeType.Type, []byte(data.Data)) c.Data(200, data.MimeType.Type, []byte(data.Data))
}) })
scripts := common.MinifyStatic(m, web.Scripts_fs) scripts := common.MinifyStatic(m, web.Scripts_fs)
router.GET("/scripts/*script", func(c *gin.Context) { router.GET("/scripts/*script", func(c *gin.Context) {
path, _ := c.Params.Get("script") path, _ := c.Params.Get("script")
data := scripts[fmt.Sprintf("scripts%s", path)] data := scripts[fmt.Sprintf("scripts%s", path)]
c.Header("Content-Encoding", data.MimeType.Encoding)
c.Data(200, data.MimeType.Type, []byte(data.Data)) 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,
-3
View File
@@ -1,3 +0,0 @@
web/modules/*
WFA-JS/*
dist/*
+16 -15
View File
@@ -1,25 +1,26 @@
module proxmoxaas-dashboard module proxmoxaas-dashboard
go 1.26.5 go 1.27.0
require ( require (
github.com/gerow/go-color v0.0.0-20140219113758-125d37f527f1 github.com/gerow/go-color v0.0.0-20140219113758-125d37f527f1
github.com/gin-gonic/gin v1.12.0 github.com/gin-gonic/gin v1.12.0
github.com/go-viper/mapstructure/v2 v2.5.0 github.com/go-viper/mapstructure/v2 v2.5.0
github.com/tdewolff/minify/v2 v2.24.13 github.com/tdewolff/minify/v2 v2.24.17
proxmoxaas-common-lib v0.0.0 proxmoxaas-common-lib v0.0.0
web v0.0.0 web v0.0.0
) )
replace proxmoxaas-common-lib => ./proxmoxaas-common-lib replace proxmoxaas-common-lib => ./proxmoxaas-common-lib
replace web => ./dist/web replace web => ./dist/web
require ( require (
github.com/bytedance/gopkg v0.1.4 // indirect github.com/bytedance/gopkg v0.1.4 // indirect
github.com/bytedance/sonic v1.15.2 // indirect github.com/bytedance/sonic v1.15.2 // indirect
github.com/bytedance/sonic/loader v0.5.1 // indirect github.com/bytedance/sonic/loader v0.5.2 // indirect
github.com/cloudwego/base64x v0.1.7 // indirect github.com/cloudwego/base64x v0.1.7 // indirect
github.com/gabriel-vasile/mimetype v1.4.13 // indirect github.com/gabriel-vasile/mimetype v1.4.15 // indirect
github.com/gin-contrib/sse v1.1.1 // indirect github.com/gin-contrib/sse v1.1.1 // indirect
github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect
@@ -28,21 +29,21 @@ require (
github.com/goccy/go-yaml v1.19.2 // indirect github.com/goccy/go-yaml v1.19.2 // indirect
github.com/json-iterator/go v1.1.12 // indirect github.com/json-iterator/go v1.1.12 // indirect
github.com/klauspost/cpuid/v2 v2.4.0 // indirect github.com/klauspost/cpuid/v2 v2.4.0 // indirect
github.com/leodido/go-urn v1.4.0 // indirect github.com/leodido/go-urn v1.5.0 // indirect
github.com/mattn/go-isatty v0.0.23 // indirect github.com/mattn/go-isatty v0.0.24 // indirect
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
github.com/modern-go/reflect2 v1.0.2 // indirect github.com/modern-go/reflect2 v1.0.2 // indirect
github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/pelletier/go-toml/v2 v2.4.3 // indirect
github.com/quic-go/qpack v0.6.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect
github.com/quic-go/quic-go v0.60.0 // indirect github.com/quic-go/quic-go v0.61.0 // indirect
github.com/tdewolff/parse/v2 v2.8.13 // indirect github.com/tdewolff/parse/v2 v2.8.16 // indirect
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
github.com/ugorji/go/codec v1.3.1 // indirect github.com/ugorji/go/codec v1.3.2 // indirect
go.mongodb.org/mongo-driver/v2 v2.8.0 // indirect go.mongodb.org/mongo-driver/v2 v2.8.1 // indirect
golang.org/x/arch v0.29.0 // indirect golang.org/x/arch v0.30.0 // indirect
golang.org/x/crypto v0.54.0 // indirect golang.org/x/crypto v0.55.0 // indirect
golang.org/x/net v0.57.0 // indirect golang.org/x/net v0.58.0 // indirect
golang.org/x/sys v0.47.0 // indirect golang.org/x/sys v0.47.0 // indirect
golang.org/x/text v0.40.0 // indirect golang.org/x/text v0.41.0 // indirect
google.golang.org/protobuf v1.36.11 // indirect google.golang.org/protobuf v1.36.12 // indirect
) )
+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}
+1 -1
View File
@@ -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>
+332 -307
View File
@@ -1,53 +1,26 @@
// Copyright 2018 The Go Authors. All rights reserved. // Copyright 2018 The Go Authors. All rights reserved.
// Use of this source code is governed by a BSD-style // Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file. // license that can be found in the LICENSE file.
//
// This file has been modified for use by the TinyGo compiler. "use strict";
(() => { (() => {
// Map multiple JavaScript environments to a single common API,
// preferring web standards over Node.js API.
//
// Environments considered:
// - Browsers
// - Node.js
// - Electron
// - Parcel
if (typeof global !== "undefined") {
// global already exists
} else if (typeof window !== "undefined") {
window.global = window;
} else if (typeof self !== "undefined") {
self.global = self;
} else {
throw new Error("cannot export Go (neither global, window nor self is defined)");
}
if (!global.require && typeof require !== "undefined") {
global.require = require;
}
if (!global.fs && global.require) {
global.fs = require("node:fs");
}
const enosys = () => { const enosys = () => {
const err = new Error("not implemented"); const err = new Error("not implemented");
err.code = "ENOSYS"; err.code = "ENOSYS";
return err; return err;
}; };
if (!global.fs) { if (!globalThis.fs) {
let outputBuf = ""; let outputBuf = "";
global.fs = { globalThis.fs = {
constants: { O_WRONLY: -1, O_RDWR: -1, O_CREAT: -1, O_TRUNC: -1, O_APPEND: -1, O_EXCL: -1 }, // unused 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) { writeSync(fd, buf) {
outputBuf += decoder.decode(buf); outputBuf += decoder.decode(buf);
const nl = outputBuf.lastIndexOf("\n"); const nl = outputBuf.lastIndexOf("\n");
if (nl != -1) { if (nl != -1) {
console.log(outputBuf.substr(0, nl)); console.log(outputBuf.substring(0, nl));
outputBuf = outputBuf.substr(nl + 1); outputBuf = outputBuf.substring(nl + 1);
} }
return buf.length; return buf.length;
}, },
@@ -85,8 +58,8 @@
}; };
} }
if (!global.process) { if (!globalThis.process) {
global.process = { globalThis.process = {
getuid() { return -1; }, getuid() { return -1; },
getgid() { return -1; }, getgid() { return -1; },
geteuid() { return -1; }, geteuid() { return -1; },
@@ -100,53 +73,66 @@
} }
} }
if (!global.crypto) { if (!globalThis.path) {
const nodeCrypto = require("node:crypto"); globalThis.path = {
global.crypto = { resolve(...pathSegments) {
getRandomValues(b) { return pathSegments.join("/");
nodeCrypto.randomFillSync(b); }
}, }
};
} }
if (!global.performance) { if (!globalThis.crypto) {
global.performance = { throw new Error("globalThis.crypto is not available, polyfill required (crypto.getRandomValues only)");
now() {
const [sec, nsec] = process.hrtime();
return sec * 1000 + nsec / 1000000;
},
};
} }
if (!global.TextEncoder) { if (!globalThis.performance) {
global.TextEncoder = require("node:util").TextEncoder; throw new Error("globalThis.performance is not available, polyfill required (performance.now only)");
} }
if (!global.TextDecoder) { if (!globalThis.TextEncoder) {
global.TextDecoder = require("node:util").TextDecoder; throw new Error("globalThis.TextEncoder is not available, polyfill required");
} }
// End of polyfills for common API. if (!globalThis.TextDecoder) {
throw new Error("globalThis.TextDecoder is not available, polyfill required");
}
const encoder = new TextEncoder("utf-8"); const encoder = new TextEncoder("utf-8");
const decoder = new TextDecoder("utf-8"); const decoder = new TextDecoder("utf-8");
let reinterpretBuf = new DataView(new ArrayBuffer(8));
var logLine = [];
const wasmExit = {}; // thrown to exit via proc_exit (not an error)
global.Go = class { globalThis.Go = class {
constructor() { constructor() {
this._callbackTimeouts = new Map(); 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; this._nextCallbackTimeoutID = 1;
const mem = () => { const setInt64 = (addr, v) => {
// The buffer may change when requesting more memory. this.mem.setUint32(addr + 0, v, true);
return new DataView(this._inst.exports.memory.buffer); this.mem.setUint32(addr + 4, Math.floor(v / 4294967296), true);
} }
const unboxValue = (v_ref) => { const setInt32 = (addr, v) => {
reinterpretBuf.setBigInt64(0, v_ref, true); this.mem.setUint32(addr + 0, v, true);
const f = reinterpretBuf.getFloat64(0, 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) { if (f === 0) {
return undefined; return undefined;
} }
@@ -154,77 +140,69 @@
return f; return f;
} }
const id = v_ref & 0xffffffffn; const id = this.mem.getUint32(addr, true);
return this._values[id]; return this._values[id];
} }
const storeValue = (addr, v) => {
const nanHead = 0x7FF80000;
const loadValue = (addr) => { if (typeof v === "number" && v !== 0) {
let v_ref = mem().getBigUint64(addr, true);
return unboxValue(v_ref);
}
const boxValue = (v) => {
const nanHead = 0x7FF80000n;
if (typeof v === "number") {
if (isNaN(v)) { if (isNaN(v)) {
return nanHead << 32n; this.mem.setUint32(addr + 4, nanHead, true);
this.mem.setUint32(addr, 0, true);
return;
} }
if (v === 0) { this.mem.setFloat64(addr, v, true);
return (nanHead << 32n) | 1n; return;
}
reinterpretBuf.setFloat64(0, v, true);
return reinterpretBuf.getBigInt64(0, true);
} }
switch (v) { if (v === undefined) {
case undefined: this.mem.setFloat64(addr, 0, true);
return 0n; return;
case null:
return (nanHead << 32n) | 2n;
case true:
return (nanHead << 32n) | 3n;
case false:
return (nanHead << 32n) | 4n;
} }
let id = this._ids.get(v); let id = this._ids.get(v);
if (id === undefined) { if (id === undefined) {
id = this._idPool.pop(); id = this._idPool.pop();
if (id === undefined) { if (id === undefined) {
id = BigInt(this._values.length); id = this._values.length;
} }
this._values[id] = v; this._values[id] = v;
this._goRefCounts[id] = 0; this._goRefCounts[id] = 0;
this._ids.set(v, id); this._ids.set(v, id);
} }
this._goRefCounts[id]++; this._goRefCounts[id]++;
let typeFlag = 1n; let typeFlag = 0;
switch (typeof v) { switch (typeof v) {
case "object":
if (v !== null) {
typeFlag = 1;
}
break;
case "string": case "string":
typeFlag = 2n; typeFlag = 2;
break; break;
case "symbol": case "symbol":
typeFlag = 3n; typeFlag = 3;
break; break;
case "function": case "function":
typeFlag = 4n; typeFlag = 4;
break; break;
} }
return id | ((nanHead | typeFlag) << 32n); this.mem.setUint32(addr + 4, nanHead | typeFlag, true);
this.mem.setUint32(addr, id, true);
} }
const storeValue = (addr, v) => { const loadSlice = (addr) => {
let v_ref = boxValue(v); const array = getInt64(addr + 0);
mem().setBigUint64(addr, v_ref, true); const len = getInt64(addr + 8);
return new Uint8Array(this._inst.exports.mem.buffer, array, len);
} }
const loadSlice = (array, len, cap) => { const loadSliceOfValues = (addr) => {
return new Uint8Array(this._inst.exports.memory.buffer, array, len); const array = getInt64(addr + 0);
} const len = getInt64(addr + 8);
const loadSliceOfValues = (array, len, cap) => {
const a = new Array(len); const a = new Array(len);
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
a[i] = loadValue(array + i * 8); a[i] = loadValue(array + i * 8);
@@ -232,287 +210,353 @@
return a; return a;
} }
const loadString = (ptr, len) => { const loadString = (addr) => {
return decoder.decode(new DataView(this._inst.exports.memory.buffer, ptr, len)); 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(); const timeOrigin = Date.now() - performance.now();
this.importObject = { this.importObject = {
wasi_snapshot_preview1: { _gotest: {
// https://github.com/WebAssembly/WASI/blob/main/phases/snapshot/docs.md#fd_write add: (a, b) => a + b,
fd_write: function(fd, iovs_ptr, iovs_len, nwritten_ptr) { callExport: testCallExport,
let nwritten = 0;
if (fd == 1) {
for (let iovs_i=0; iovs_i<iovs_len;iovs_i++) {
let iov_ptr = iovs_ptr+iovs_i*8; // assuming wasm32
let ptr = mem().getUint32(iov_ptr + 0, true);
let len = mem().getUint32(iov_ptr + 4, true);
nwritten += len;
for (let i=0; i<len; i++) {
let c = mem().getUint8(ptr+i);
if (c == 13) { // CR
// ignore
} else if (c == 10) { // LF
// write line
let line = decoder.decode(new Uint8Array(logLine));
logLine = [];
console.log(line);
} else {
logLine.push(c);
}
}
}
} else {
console.error('invalid file descriptor:', fd);
}
mem().setUint32(nwritten_ptr, nwritten, true);
return 0;
},
fd_close: () => 0, // dummy
fd_fdstat_get: () => 0, // dummy
fd_seek: () => 0, // dummy
proc_exit: (code) => {
this.exited = true;
this.exitCode = code;
this._resolveExitPromise();
throw wasmExit;
},
random_get: (bufPtr, bufLen) => {
crypto.getRandomValues(loadSlice(bufPtr, bufLen));
return 0;
},
}, },
gojs: { gojs: {
// func ticks() int64 // Go's SP does not change as long as no Go code is running. Some operations (e.g. calls, getters and setters)
"runtime.ticks": () => { // may synchronously trigger a Go event handler. This makes Go code get executed in the middle of the imported
return BigInt((timeOrigin + performance.now()) * 1e6); // 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 sleepTicks(timeout int64) // func wasmWrite(fd uintptr, p unsafe.Pointer, n int32)
"runtime.sleepTicks": (timeout) => { "runtime.wasmWrite": (sp) => {
// Do not sleep, only reactivate scheduler after the given timeout. sp >>>= 0;
setTimeout(() => { const fd = getInt64(sp + 8);
if (this.exited) return; const p = getInt64(sp + 16);
try { const n = this.mem.getInt32(sp + 24, true);
this._inst.exports.go_scheduler(); fs.writeSync(fd, new Uint8Array(this._inst.exports.mem.buffer, p, n));
} catch (e) { },
if (e !== wasmExit) throw e;
} // func resetMemoryDataView()
}, Number(timeout)/1e6); "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) // func finalizeRef(v ref)
"syscall/js.finalizeRef": (v_ref) => { "syscall/js.finalizeRef": (sp) => {
// Note: TinyGo does not support finalizers so this is only called sp >>>= 0;
// for one specific case, by js.go:jsString. and can/might leak memory. const id = this.mem.getUint32(sp + 8, true);
const id = v_ref & 0xffffffffn; this._goRefCounts[id]--;
if (this._goRefCounts?.[id] !== undefined) { if (this._goRefCounts[id] === 0) {
this._goRefCounts[id]--; const v = this._values[id];
if (this._goRefCounts[id] === 0) { this._values[id] = null;
const v = this._values[id]; this._ids.delete(v);
this._values[id] = null; this._idPool.push(id);
this._ids.delete(v);
this._idPool.push(id);
}
} else {
console.error("syscall/js.finalizeRef: unknown id", id);
} }
}, },
// func stringVal(value string) ref // func stringVal(value string) ref
"syscall/js.stringVal": (value_ptr, value_len) => { "syscall/js.stringVal": (sp) => {
value_ptr >>>= 0; sp >>>= 0;
const s = loadString(value_ptr, value_len); storeValue(sp + 24, loadString(sp + 8));
return boxValue(s);
}, },
// func valueGet(v ref, p string) ref // func valueGet(v ref, p string) ref
"syscall/js.valueGet": (v_ref, p_ptr, p_len) => { "syscall/js.valueGet": (sp) => {
let prop = loadString(p_ptr, p_len); sp >>>= 0;
let v = unboxValue(v_ref); const result = Reflect.get(loadValue(sp + 8), loadString(sp + 16));
let result = Reflect.get(v, prop); sp = this._inst.exports.getsp() >>> 0; // see comment above
return boxValue(result); storeValue(sp + 32, result);
}, },
// func valueSet(v ref, p string, x ref) // func valueSet(v ref, p string, x ref)
"syscall/js.valueSet": (v_ref, p_ptr, p_len, x_ref) => { "syscall/js.valueSet": (sp) => {
const v = unboxValue(v_ref); sp >>>= 0;
const p = loadString(p_ptr, p_len); Reflect.set(loadValue(sp + 8), loadString(sp + 16), loadValue(sp + 32));
const x = unboxValue(x_ref);
Reflect.set(v, p, x);
}, },
// func valueDelete(v ref, p string) // func valueDelete(v ref, p string)
"syscall/js.valueDelete": (v_ref, p_ptr, p_len) => { "syscall/js.valueDelete": (sp) => {
const v = unboxValue(v_ref); sp >>>= 0;
const p = loadString(p_ptr, p_len); Reflect.deleteProperty(loadValue(sp + 8), loadString(sp + 16));
Reflect.deleteProperty(v, p);
}, },
// func valueIndex(v ref, i int) ref // func valueIndex(v ref, i int) ref
"syscall/js.valueIndex": (v_ref, i) => { "syscall/js.valueIndex": (sp) => {
return boxValue(Reflect.get(unboxValue(v_ref), i)); sp >>>= 0;
storeValue(sp + 24, Reflect.get(loadValue(sp + 8), getInt64(sp + 16)));
}, },
// valueSetIndex(v ref, i int, x ref) // valueSetIndex(v ref, i int, x ref)
"syscall/js.valueSetIndex": (v_ref, i, x_ref) => { "syscall/js.valueSetIndex": (sp) => {
Reflect.set(unboxValue(v_ref), i, unboxValue(x_ref)); sp >>>= 0;
Reflect.set(loadValue(sp + 8), getInt64(sp + 16), loadValue(sp + 24));
}, },
// func valueCall(v ref, m string, args []ref) (ref, bool) // func valueCall(v ref, m string, args []ref) (ref, bool)
"syscall/js.valueCall": (ret_addr, v_ref, m_ptr, m_len, args_ptr, args_len, args_cap) => { "syscall/js.valueCall": (sp) => {
const v = unboxValue(v_ref); sp >>>= 0;
const name = loadString(m_ptr, m_len);
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
try { try {
const m = Reflect.get(v, name); const v = loadValue(sp + 8);
storeValue(ret_addr, Reflect.apply(m, v, args)); const m = Reflect.get(v, loadString(sp + 16));
mem().setUint8(ret_addr + 8, 1); 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) { } catch (err) {
storeValue(ret_addr, err); sp = this._inst.exports.getsp() >>> 0; // see comment above
mem().setUint8(ret_addr + 8, 0); storeValue(sp + 56, err);
this.mem.setUint8(sp + 64, 0);
} }
}, },
// func valueInvoke(v ref, args []ref) (ref, bool) // func valueInvoke(v ref, args []ref) (ref, bool)
"syscall/js.valueInvoke": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { "syscall/js.valueInvoke": (sp) => {
sp >>>= 0;
try { try {
const v = unboxValue(v_ref); const v = loadValue(sp + 8);
const args = loadSliceOfValues(args_ptr, args_len, args_cap); const args = loadSliceOfValues(sp + 16);
storeValue(ret_addr, Reflect.apply(v, undefined, args)); const result = Reflect.apply(v, undefined, args);
mem().setUint8(ret_addr + 8, 1); sp = this._inst.exports.getsp() >>> 0; // see comment above
storeValue(sp + 40, result);
this.mem.setUint8(sp + 48, 1);
} catch (err) { } catch (err) {
storeValue(ret_addr, err); sp = this._inst.exports.getsp() >>> 0; // see comment above
mem().setUint8(ret_addr + 8, 0); storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
} }
}, },
// func valueNew(v ref, args []ref) (ref, bool) // func valueNew(v ref, args []ref) (ref, bool)
"syscall/js.valueNew": (ret_addr, v_ref, args_ptr, args_len, args_cap) => { "syscall/js.valueNew": (sp) => {
const v = unboxValue(v_ref); sp >>>= 0;
const args = loadSliceOfValues(args_ptr, args_len, args_cap);
try { try {
storeValue(ret_addr, Reflect.construct(v, args)); const v = loadValue(sp + 8);
mem().setUint8(ret_addr + 8, 1); 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) { } catch (err) {
storeValue(ret_addr, err); sp = this._inst.exports.getsp() >>> 0; // see comment above
mem().setUint8(ret_addr+ 8, 0); storeValue(sp + 40, err);
this.mem.setUint8(sp + 48, 0);
} }
}, },
// func valueLength(v ref) int // func valueLength(v ref) int
"syscall/js.valueLength": (v_ref) => { "syscall/js.valueLength": (sp) => {
return unboxValue(v_ref).length; sp >>>= 0;
setInt64(sp + 16, parseInt(loadValue(sp + 8).length));
}, },
// valuePrepareString(v ref) (ref, int) // valuePrepareString(v ref) (ref, int)
"syscall/js.valuePrepareString": (ret_addr, v_ref) => { "syscall/js.valuePrepareString": (sp) => {
const s = String(unboxValue(v_ref)); sp >>>= 0;
const str = encoder.encode(s); const str = encoder.encode(String(loadValue(sp + 8)));
storeValue(ret_addr, str); storeValue(sp + 16, str);
mem().setInt32(ret_addr + 8, str.length, true); setInt64(sp + 24, str.length);
}, },
// valueLoadString(v ref, b []byte) // valueLoadString(v ref, b []byte)
"syscall/js.valueLoadString": (v_ref, slice_ptr, slice_len, slice_cap) => { "syscall/js.valueLoadString": (sp) => {
const str = unboxValue(v_ref); sp >>>= 0;
loadSlice(slice_ptr, slice_len, slice_cap).set(str); const str = loadValue(sp + 8);
loadSlice(sp + 16).set(str);
}, },
// func valueInstanceOf(v ref, t ref) bool // func valueInstanceOf(v ref, t ref) bool
"syscall/js.valueInstanceOf": (v_ref, t_ref) => { "syscall/js.valueInstanceOf": (sp) => {
return unboxValue(v_ref) instanceof unboxValue(t_ref); sp >>>= 0;
this.mem.setUint8(sp + 24, (loadValue(sp + 8) instanceof loadValue(sp + 16)) ? 1 : 0);
}, },
// func copyBytesToGo(dst []byte, src ref) (int, bool) // func copyBytesToGo(dst []byte, src ref) (int, bool)
"syscall/js.copyBytesToGo": (ret_addr, dest_addr, dest_len, dest_cap, src_ref) => { "syscall/js.copyBytesToGo": (sp) => {
let num_bytes_copied_addr = ret_addr; sp >>>= 0;
let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable const dst = loadSlice(sp + 8);
const src = loadValue(sp + 32);
const dst = loadSlice(dest_addr, dest_len);
const src = unboxValue(src_ref);
if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) { if (!(src instanceof Uint8Array || src instanceof Uint8ClampedArray)) {
mem().setUint8(returned_status_addr, 0); // Return "not ok" status this.mem.setUint8(sp + 48, 0);
return; return;
} }
const toCopy = src.subarray(0, dst.length); const toCopy = src.subarray(0, dst.length);
dst.set(toCopy); dst.set(toCopy);
mem().setUint32(num_bytes_copied_addr, toCopy.length, true); setInt64(sp + 40, toCopy.length);
mem().setUint8(returned_status_addr, 1); // Return "ok" status this.mem.setUint8(sp + 48, 1);
}, },
// copyBytesToJS(dst ref, src []byte) (int, bool) // func copyBytesToJS(dst ref, src []byte) (int, bool)
// Originally copied from upstream Go project, then modified: "syscall/js.copyBytesToJS": (sp) => {
// https://github.com/golang/go/blob/3f995c3f3b43033013013e6c7ccc93a9b1411ca9/misc/wasm/wasm_exec.js#L404-L416 sp >>>= 0;
"syscall/js.copyBytesToJS": (ret_addr, dst_ref, src_addr, src_len, src_cap) => { const dst = loadValue(sp + 8);
let num_bytes_copied_addr = ret_addr; const src = loadSlice(sp + 16);
let returned_status_addr = ret_addr + 4; // Address of returned boolean status variable
const dst = unboxValue(dst_ref);
const src = loadSlice(src_addr, src_len);
if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) { if (!(dst instanceof Uint8Array || dst instanceof Uint8ClampedArray)) {
mem().setUint8(returned_status_addr, 0); // Return "not ok" status this.mem.setUint8(sp + 48, 0);
return; return;
} }
const toCopy = src.subarray(0, dst.length); const toCopy = src.subarray(0, dst.length);
dst.set(toCopy); dst.set(toCopy);
mem().setUint32(num_bytes_copied_addr, toCopy.length, true); setInt64(sp + 40, toCopy.length);
mem().setUint8(returned_status_addr, 1); // Return "ok" status this.mem.setUint8(sp + 48, 1);
},
"debug": (value) => {
console.log(value);
}, },
} }
}; };
// Go 1.20 uses 'env'. Go 1.21 uses 'gojs'.
// For compatibility, we use both as long as Go 1.20 is supported.
this.importObject.env = this.importObject.gojs;
} }
async run(instance) { async run(instance) {
if (!(instance instanceof WebAssembly.Instance)) {
throw new Error("Go.run: WebAssembly.Instance expected");
}
this._inst = instance; 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 this._values = [ // JS values that Go currently has references to, indexed by reference id
NaN, NaN,
0, 0,
null, null,
true, true,
false, false,
global, globalThis,
this, this,
]; ];
this._goRefCounts = []; // number of references that Go has to a JS value, indexed by reference id 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 this._ids = new Map([ // mapping from JS values to reference ids
this._idPool = []; // unused ids that have been garbage collected [0, 1],
this.exited = false; // whether the Go program has exited [null, 2],
this.exitCode = 0; [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
if (this._inst.exports._start) { // Pass command line arguments and environment variables to WebAssembly by writing them to the linear memory.
let exitPromise = new Promise((resolve, reject) => { let offset = 4096;
this._resolveExitPromise = resolve;
});
// Run program, but catch the wasmExit exception that's thrown const strPtr = (str) => {
// to return back here. const ptr = offset;
try { const bytes = encoder.encode(str + "\0");
this._inst.exports._start(); new Uint8Array(this.mem.buffer, offset, bytes.length).set(bytes);
} catch (e) { offset += bytes.length;
if (e !== wasmExit) throw e; if (offset % 8 !== 0) {
offset += 8 - (offset % 8);
} }
return ptr;
};
await exitPromise; const argc = this.argv.length;
return this.exitCode;
} else { const argvPtrs = [];
this._inst.exports._initialize(); 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() { _resume() {
if (this.exited) { if (this.exited) {
throw new Error("Go program has already exited"); throw new Error("Go program has already exited");
} }
try { this._inst.exports.resume();
this._inst.exports.resume();
} catch (e) {
if (e !== wasmExit) throw e;
}
if (this.exited) { if (this.exited) {
this._resolveExitPromise(); this._resolveExitPromise();
} }
@@ -528,32 +572,13 @@
}; };
} }
} }
if (
global.require &&
global.require.main === module &&
global.process &&
global.process.versions &&
!global.process.versions.electron
) {
if (process.argv.length != 3) {
console.error("usage: go_js_wasm_exec [wasm binary] [arguments]");
process.exit(1);
}
const go = new Go();
WebAssembly.instantiate(fs.readFileSync(process.argv[2]), go.importObject).then(async (result) => {
let exitCode = await go.run(result.instance);
process.exit(exitCode);
}).catch((err) => {
console.error(err);
process.exit(1);
});
}
})(); })();
// wasm setup code // wasm setup code
export default function init (path) { export default function init (path) {
if (typeof global === "undefined") {
var global = globalThis;
}
return new Promise ((res) => { return new Promise ((res) => {
const go = new Go(); const go = new Go();
const run = (obj) => { const run = (obj) => {
Binary file not shown.
BIN
View File
Binary file not shown.
+2 -2
View File
@@ -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);
@@ -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) {
+3 -3
View File
@@ -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("modules/wfa.wasm.gz");
initInstances(); initInstances();
document.querySelector("#instance-add").addEventListener("click", handleInstanceAddButton); document.querySelector("#instance-add").addEventListener("click", handleInstanceAddButton);
@@ -418,7 +418,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));
} }
+2 -2
View File
@@ -1,4 +1,4 @@
{{/* <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">
@@ -15,7 +15,7 @@
<link rel="stylesheet" href="css/form.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>