diff --git a/account.html b/account.html
index 0125233..168b92f 100644
--- a/account.html
+++ b/account.html
@@ -50,7 +50,6 @@
Instances
Account
Settings
- Admin
Logout
diff --git a/admin.html b/admin.html
deleted file mode 100644
index 8e04032..0000000
--- a/admin.html
+++ /dev/null
@@ -1,66 +0,0 @@
-
-
-
-
-
- proxmox - dashboard
-
-
-
-
-
-
-
-
-
-
- Admin
-
-
-
Users
-
-
-
-
-
User
-
Groups
-
Admin
-
Actions
-
-
-
-
-
-
-
Groups
-
-
-
-
-
Group
-
Members
-
Actions
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/index.html b/index.html
index 698a691..17e814f 100644
--- a/index.html
+++ b/index.html
@@ -40,7 +40,6 @@
Instances
Account
Settings
- Admin
Logout
diff --git a/config.html b/instance.html
similarity index 94%
rename from config.html
rename to instance.html
index 98089bd..e06d1fc 100644
--- a/config.html
+++ b/instance.html
@@ -10,7 +10,7 @@
-
+
@@ -32,7 +32,6 @@
Instances
Account
Settings
- Admin
Logout
diff --git a/scripts/admin.js b/scripts/admin.js
deleted file mode 100644
index 283fed8..0000000
--- a/scripts/admin.js
+++ /dev/null
@@ -1,195 +0,0 @@
-import { setTitleAndHeader, setAppearance, requestAPI, goToPage, isEmpty } from "./utils.js";
-
-window.addEventListener("DOMContentLoaded", init);
-
-async function init () {
- setAppearance();
- setTitleAndHeader();
-
- const cookie = document.cookie;
- if (cookie === "") {
- goToPage("login.html");
- }
-
- document.querySelector("#user-add").addEventListener("click", handleUserAdd);
- document.querySelector("#group-add").addEventListener("click", handleGroupAdd);
-
- await getUsers();
- await getGroups();
-}
-
-async function getUsers () {
- const users = (await requestAPI("/access/users")).users;
- const usersContainer = document.querySelector("#users-container");
- for (const user of Object.keys(users)) {
- const newUserCard = document.createElement("user-card");
- users[user].username = user;
- newUserCard.data = users[user];
- usersContainer.append(newUserCard);
- }
-}
-
-async function getGroups () {
- const groups = (await requestAPI("/access/groups")).groups;
- const groupsContainer = document.querySelector("#groups-container");
- for (const group of Object.keys(groups)) {
- const newGroupCard = document.createElement("group-card");
- groups[group].groupname = group;
- newGroupCard.data = groups[group];
- groupsContainer.append(newGroupCard);
- }
-}
-
-class UserCard extends HTMLElement {
- constructor () {
- super();
- this.attachShadow({ mode: "open" });
- this.shadowRoot.innerHTML = `
-
-
-
-
-
- `;
-
- const configButton = this.shadowRoot.querySelector("#config-btn");
- configButton.onclick = this.handleConfigButton.bind(this);
-
- const deleteButton = this.shadowRoot.querySelector("#delete-btn");
- deleteButton.onclick = this.handleDeleteButton.bind(this);
- }
-
- get data () {
- return {
- username: this.username,
- groups: this.groups,
- admin: this.admin
- };
- }
-
- set data (data) {
- this.username = data.username;
- this.groups = this.#getGroupsFromAttribute(data.attributes.memberOf);
- this.admin = data.cluster.admin;
- this.update();
- }
-
- #getGroupsFromAttribute (attribute) {
- return Array.from(attribute, (e) => e.split("cn=")[1].split(",")[0]);
- }
-
- update () {
- const nameParagraph = this.shadowRoot.querySelector("#user-name");
- nameParagraph.innerText = this.username;
-
- const groupsParagraph = this.shadowRoot.querySelector("#user-groups");
- if (isEmpty(this.groups)) {
- groupsParagraph.innerHTML = " ";
- }
- else {
- groupsParagraph.innerText = this.groups.toString();
- }
-
- const adminParagraph = this.shadowRoot.querySelector("#user-admin");
- adminParagraph.innerText = this.admin;
- }
-
- handleConfigButton () {
- goToPage("user.html", { username: this.username });
- }
-
- handleDeleteButton () {
- // TODO
- }
-}
-
-class GroupCard extends HTMLElement {
- constructor () {
- super();
- this.attachShadow({ mode: "open" });
- this.shadowRoot.innerHTML = `
-
-
-
-
-
- `;
-
- const configButton = this.shadowRoot.querySelector("#config-btn");
- configButton.onclick = this.handleConfigButton.bind(this);
-
- const deleteButton = this.shadowRoot.querySelector("#delete-btn");
- deleteButton.onclick = this.handleDeleteButton.bind(this);
- }
-
- get data () {
- return {
- groupname: this.groupname,
- members: this.members
- };
- }
-
- set data (data) {
- this.groupname = data.groupname;
- this.members = this.#getMembersFromAttribute(data.attributes.member);
- this.update();
- }
-
- #getMembersFromAttribute (attribute) {
- const filteredGroups = attribute.filter(e => e !== "");
- return Array.from(filteredGroups, (e) => e.split("uid=")[1].split(",")[0]);
- }
-
- update () {
- const nameParagraph = this.shadowRoot.querySelector("#group-name");
- nameParagraph.innerText = this.groupname;
-
- const membersParagraph = this.shadowRoot.querySelector("#group-members");
- membersParagraph.innerText = `${this.members.toString()}`;
- }
-
- handleConfigButton () {
- // TODO
- }
-
- handleDeleteButton () {
- // TODO
- }
-}
-
-customElements.define("user-card", UserCard);
-customElements.define("group-card", GroupCard);
-
-function handleUserAdd () {
- // TODO
-}
-
-function handleGroupAdd () {
- // TODO
-}
diff --git a/scripts/draggable.js b/scripts/draggable.js
index ed6e07d..af5ed34 100644
--- a/scripts/draggable.js
+++ b/scripts/draggable.js
@@ -65,7 +65,7 @@ class DraggableContainer extends HTMLElement {
get value () {
const value = [];
this.content.childNodes.forEach((element) => {
- if (element.value) {
+ if (element.value !== null) {
value.push(element.value);
}
});
@@ -74,6 +74,7 @@ class DraggableContainer extends HTMLElement {
}
class DraggableItem extends HTMLElement {
+ #value = null;
uuid = null;
constructor () {
super();
@@ -103,6 +104,14 @@ class DraggableItem extends HTMLElement {
set innerHTML (innerHTML) {
this.content.innerHTML = innerHTML;
}
+
+ get value () {
+ return this.#value;
+ }
+
+ set value (value) {
+ this.#value = value;
+ }
}
customElements.define("draggable-container", DraggableContainer);
diff --git a/scripts/index.js b/scripts/index.js
index 7569379..97fd78e 100644
--- a/scripts/index.js
+++ b/scripts/index.js
@@ -1,7 +1,7 @@
import { requestPVE, requestAPI, goToPage, setTitleAndHeader, setAppearance, getSearchSettings, goToURL, instancesConfig, nodesConfig, setSVGSrc, setSVGAlt } from "./utils.js";
import { alert, dialog } from "./dialog.js";
import { setupClientSync } from "./clientsync.js";
-import wfAlign from "../modules/wfa.js";
+import wfaInit from "../modules/wfa.js";
import { PVE } from "../vars.js";
class InstanceCard extends HTMLElement {
@@ -212,7 +212,7 @@ class InstanceCard extends HTMLElement {
handleConfigButton () {
if (!this.actionLock && this.status === "stopped") { // if the action lock is false, and the node is stopped, then navigate to the config page with the node info in the search query
- goToPage("config.html", { node: this.node.name, type: this.type, vmid: this.vmid });
+ goToPage("instance.html", { node: this.node.name, type: this.type, vmid: this.vmid });
}
}
@@ -271,6 +271,8 @@ async function init () {
goToPage("login.html");
}
+ await wfaInit("./modules/wfa.wasm");
+
document.querySelector("#instance-add").addEventListener("click", handleInstanceAdd);
document.querySelector("#vm-search").addEventListener("input", populateInstances);
@@ -329,7 +331,7 @@ async function populateInstances () {
};
criteria = (item, query) => {
// lower is better
- const { score, CIGAR } = wfAlign(query, item, penalties, true);
+ const { score, CIGAR } = global.wfAlign(query, item, penalties, true);
return { score: score / item.length, alignment: CIGAR };
};
}
diff --git a/scripts/config.js b/scripts/instance.js
similarity index 98%
rename from scripts/config.js
rename to scripts/instance.js
index b2e75e3..d8150ee 100644
--- a/scripts/config.js
+++ b/scripts/instance.js
@@ -105,12 +105,12 @@ async function populateResources () {
return a.localeCompare(b);
});
}
- addResourceLine(resourcesConfigPage, field, "cpu", { value: config.data.cpu, options });
+ addResourceLine(resourcesConfigPage.cpu, field, { value: config.data.cpu, options });
}
- addResourceLine(resourcesConfigPage, field, "cores", { value: config.data.cores, min: 1, max: 8192 });
- addResourceLine(resourcesConfigPage, field, "memory", { value: config.data.memory, min: 16, step: 1 });
+ addResourceLine(resourcesConfigPage.cores, field, { value: config.data.cores, min: 1, max: 8192 });
+ addResourceLine(resourcesConfigPage.memory, field, { value: config.data.memory, min: 16, step: 1 });
if (type === "lxc") {
- addResourceLine(resourcesConfigPage, field, "swap", { value: config.data.swap, min: 0, step: 1 });
+ addResourceLine(resourcesConfigPage.swap, field, { value: config.data.swap, min: 0, step: 1 });
}
}
diff --git a/scripts/user.js b/scripts/user.js
deleted file mode 100644
index 9585b61..0000000
--- a/scripts/user.js
+++ /dev/null
@@ -1,496 +0,0 @@
-import { goToPage, getURIData, setTitleAndHeader, setAppearance, requestAPI, resourcesConfig, mergeDeep, addResourceLine, setSVGAlt, setSVGSrc } from "./utils.js";
-import { alert, dialog } from "./dialog.js";
-
-window.addEventListener("DOMContentLoaded", init);
-
-let username;
-let userData;
-let allGroups;
-let allNodes;
-let allPools;
-let clusterResourceConfig;
-
-const resourceInputTypes = { // input types for each resource for config page
- cpu: {
- element: "interactive-list",
- align: "start"
- },
- cores: {
- element: "input",
- attributes: {
- type: "number"
- }
- },
- memory: {
- element: "input",
- attributes: {
- type: "number"
- }
- },
- swap: {
- element: "input",
- attributes: {
- type: "number"
- }
- },
- network: {
- element: "input",
- attributes: {
- type: "number"
- }
- },
- pci: {
- element: "interactive-list",
- align: "start"
- }
-};
-
-class InteractiveList extends HTMLElement {
- #name;
- #addText;
-
- constructor () {
- super();
- this.attachShadow({ mode: "open" });
- this.shadowRoot.innerHTML = `
-
-
-
-
-
- `;
- this.addBtn = this.shadowRoot.querySelector("#add-btn");
- this.addBtn.onclick = this.#handleAdd.bind(this);
- this.container = this.shadowRoot.querySelector("#container");
- setSVGSrc(this.addBtn, "images/common/add.svg");
- setSVGAlt(this.addBtn, "Add Item");
- }
-
- get name () {
- return this.#name;
- }
-
- set name (name) {
- this.#name = name;
- }
-
- get addText () {
- return this.#addText;
- }
-
- set addText (addText) {
- this.#addText = addText;
- }
-
- get value () {
-
- }
-
- set value (value) {
- for (const item of value) {
- this.#addItem(item);
- }
- }
-
- #addItem (item) {
- const itemElem = document.createElement("interactive-list-match-item");
- itemElem.name = item.name;
- itemElem.match = item.match;
- itemElem.max = item.max;
- this.container.appendChild(itemElem);
- }
-
- #handleAdd () {
- const header = `Add New ${this.#name} Rule`;
-
- const body = `
-
- `;
-
- dialog(header, body, (result, form) => {
- if (result === "confirm") {
- const newItem = {
- name: form.get("name"),
- match: form.get("match"),
- max: form.get("max")
- };
- this.#addItem(newItem);
- }
- });
- }
-}
-
-class InteractiveListMatchItem extends HTMLElement {
- #name;
- #match;
- #max;
-
- #nameElem;
- #matchElem;
- #maxElem;
-
- constructor () {
- super();
- this.attachShadow({ mode: "open" });
- this.shadowRoot.innerHTML = `
-
-
-
-
-
- `;
- this.#nameElem = this.shadowRoot.querySelector("#name");
- this.#matchElem = this.shadowRoot.querySelector("#match");
- this.#maxElem = this.shadowRoot.querySelector("#max");
-
- this.deleteBtn = this.shadowRoot.querySelector("#delete-btn");
- this.deleteBtn.onclick = this.#handleDelete.bind(this);
- setSVGSrc(this.deleteBtn, "images/actions/delete-active.svg");
- setSVGAlt(this.deleteBtn, "Delete Item");
- }
-
- #update () {
- this.#nameElem.innerText = this.#name;
- this.#matchElem.innerText = this.#match;
- this.#maxElem.innerText = this.#max;
- }
-
- get name () {
- return this.#name;
- }
-
- set name (name) {
- this.#name = name;
- this.#update();
- }
-
- get match () {
- return this.#match;
- }
-
- set match (match) {
- this.#match = match;
- this.#update();
- }
-
- get max () {
- return this.#max;
- }
-
- set max (max) {
- this.#max = max;
- this.#update();
- }
-
- #handleDelete () {
- const header = `Delete ${this.name}`;
- const body = `Are you sure you want to delete ${this.name}
`;
-
- dialog(header, body, async (result, form) => {
- if (result === "confirm") {
- if (this.parentElement) {
- this.parentElement.removeChild(this);
- }
- }
- });
- }
-}
-
-customElements.define("interactive-list", InteractiveList);
-customElements.define("interactive-list-match-item", InteractiveListMatchItem);
-
-const resourcesConfigPage = mergeDeep({}, resourcesConfig, resourceInputTypes);
-
-async function init () {
- setAppearance();
- setTitleAndHeader();
- const cookie = document.cookie;
- if (cookie === "") {
- goToPage("login.html");
- }
-
- const uriData = getURIData();
- username = uriData.username;
-
- document.querySelector("#name").innerHTML = document.querySelector("#name").innerHTML.replace("%{username}", username);
-
- await getUser();
- await populateGroups();
- await populateResources();
- await populateCluster();
-
- clusterResourceConfig = (await requestAPI("/global/config/resources")).resources;
-
- document.querySelector("#exit").addEventListener("click", handleFormExit);
-}
-
-async function getUser () {
- userData = (await requestAPI(`/access/users/${username}`)).user;
- allGroups = (await requestAPI("/access/groups")).groups;
- allNodes = (await requestAPI("/cluster/nodes")).nodes;
- allPools = (await requestAPI("/cluster/pools")).pools;
-}
-
-async function populateGroups () {
- const groupsDisabled = document.querySelector("#groups-disabled");
- const groupsEnabled = document.querySelector("#groups-enabled");
- // for each group in cluster
- for (const groupName of Object.keys(allGroups)) {
- const group = allGroups[groupName];
- const item = document.createElement("draggable-item");
- item.data = group;
- item.innerHTML = `
-
-
-
${group.attributes.cn}
-
- `;
- // if user in group
- if (userData.attributes.memberOf.indexOf(group.dn) !== -1) {
- groupsEnabled.append(item);
- }
- // user is not in group
- else {
- groupsDisabled.append(item);
- }
- }
-}
-
-async function populateResources () {
- const field = document.querySelector("#resources");
- for (const resourceName of Object.keys(userData.resources)) {
- const resource = userData.resources[resourceName];
- if (resourcesConfigPage[resourceName]) {
- const resourceConfig = resourcesConfigPage[resourceName];
- let resourceLine;
-
- if (resourceName === "cpu" || resourceName === "pci") {
- resourceLine = addResourceLine(resourcesConfigPage, field, resourceName, { value: resource.global }, "(Global)");
- }
- else {
- resourceLine = addResourceLine(resourcesConfigPage, field, resourceName, { value: resource.global.max }, "(Global)");
- }
-
- postPopulateResourceLine(field, resourceName, "global", resourceConfig, resourceLine);
-
- for (const nodeSpecificName of Object.keys(resource.nodes)) { // for each node specific, add a line with the node name as a prefix
- if (resourceName === "cpu" || resourceName === "pci") {
- resourceLine = addResourceLine(resourcesConfigPage, field, resourceName, { value: resource.nodes[nodeSpecificName] }, `(${nodeSpecificName})`);
- }
- else {
- resourceLine = addResourceLine(resourcesConfigPage, field, resourceName, { value: resource.nodes[nodeSpecificName].max }, `(${nodeSpecificName})`);
- }
-
- postPopulateResourceLine(field, resourceName, nodeSpecificName, resourceConfig, resourceLine);
- }
- }
- }
- document.querySelector("#resource-add").addEventListener("click", handleResourceAdd);
-}
-
-function postPopulateResourceLine (field, resourceName, resourceScope, resourceConfig, resourceLine) {
- const deleteBtn = document.createElementNS("http://www.w3.org/2000/svg", "svg");
- deleteBtn.classList.add("clickable");
- setSVGSrc(deleteBtn, "images/actions/delete-active.svg");
- setSVGAlt(deleteBtn, "Delete Rule");
- field.appendChild(deleteBtn);
-
- resourceLine.field = field;
- resourceLine.deleteBtn = deleteBtn;
- deleteBtn.onclick = handleResourceDelete.bind(resourceLine);
-
- if (resourceConfig.align && resourceConfig.align === "start") {
- resourceLine.icon.style.alignSelf = "start";
- resourceLine.icon.style.marginTop = "calc(8px + (0.5lh - 0.5em))";
- resourceLine.label.style.alignSelf = "start";
- }
-
- resourceLine.resourceName = resourceName;
- resourceLine.resourceScope = resourceScope;
-}
-
-async function handleResourceAdd () {
- const header = "Add New Resource Constraint";
- const body = `
-
- `;
-
- const d = dialog(header, body, async (result, form) => {
- if (result === "confirm") {
- const name = form.get("name");
- const type = clusterResourceConfig[name].type;
- const scope = form.get("scope");
-
- console.log(name, type, scope);
-
- // check if the resource name is not in the cluster config resources
- if (!clusterResourceConfig[name]) {
- alert(`${name} is not an allowed resource name`);
- }
- // check if a global scope rule already exists in the user's resource config
- else if (scope === "global" && userData.resources[name] && userData.resources[name].global) {
- alert(`${name} (${scope}) is already a rule`);
- }
- // check if node specific rule already exists in the user's resource config
- else if (scope !== "global" && userData.resources[name] && userData.resources[name].nodes[scope]) {
- alert(`${name} (${scope}) is already a rule`);
- }
- // no existing rule exists, add a new resource rule line and add a the rule to userData
- else {
- // if the rule does not exist at all, add a temporary filler to mark that a new rule has been created
- if (!userData.resources[name]) {
- userData.resources[name] = {
- global: null,
- node: {}
- };
- }
-
- const field = document.querySelector("#resources");
- let resourceLine;
-
- if (scope === "global" && type === "numeric") {
- userData.resources[name].global = { max: 0 };
- resourceLine = addResourceLine(resourcesConfigPage, field, name, { value: userData.resources[name].global.max }, "(Global)");
- }
- else if (scope === "global" && type === "list") {
- userData.resources[name].global = [];
- resourceLine = addResourceLine(resourcesConfigPage, field, name, { value: userData.resources[name].global }, "(Global)");
- }
- else if (scope !== "global" && type === "numeric") {
- userData.resources[name].nodes[scope] = { max: 0 };
- resourceLine = addResourceLine(resourcesConfigPage, field, name, { value: userData.resources[name].nodes[scope].max }, `(${scope})`);
- }
- else if (scope !== "global" && type === "list") {
- userData.resources[name].nodes[scope] = [];
- resourceLine = addResourceLine(resourcesConfigPage, field, name, { value: userData.resources[name].nodes[scope] }, `(${scope})`);
- }
-
- postPopulateResourceLine(field, name, scope, resourcesConfigPage[name], resourceLine);
- }
- }
- });
-
- const nameSelect = d.querySelector("#name");
- for (const resourceName of Object.keys(clusterResourceConfig)) {
- nameSelect.add(new Option(resourceName, resourceName));
- }
-
- const scopeSelect = d.querySelector("#scope");
- for (const node of allNodes) {
- scopeSelect.add(new Option(node, node));
- }
-}
-
-async function handleResourceDelete () {
- const header = `Delete Resource Constraint ${this.label.innerText}`;
- const body = `Are you sure you want to delete VM ${this.label.innerText}
`;
-
- dialog(header, body, async (result, form) => {
- if (result === "confirm") {
- this.icon.parentElement.removeChild(this.icon);
- this.label.parentElement.removeChild(this.label);
- this.element.parentElement.removeChild(this.element);
- this.unit.parentElement.removeChild(this.unit);
- this.deleteBtn.parentElement.removeChild(this.deleteBtn);
-
- if (this.resourceScope === "global") {
- userData.resources[this.resourceName].global = false;
- }
- else {
- userData.resources[this.resourceName].nodes[this.resourceScope] = false;
- }
- }
- });
-}
-
-async function populateCluster () {
- const nodesEnabled = document.querySelector("#nodes-enabled");
- const nodesDisabled = document.querySelector("#nodes-disabled");
- const poolsEnabled = document.querySelector("#pools-enabled");
- const poolsDisabled = document.querySelector("#pools-disabled");
-
- for (const node of allNodes) { // for each node of all cluster nodes
- const item = document.createElement("draggable-item");
- item.data = node;
- item.innerHTML = `
-
- `;
- if (userData.cluster.nodes[node] === true) {
- nodesEnabled.append(item);
- }
- else {
- nodesDisabled.append(item);
- }
- }
-
- for (const pool of allPools) { // for each pool of all cluster pools
- const item = document.createElement("draggable-item");
- item.data = pool;
- item.innerHTML = `
-
- `;
- if (userData.cluster.pools[pool] === true) {
- poolsEnabled.append(item);
- }
- else {
- poolsDisabled.append(item);
- }
- }
-
- const vmidMin = document.querySelector("#vmid-min");
- const vmidMax = document.querySelector("#vmid-max");
-
- vmidMin.value = userData.cluster.vmid.min;
- vmidMax.value = userData.cluster.vmid.max;
-
- const adminCheckbox = document.querySelector("#admin");
- adminCheckbox.checked = userData.cluster.admin === true;
-}
-
-async function handleFormExit () {
- // TODO
-}
diff --git a/scripts/utils.js b/scripts/utils.js
index 61f46ee..00d275a 100644
--- a/scripts/utils.js
+++ b/scripts/utils.js
@@ -300,15 +300,6 @@ export function getURIData () {
export async function setTitleAndHeader () {
document.title = `${organization} - dashboard`;
document.querySelector("h1").innerText = organization;
- if (getCookie("auth") === "1") {
- const userIsAdmin = (await requestAPI("/user/config/cluster")).admin;
- if (userIsAdmin) {
- const adminNavLink = document.querySelector("#navigation #admin-link");
- adminNavLink.href = "admin.html";
- adminNavLink.classList.remove("none");
- adminNavLink.ariaDisabled = false;
- }
- }
}
const settingsDefault = {
@@ -427,8 +418,7 @@ export function isEmpty (obj) {
}
}
-export function addResourceLine (config, field, resourceType, attributesOverride, labelPrefix = null) {
- const resourceConfig = config[resourceType];
+export function addResourceLine (resourceConfig, field, attributesOverride, labelPrefix = null) {
const iconHref = resourceConfig.icon;
const elementType = resourceConfig.element;
const labelText = labelPrefix ? `${labelPrefix} ${resourceConfig.name}` : resourceConfig.name;
diff --git a/settings.html b/settings.html
index d7b8d85..d743059 100644
--- a/settings.html
+++ b/settings.html
@@ -41,7 +41,6 @@
Instances
Account
Settings
- Admin
Logout
diff --git a/user.html b/user.html
deleted file mode 100644
index e263201..0000000
--- a/user.html
+++ /dev/null
@@ -1,102 +0,0 @@
-
-
-
-
-
- proxmox - dashboard
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Admin / Users / %{username}
-
-
-
-
-
\ No newline at end of file