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 - - - - - - - - -
-

proxmox

- - - -
-
-

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 = ` - - - - -
-

-

match=""

-

max=

- -
- `; - 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 = ` -
- drag icon -

${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 = ` -
- drag icon -

${node}

-
- `; - 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 = ` -
- drag icon -

${pool}

-
- `; - 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 - - - - - - - - - - - - - -
-

proxmox

- - - -
-
-
-

Admin / Users / %{username}

-
-
- Groups -
-

Member Of

-

Not Member Of

- - -
-
-
- Resources -
-
- -
-
-
- Cluster -
- - -
- -
-

Allowed

-

Not Allowed

- - -
- -
-

Allowed

-

Not Allowed

- - -
- - -
- - -
-
-
-
- -
-
-
-
- - \ No newline at end of file