cleanup various unused code,
simplify instance action hiding when node is not online
This commit is contained in:
@@ -10,7 +10,7 @@ import (
|
|||||||
"proxmoxaas-dashboard/app/routes"
|
"proxmoxaas-dashboard/app/routes"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/tdewolff/minify"
|
"github.com/tdewolff/minify/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Run() {
|
func Run() {
|
||||||
|
@@ -3,17 +3,19 @@ package common
|
|||||||
import (
|
import (
|
||||||
"io"
|
"io"
|
||||||
|
|
||||||
"github.com/tdewolff/minify"
|
"github.com/tdewolff/minify/v2"
|
||||||
"github.com/tdewolff/minify/css"
|
"github.com/tdewolff/minify/v2/css"
|
||||||
"github.com/tdewolff/minify/html"
|
"github.com/tdewolff/minify/v2/html"
|
||||||
"github.com/tdewolff/minify/js"
|
"github.com/tdewolff/minify/v2/js"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// defines mime type and associated minifier
|
||||||
type MimeType struct {
|
type MimeType struct {
|
||||||
Type string
|
Type 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
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// map file extension to mime types
|
||||||
var MimeTypes = map[string]MimeType{
|
var MimeTypes = map[string]MimeType{
|
||||||
"css": {
|
"css": {
|
||||||
Type: "text/css",
|
Type: "text/css",
|
||||||
|
@@ -16,7 +16,7 @@ import (
|
|||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/gin-gonic/gin"
|
"github.com/gin-gonic/gin"
|
||||||
"github.com/tdewolff/minify"
|
"github.com/tdewolff/minify/v2"
|
||||||
)
|
)
|
||||||
|
|
||||||
var TMPL *template.Template
|
var TMPL *template.Template
|
||||||
|
2
go.mod
2
go.mod
@@ -35,7 +35,9 @@ require (
|
|||||||
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.2.4 // indirect
|
github.com/pelletier/go-toml/v2 v2.2.4 // indirect
|
||||||
|
github.com/tdewolff/minify/v2 v2.23.1 // indirect
|
||||||
github.com/tdewolff/parse v2.3.4+incompatible // indirect
|
github.com/tdewolff/parse v2.3.4+incompatible // indirect
|
||||||
|
github.com/tdewolff/parse/v2 v2.7.23 // indirect
|
||||||
github.com/tdewolff/test v1.0.11 // indirect
|
github.com/tdewolff/test v1.0.11 // 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.2.12 // indirect
|
github.com/ugorji/go/codec v1.2.12 // indirect
|
||||||
|
@@ -77,4 +77,6 @@ input[type="radio"] {
|
|||||||
|
|
||||||
dialog {
|
dialog {
|
||||||
max-width: calc(min(50%, 80ch));
|
max-width: calc(min(50%, 80ch));
|
||||||
|
background-color: var(--main-bg-color);
|
||||||
|
color: var(--main-text-color);
|
||||||
}
|
}
|
@@ -61,13 +61,10 @@ body {
|
|||||||
grid-template-rows: auto 1fr;
|
grid-template-rows: auto 1fr;
|
||||||
}
|
}
|
||||||
|
|
||||||
main, dialog {
|
main {
|
||||||
max-width: 100vw;
|
max-width: 100vw;
|
||||||
background-color: var(--main-bg-color);
|
background-color: var(--main-bg-color);
|
||||||
color: var(--main-text-color);
|
color: var(--main-text-color);
|
||||||
}
|
|
||||||
|
|
||||||
main {
|
|
||||||
padding: 0 16px 16px;
|
padding: 0 16px 16px;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -25,7 +25,7 @@
|
|||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<section>
|
<section>
|
||||||
<h2 id="name"><a href="index">Instances</a> / {{.config.Name}}</h2>
|
<h2><a href="index">Instances</a> / {{.config.Name}}</h2>
|
||||||
<form>
|
<form>
|
||||||
<fieldset class="w3-card w3-padding">
|
<fieldset class="w3-card w3-padding">
|
||||||
<legend>Resources</legend>
|
<legend>Resources</legend>
|
||||||
|
@@ -12,10 +12,10 @@ async function init () {
|
|||||||
function handlePasswordChangeForm () {
|
function handlePasswordChangeForm () {
|
||||||
const body = `
|
const body = `
|
||||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||||
<label for="new-password">New Password</label>
|
<label for="new-password">New Password</label>
|
||||||
<input class="w3-input w3-border" id="new-password" name="new-password" type="password"required>
|
<input class="w3-input w3-border" id="new-password" name="new-password" type="password"required>
|
||||||
<label for="confirm-password">Confirm Password</label>
|
<label for="confirm-password">Confirm Password</label>
|
||||||
<input class="w3-input w3-border" id="confirm-password" name="confirm-password" type="password" required>
|
<input class="w3-input w3-border" id="confirm-password" name="confirm-password" type="password" required>
|
||||||
</form>
|
</form>
|
||||||
`;
|
`;
|
||||||
const d = dialog("Change Password", body, async (result, form) => {
|
const d = dialog("Change Password", body, async (result, form) => {
|
||||||
|
@@ -6,7 +6,6 @@ window.addEventListener("DOMContentLoaded", init);
|
|||||||
let node;
|
let node;
|
||||||
let type;
|
let type;
|
||||||
let vmid;
|
let vmid;
|
||||||
let config;
|
|
||||||
|
|
||||||
async function init () {
|
async function init () {
|
||||||
setAppearance();
|
setAppearance();
|
||||||
@@ -16,11 +15,6 @@ async function init () {
|
|||||||
type = uriData.type;
|
type = uriData.type;
|
||||||
vmid = uriData.vmid;
|
vmid = uriData.vmid;
|
||||||
|
|
||||||
await getConfig();
|
|
||||||
|
|
||||||
const name = type === "qemu" ? "name" : "hostname";
|
|
||||||
document.querySelector("#name").innerHTML = document.querySelector("#name").innerHTML.replace("%{vmname}", config.data[name]);
|
|
||||||
|
|
||||||
initVolumes();
|
initVolumes();
|
||||||
initNetworks();
|
initNetworks();
|
||||||
initDevices();
|
initDevices();
|
||||||
@@ -28,10 +22,6 @@ async function init () {
|
|||||||
document.querySelector("#exit").addEventListener("click", handleFormExit);
|
document.querySelector("#exit").addEventListener("click", handleFormExit);
|
||||||
}
|
}
|
||||||
|
|
||||||
async function getConfig () {
|
|
||||||
config = await requestPVE(`/nodes/${node}/${type}/${vmid}/config`, "GET");
|
|
||||||
}
|
|
||||||
|
|
||||||
class VolumeAction extends HTMLElement {
|
class VolumeAction extends HTMLElement {
|
||||||
shadowRoot = null;
|
shadowRoot = null;
|
||||||
|
|
||||||
@@ -83,7 +73,7 @@ class VolumeAction extends HTMLElement {
|
|||||||
const body = `
|
const body = `
|
||||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||||
<label for="device">${type === "qemu" ? "SCSI" : "MP"}</label>
|
<label for="device">${type === "qemu" ? "SCSI" : "MP"}</label>
|
||||||
<input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="${type === "qemu" ? "5" : "255"}" required>
|
<input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="${type === "qemu" ? "30" : "255"}" required>
|
||||||
</form>
|
</form>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
@@ -113,7 +103,7 @@ class VolumeAction extends HTMLElement {
|
|||||||
<label for="size-increment">Size Increment (GiB)</label>
|
<label for="size-increment">Size Increment (GiB)</label>
|
||||||
<input class="w3-input w3-border" name="size-increment" id="size-increment" type="number" min="0" max="131072">
|
<input class="w3-input w3-border" name="size-increment" id="size-increment" type="number" min="0" max="131072">
|
||||||
</form>
|
</form>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
dialog(header, body, async (result, form) => {
|
dialog(header, body, async (result, form) => {
|
||||||
if (result === "confirm") {
|
if (result === "confirm") {
|
||||||
@@ -135,9 +125,7 @@ class VolumeAction extends HTMLElement {
|
|||||||
async handleDiskMove () {
|
async handleDiskMove () {
|
||||||
const content = type === "qemu" ? "images" : "rootdir";
|
const content = type === "qemu" ? "images" : "rootdir";
|
||||||
const storage = await requestPVE(`/nodes/${node}/storage`, "GET");
|
const storage = await requestPVE(`/nodes/${node}/storage`, "GET");
|
||||||
|
|
||||||
const header = `Move ${this.dataset.volume}`;
|
const header = `Move ${this.dataset.volume}`;
|
||||||
|
|
||||||
let options = "";
|
let options = "";
|
||||||
storage.data.forEach((element) => {
|
storage.data.forEach((element) => {
|
||||||
if (element.content.includes(content)) {
|
if (element.content.includes(content)) {
|
||||||
@@ -215,9 +203,7 @@ async function refreshVolumes () {
|
|||||||
async function handleDiskAdd () {
|
async function handleDiskAdd () {
|
||||||
const content = type === "qemu" ? "images" : "rootdir";
|
const content = type === "qemu" ? "images" : "rootdir";
|
||||||
const storage = await requestPVE(`/nodes/${node}/storage`, "GET");
|
const storage = await requestPVE(`/nodes/${node}/storage`, "GET");
|
||||||
|
|
||||||
const header = "Create New Disk";
|
const header = "Create New Disk";
|
||||||
|
|
||||||
let options = "";
|
let options = "";
|
||||||
storage.data.forEach((element) => {
|
storage.data.forEach((element) => {
|
||||||
if (element.content.includes(content)) {
|
if (element.content.includes(content)) {
|
||||||
@@ -228,7 +214,7 @@ async function handleDiskAdd () {
|
|||||||
|
|
||||||
const body = `
|
const body = `
|
||||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||||
<label for="device">${type === "qemu" ? "SCSI" : "MP"}</label><input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="${type === "qemu" ? "5" : "255"}" value="0" required>
|
<label for="device">${type === "qemu" ? "SCSI" : "MP"}</label><input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="${type === "qemu" ? "30" : "255"}" value="0" required>
|
||||||
${select}
|
${select}
|
||||||
<label for="size">Size (GiB)</label><input class="w3-input w3-border" name="size" id="size" type="number" min="0" max="131072" required>
|
<label for="size">Size (GiB)</label><input class="w3-input w3-border" name="size" id="size" type="number" min="0" max="131072" required>
|
||||||
</form>
|
</form>
|
||||||
@@ -255,9 +241,7 @@ async function handleDiskAdd () {
|
|||||||
|
|
||||||
async function handleCDAdd () {
|
async function handleCDAdd () {
|
||||||
const isos = await requestAPI("/user/vm-isos", "GET");
|
const isos = await requestAPI("/user/vm-isos", "GET");
|
||||||
|
|
||||||
const header = "Mount a CDROM";
|
const header = "Mount a CDROM";
|
||||||
|
|
||||||
const body = `
|
const body = `
|
||||||
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
<form method="dialog" class="input-grid" style="grid-template-columns: auto 1fr;" id="form">
|
||||||
<label for="device">IDE</label><input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="3" required>
|
<label for="device">IDE</label><input class="w3-input w3-border" name="device" id="device" type="number" min="0" max="3" required>
|
||||||
@@ -341,7 +325,6 @@ class NetworkAction extends HTMLElement {
|
|||||||
const netID = this.dataset.network;
|
const netID = this.dataset.network;
|
||||||
const header = `Delete ${netID}`;
|
const header = `Delete ${netID}`;
|
||||||
const body = "";
|
const body = "";
|
||||||
|
|
||||||
dialog(header, body, async (result, form) => {
|
dialog(header, body, async (result, form) => {
|
||||||
if (result === "confirm") {
|
if (result === "confirm") {
|
||||||
setSVGSrc(document.querySelector(`svg[data-network="${netID}"]`), "images/status/loading.svg");
|
setSVGSrc(document.querySelector(`svg[data-network="${netID}"]`), "images/status/loading.svg");
|
||||||
@@ -388,7 +371,6 @@ async function handleNetworkAdd () {
|
|||||||
body += "<label for=\"name\">Interface Name</label><input type=\"text\" id=\"name\" name=\"name\" class=\"w3-input w3-border\">";
|
body += "<label for=\"name\">Interface Name</label><input type=\"text\" id=\"name\" name=\"name\" class=\"w3-input w3-border\">";
|
||||||
}
|
}
|
||||||
body += "</form>";
|
body += "</form>";
|
||||||
|
|
||||||
dialog(header, body, async (result, form) => {
|
dialog(header, body, async (result, form) => {
|
||||||
if (result === "confirm") {
|
if (result === "confirm") {
|
||||||
const body = {
|
const body = {
|
||||||
@@ -468,7 +450,6 @@ class DeviceAction extends HTMLElement {
|
|||||||
const deviceID = this.dataset.device;
|
const deviceID = this.dataset.device;
|
||||||
const header = `Remove Expansion Card ${deviceID}`;
|
const header = `Remove Expansion Card ${deviceID}`;
|
||||||
const body = "";
|
const body = "";
|
||||||
|
|
||||||
dialog(header, body, async (result, form) => {
|
dialog(header, body, async (result, form) => {
|
||||||
if (result === "confirm") {
|
if (result === "confirm") {
|
||||||
this.setStatusLoading();
|
this.setStatusLoading();
|
||||||
@@ -514,7 +495,6 @@ async function handleDeviceAdd () {
|
|||||||
<label for="pcie">PCI-Express</label><input type="checkbox" id="pcie" name="pcie" class="w3-input w3-border">
|
<label for="pcie">PCI-Express</label><input type="checkbox" id="pcie" name="pcie" class="w3-input w3-border">
|
||||||
</form>
|
</form>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
const d = dialog(header, body, async (result, form) => {
|
const d = dialog(header, body, async (result, form) => {
|
||||||
if (result === "confirm") {
|
if (result === "confirm") {
|
||||||
const hostpci = form.get("hostpci");
|
const hostpci = form.get("hostpci");
|
||||||
|
@@ -3,6 +3,20 @@ import { alert, dialog } from "./dialog.js";
|
|||||||
import { setupClientSync } from "./clientsync.js";
|
import { setupClientSync } from "./clientsync.js";
|
||||||
import wfaInit from "../modules/wfa.js";
|
import wfaInit from "../modules/wfa.js";
|
||||||
|
|
||||||
|
window.addEventListener("DOMContentLoaded", init);
|
||||||
|
|
||||||
|
async function init () {
|
||||||
|
setAppearance();
|
||||||
|
|
||||||
|
wfaInit("modules/wfa.wasm");
|
||||||
|
initInstances();
|
||||||
|
|
||||||
|
document.querySelector("#instance-add").addEventListener("click", handleInstanceAdd);
|
||||||
|
document.querySelector("#vm-search").addEventListener("input", sortInstances);
|
||||||
|
|
||||||
|
setupClientSync(refreshInstances);
|
||||||
|
}
|
||||||
|
|
||||||
class InstanceCard extends HTMLElement {
|
class InstanceCard extends HTMLElement {
|
||||||
actionLock = false;
|
actionLock = false;
|
||||||
shadowRoot = null;
|
shadowRoot = null;
|
||||||
@@ -125,20 +139,12 @@ class InstanceCard extends HTMLElement {
|
|||||||
if (deleteButton.classList.contains("clickable")) {
|
if (deleteButton.classList.contains("clickable")) {
|
||||||
deleteButton.onclick = this.handleDeleteButton.bind(this);
|
deleteButton.onclick = this.handleDeleteButton.bind(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.node.status !== "online") {
|
|
||||||
powerButton.classList.add("hidden");
|
|
||||||
configButton.classList.add("hidden");
|
|
||||||
consoleButton.classList.add("hidden");
|
|
||||||
deleteButton.classList.add("hidden");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async handlePowerButton () {
|
async handlePowerButton () {
|
||||||
if (!this.actionLock) {
|
if (!this.actionLock) {
|
||||||
const header = `${this.status === "running" ? "Stop" : "Start"} VM ${this.vmid}`;
|
const header = `${this.status === "running" ? "Stop" : "Start"} VM ${this.vmid}`;
|
||||||
const body = `<p>Are you sure you want to ${this.status === "running" ? "stop" : "start"} VM ${this.vmid}</p>`;
|
const body = `<p>Are you sure you want to ${this.status === "running" ? "stop" : "start"} VM ${this.vmid}</p>`;
|
||||||
|
|
||||||
dialog(header, body, async (result, form) => {
|
dialog(header, body, async (result, form) => {
|
||||||
if (result === "confirm") {
|
if (result === "confirm") {
|
||||||
this.actionLock = true;
|
this.actionLock = true;
|
||||||
@@ -226,22 +232,8 @@ class InstanceCard extends HTMLElement {
|
|||||||
|
|
||||||
customElements.define("instance-card", InstanceCard);
|
customElements.define("instance-card", InstanceCard);
|
||||||
|
|
||||||
window.addEventListener("DOMContentLoaded", init);
|
|
||||||
|
|
||||||
async function init () {
|
|
||||||
setAppearance();
|
|
||||||
|
|
||||||
wfaInit("modules/wfa.wasm");
|
|
||||||
initInstances();
|
|
||||||
|
|
||||||
document.querySelector("#instance-add").addEventListener("click", handleInstanceAdd);
|
|
||||||
document.querySelector("#vm-search").addEventListener("input", sortInstances);
|
|
||||||
|
|
||||||
setupClientSync(refreshInstances);
|
|
||||||
}
|
|
||||||
|
|
||||||
async function getInstancesFragment () {
|
async function getInstancesFragment () {
|
||||||
return await requestDash("/index/instances", "GET")
|
return await requestDash("/index/instances", "GET");
|
||||||
}
|
}
|
||||||
|
|
||||||
async function refreshInstances () {
|
async function refreshInstances () {
|
||||||
|
@@ -1,91 +1,3 @@
|
|||||||
export const resourcesConfig = {
|
|
||||||
cpu: {
|
|
||||||
name: "CPU Type",
|
|
||||||
icon: "images/resources/cpu.svg",
|
|
||||||
id: "proctype",
|
|
||||||
unitText: null
|
|
||||||
},
|
|
||||||
cores: {
|
|
||||||
name: "CPU Amount",
|
|
||||||
icon: "images/resources/cpu.svg",
|
|
||||||
id: "cores",
|
|
||||||
unitText: "Cores"
|
|
||||||
},
|
|
||||||
memory: {
|
|
||||||
name: "Memory",
|
|
||||||
icon: "images/resources/ram.svg",
|
|
||||||
id: "ram",
|
|
||||||
unitText: "MiB"
|
|
||||||
},
|
|
||||||
swap: {
|
|
||||||
name: "Swap",
|
|
||||||
icon: "images/resources/swap.svg",
|
|
||||||
id: "swap",
|
|
||||||
unitText: "MiB"
|
|
||||||
},
|
|
||||||
disk: {
|
|
||||||
actionBarOrder: ["move", "resize", "detach_attach", "delete"],
|
|
||||||
lxc: {
|
|
||||||
prefixOrder: ["rootfs", "mp", "unused"],
|
|
||||||
rootfs: { name: "ROOTFS", icon: "images/resources/drive.svg", actions: ["move", "resize"] },
|
|
||||||
mp: { name: "MP", icon: "images/resources/drive.svg", actions: ["detach", "move", "reassign", "resize"] },
|
|
||||||
unused: { name: "UNUSED", icon: "images/resources/drive.svg", actions: ["attach", "delete", "reassign"] }
|
|
||||||
},
|
|
||||||
qemu: {
|
|
||||||
prefixOrder: ["ide", "sata", "scsi", "unused"],
|
|
||||||
ide: { name: "IDE", icon: "images/resources/disk.svg", actions: ["delete"] },
|
|
||||||
sata: { name: "SATA", icon: "images/resources/drive.svg", actions: ["detach", "move", "reassign", "resize"] },
|
|
||||||
scsi: { name: "SCSI", icon: "images/resources/drive.svg", actions: ["detach", "move", "reassign", "resize"] },
|
|
||||||
unused: { name: "UNUSED", icon: "images/resources/drive.svg", actions: ["attach", "delete", "reassign"] }
|
|
||||||
},
|
|
||||||
actions: {
|
|
||||||
attach: {
|
|
||||||
src: "images/actions/disk/attach.svg",
|
|
||||||
title: "Attach Disk"
|
|
||||||
},
|
|
||||||
detach: {
|
|
||||||
src: "images/actions/disk/detach.svg",
|
|
||||||
title: "Detach Disk"
|
|
||||||
},
|
|
||||||
delete: null
|
|
||||||
}
|
|
||||||
},
|
|
||||||
network: {
|
|
||||||
name: "Network",
|
|
||||||
icon: "images/resources/network.svg",
|
|
||||||
id: "network",
|
|
||||||
unitText: "MB/s",
|
|
||||||
prefix: "net"
|
|
||||||
},
|
|
||||||
pci: {
|
|
||||||
name: "Devices",
|
|
||||||
icon: "images/resources/device.svg",
|
|
||||||
id: "devices",
|
|
||||||
unitText: null,
|
|
||||||
prefix: "hostpci"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export const bootConfig = {
|
|
||||||
eligiblePrefixes: ["ide", "sata", "scsi", "net"],
|
|
||||||
ide: {
|
|
||||||
icon: "images/resources/disk.svg",
|
|
||||||
alt: "IDE Bootable Icon"
|
|
||||||
},
|
|
||||||
sata: {
|
|
||||||
icon: "images/resources/drive.svg",
|
|
||||||
alt: "SATA Bootable Icon"
|
|
||||||
},
|
|
||||||
scsi: {
|
|
||||||
icon: "images/resources/drive.svg",
|
|
||||||
alt: "SCSI Bootable Icon"
|
|
||||||
},
|
|
||||||
net: {
|
|
||||||
icon: "images/resources/network.svg",
|
|
||||||
alt: "NET Bootable Icon"
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
export function setCookie (cname, cval) {
|
export function setCookie (cname, cval) {
|
||||||
document.cookie = `${cname}=${cval}`;
|
document.cookie = `${cname}=${cval}`;
|
||||||
}
|
}
|
||||||
@@ -301,135 +213,3 @@ export function setSVGSrc (svgElem, href) {
|
|||||||
export function setSVGAlt (svgElem, alt) {
|
export function setSVGAlt (svgElem, alt) {
|
||||||
svgElem.setAttribute("aria-label", alt);
|
svgElem.setAttribute("aria-label", alt);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Simple object check.
|
|
||||||
* @param item
|
|
||||||
* @returns {boolean}
|
|
||||||
*/
|
|
||||||
export function isObject (item) {
|
|
||||||
return (item && typeof item === "object" && !Array.isArray(item));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deep merge two objects.
|
|
||||||
* @param target
|
|
||||||
* @param ...sources
|
|
||||||
*/
|
|
||||||
export function mergeDeep (target, ...sources) {
|
|
||||||
if (!sources.length) return target;
|
|
||||||
const source = sources.shift();
|
|
||||||
|
|
||||||
if (isObject(target) && isObject(source)) {
|
|
||||||
for (const key in source) {
|
|
||||||
if (isObject(source[key])) {
|
|
||||||
if (!target[key]) Object.assign(target, { [key]: {} });
|
|
||||||
mergeDeep(target[key], source[key]);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Object.assign(target, { [key]: source[key] });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return mergeDeep(target, ...sources);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Checks if object or array is empty
|
|
||||||
* @param {*} obj
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function isEmpty (obj) {
|
|
||||||
if (obj instanceof Array) {
|
|
||||||
return obj.length === 0;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
for (const prop in obj) {
|
|
||||||
if (Object.hasOwn(obj, prop)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
export function addResourceLine (resourceConfig, field, attributesOverride, labelPrefix = null) {
|
|
||||||
const iconHref = resourceConfig.icon;
|
|
||||||
const elementType = resourceConfig.element;
|
|
||||||
const labelText = labelPrefix ? `${labelPrefix} ${resourceConfig.name}` : resourceConfig.name;
|
|
||||||
const id = resourceConfig.id;
|
|
||||||
const unitText = resourceConfig.unitText;
|
|
||||||
const attributes = { ...(resourceConfig.attributes), ...(attributesOverride) };
|
|
||||||
|
|
||||||
const icon = document.createElementNS("http://www.w3.org/2000/svg", "svg");
|
|
||||||
setSVGSrc(icon, iconHref);
|
|
||||||
setSVGAlt(icon, labelText);
|
|
||||||
field.append(icon);
|
|
||||||
|
|
||||||
const label = document.createElement("label");
|
|
||||||
label.innerText = labelText;
|
|
||||||
label.htmlFor = id;
|
|
||||||
field.append(label);
|
|
||||||
|
|
||||||
let element;
|
|
||||||
|
|
||||||
if (elementType === "input") {
|
|
||||||
element = document.createElement("input");
|
|
||||||
for (const k in attributes) {
|
|
||||||
element.setAttribute(k, attributes[k]);
|
|
||||||
}
|
|
||||||
element.id = id;
|
|
||||||
element.name = id;
|
|
||||||
element.required = true;
|
|
||||||
element.classList.add("w3-input");
|
|
||||||
element.classList.add("w3-border");
|
|
||||||
field.append(element);
|
|
||||||
}
|
|
||||||
else if (elementType === "select" || elementType === "multi-select") {
|
|
||||||
element = document.createElement("select");
|
|
||||||
for (const option of attributes.options) {
|
|
||||||
element.append(new Option(option));
|
|
||||||
}
|
|
||||||
element.value = attributes.value;
|
|
||||||
element.id = id;
|
|
||||||
element.name = id;
|
|
||||||
element.required = true;
|
|
||||||
element.classList.add("w3-select");
|
|
||||||
element.classList.add("w3-border");
|
|
||||||
if (elementType === "multi-select") {
|
|
||||||
element.setAttribute("multiple", true);
|
|
||||||
}
|
|
||||||
field.append(element);
|
|
||||||
}
|
|
||||||
else if (customElements.get(elementType)) {
|
|
||||||
element = document.createElement(elementType);
|
|
||||||
if (attributes.options) {
|
|
||||||
for (const option of attributes.options) {
|
|
||||||
element.append(new Option(option));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
element.value = attributes.value;
|
|
||||||
element.id = id;
|
|
||||||
element.name = id;
|
|
||||||
element.required = true;
|
|
||||||
field.append(element);
|
|
||||||
}
|
|
||||||
|
|
||||||
let unit;
|
|
||||||
|
|
||||||
if (unitText) {
|
|
||||||
unit = document.createElement("p");
|
|
||||||
unit.innerText = unitText;
|
|
||||||
field.append(unit);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
unit = document.createElement("div");
|
|
||||||
unit.classList.add("hidden");
|
|
||||||
field.append(unit);
|
|
||||||
}
|
|
||||||
|
|
||||||
return { icon, label, element, unit };
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
@@ -38,17 +38,17 @@
|
|||||||
<p>{{.NodeStatus}}</p>
|
<p>{{.NodeStatus}}</p>
|
||||||
</div>
|
</div>
|
||||||
<div class="w3-col l2 m2 s6 flex row nowrap" style="height: 1lh;">
|
<div class="w3-col l2 m2 s6 flex row nowrap" style="height: 1lh;">
|
||||||
{{if eq .Status "running"}}
|
{{if and (eq .NodeStatus "online") (eq .Status "running")}}
|
||||||
<svg id="power-btn" class="clickable" aria-label="shutdown instance"><use href="images/actions/instance/stop.svg#symb"></svg>
|
<svg id="power-btn" class="clickable" aria-label="shutdown instance"><use href="images/actions/instance/stop.svg#symb"></svg>
|
||||||
<svg id="configure-btn" aria-label=""><use href="images/actions/instance/config-inactive.svg#symb"></svg>
|
<svg id="configure-btn" aria-label=""><use href="images/actions/instance/config-inactive.svg#symb"></svg>
|
||||||
<svg id="console-btn" class="clickable" aria-label="open console"><use href="images/actions/instance/console-active.svg#symb"></svg>
|
<svg id="console-btn" class="clickable" aria-label="open console"><use href="images/actions/instance/console-active.svg#symb"></svg>
|
||||||
<svg id="delete-btn" aria-label=""><use href="images/actions/instance/delete-inactive.svg#symb"></svg>
|
<svg id="delete-btn" aria-label=""><use href="images/actions/instance/delete-inactive.svg#symb"></svg>
|
||||||
{{else if eq .Status "stopped"}}
|
{{else if and (eq .NodeStatus "online") (eq .Status "stopped")}}
|
||||||
<svg id="power-btn" class="clickable" aria-label="start instance"><use href="images/actions/instance/start.svg#symb"></svg>
|
<svg id="power-btn" class="clickable" aria-label="start instance"><use href="images/actions/instance/start.svg#symb"></svg>
|
||||||
<svg id="configure-btn" class="clickable" aria-label="change configuration"><use href="images/actions/instance/config-active.svg#symb"></svg>
|
<svg id="configure-btn" class="clickable" aria-label="change configuration"><use href="images/actions/instance/config-active.svg#symb"></svg>
|
||||||
<svg id="console-btn" aria-label=""><use href="images/actions/instance/console-inactive.svg#symb"></svg>
|
<svg id="console-btn" aria-label=""><use href="images/actions/instance/console-inactive.svg#symb"></svg>
|
||||||
<svg id="delete-btn" class="clickable" aria-label="delete instance"><use href="images/actions/instance/delete-active.svg#symb"></svg>
|
<svg id="delete-btn" class="clickable" aria-label="delete instance"><use href="images/actions/instance/delete-active.svg#symb"></svg>
|
||||||
{{else if eq .Status "loading"}}
|
{{else if and (eq .NodeStatus "online") (eq .Status "loading")}}
|
||||||
<svg id="power-btn" aria-label=""><use href="images/actions/instance/loading.svg#symb"></svg>
|
<svg id="power-btn" aria-label=""><use href="images/actions/instance/loading.svg#symb"></svg>
|
||||||
<svg id="configure-btn" aria-label=""><use href="images/actions/instance/config-inactive.svg#symb"></svg>
|
<svg id="configure-btn" aria-label=""><use href="images/actions/instance/config-inactive.svg#symb"></svg>
|
||||||
<svg id="console-btn" aria-label=""><use href="images/actions/instance/console-inactive.svg#symb"></svg>
|
<svg id="console-btn" aria-label=""><use href="images/actions/instance/console-inactive.svg#symb"></svg>
|
||||||
|
Reference in New Issue
Block a user