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/instance.html b/instance.html index 87f12a1..e06d1fc 100644 --- a/instance.html +++ b/instance.html @@ -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/user.js b/scripts/user.js deleted file mode 100644 index e6f31ef..0000000 --- a/scripts/user.js +++ /dev/null @@ -1,681 +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 resourceRulesLines = {}; // list of all resource rules fieldsets on the page - -const resourceInputTypes = { // input types for each resource for config page - cpu: { - type: "list", - element: "interactive-list", - align: "start" - }, - cores: { - type: "numeric", - element: "input", - attributes: { - type: "number" - } - }, - memory: { - type: "numeric", - element: "input", - attributes: { - type: "number" - } - }, - swap: { - type: "numeric", - element: "input", - attributes: { - type: "number" - } - }, - network: { - type: "numeric", - element: "input", - attributes: { - type: "number" - } - }, - storage: { - type: "numeric", - icon: "images/resources/disk.svg", - element: "input", - unitText: "B", - attributes: { - type: "number" - } - }, - pci: { - type: "list", - 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 () { - const ret = []; - for (const elem of this.container.childNodes) { - ret.push(elem.value); - } - return ret; - } - - set value (value) { - this.container.innerHTML = ""; - 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.configBtn = this.shadowRoot.querySelector("#config-btn"); - this.configBtn.onclick = this.#handleConfig.bind(this); - setSVGSrc(this.configBtn, "images/common/config.svg"); - setSVGAlt(this.configBtn, "Config Item"); - - 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 = `match="${this.#match}"`; - this.#maxElem.innerText = `max=${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(); - } - - get value () { - return { - name: this.#name, - match: this.#match, - max: this.#max - }; - } - - set value (value) { - this.#name = value.name; - this.#match = value.match; - this.#max = value.max; - this.#update(); - } - - #handleConfig () { - const header = `Edit ${this.#name} Rule`; - - const body = ` -
- - - - - - -
- `; - - const d = dialog(header, body, async (result, form) => { - if (result === "confirm") { - const newItem = { - name: form.get("name"), - match: form.get("match"), - max: form.get("max") - }; - this.value = newItem; - } - }); - - d.querySelector("#name").value = this.#name; - d.querySelector("#match").value = this.#match; - d.querySelector("#max").value = this.#max; - } - - #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.value = 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]; - let resourceLine; - let resourceConfig; - - if (resourcesConfigPage[resourceName]) { - resourceConfig = resourcesConfigPage[resourceName]; - resourceConfig.id = `${resourceName}-global`; - - if (resourceConfig.type === "list") { - resourceLine = addResourceLine(resourceConfig, field, { value: resource.global }, "(Global)"); - } - else { - resourceLine = addResourceLine(resourceConfig, field, { 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 - resourceConfig.id = `${resourceName}-${nodeSpecificName}`; - if (resourceConfig.type === "list") { - resourceLine = addResourceLine(resourceConfig, field, { value: resource.nodes[nodeSpecificName] }, `(${nodeSpecificName})`); - } - else { - resourceLine = addResourceLine(resourceConfig, field, { value: resource.nodes[nodeSpecificName].max }, `(${nodeSpecificName})`); - } - - postPopulateResourceLine(field, resourceName, nodeSpecificName, resourceConfig, resourceLine); - } - } - else { - resourceConfig = resourcesConfigPage.storage; - resourceConfig.id = `${resourceName}-global`; - resourceConfig.name = resourceName; - resourceLine = addResourceLine(resourceConfig, field, { 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 - resourceConfig.id = `${resourceName}-${nodeSpecificName}`; - resourceLine = addResourceLine(resourceConfig, field, { 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; - resourceLine.resourceType = resourceConfig.type; - - resourceRulesLines[resourceLine.element.id] = resourceLine; -} - -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"); - - // 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"); - const resourceConfig = resourcesConfigPage[name]; - let resourceLine; - - if (scope === "global" && type === "numeric") { - userData.resources[name].global = { max: 0 }; - resourceLine = addResourceLine(resourceConfig, field, { value: userData.resources[name].global.max }, "(Global)"); - } - else if (scope === "global" && type === "list") { - userData.resources[name].global = []; - resourceLine = addResourceLine(resourceConfig, field, { value: userData.resources[name].global }, "(Global)"); - } - else if (scope !== "global" && type === "numeric") { - userData.resources[name].nodes[scope] = { max: 0 }; - resourceLine = addResourceLine(resourceConfig, field, { value: userData.resources[name].nodes[scope].max }, `(${scope})`); - } - else if (scope !== "global" && type === "list") { - userData.resources[name].nodes[scope] = []; - resourceLine = addResourceLine(resourceConfig, field, { 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; - } - - delete resourceRulesLines[this.element.id]; - } - }); -} - -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.value = 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.value = 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 () { - const body = { - attributes: { - memberOf: [] - }, - resources: {}, - cluster: { - admin: document.querySelector("#admin").checked, - nodes: {}, - pools: {}, - vmid: { - min: document.querySelector("#vmid-min").value, - max: document.querySelector("#vmid-max").value - } - } - }; - - for (const group of document.querySelector("#groups-enabled").value) { - body.attributes.memberOf.push(group.dn); - } - - // populate resources - for (const key of Object.keys(resourceRulesLines)) { - const resourceLine = resourceRulesLines[key]; - // if type is numeric - if (resourceLine.resourceType === "numeric") { - if (body.resources[resourceLine.resourceName] === undefined) { - body.resources[resourceLine.resourceName] = { - global: { - max: 0 - }, - nodes: {} - }; - } - if (resourceLine.resourceScope === "global") { - body.resources[resourceLine.resourceName].global.max = resourceLine.element.value; - } - else { - body.resources[resourceLine.resourceName].nodes[resourceLine.resourceScope].max = resourceLine.element.value; - } - } - else { - if (body.resources[resourceLine.resourceName] === undefined) { - body.resources[resourceLine.resourceName] = { - global: [], - nodes: {} - }; - } - if (resourceLine.resourceScope === "global") { - body.resources[resourceLine.resourceName].global = resourceLine.element.value; - } - else { - body.resources[resourceLine.resourceName].nodes[resourceLine.resourceScope] = resourceLine.element.value; - } - } - } - - // populate nodes - for (const node of document.querySelector("#nodes-enabled").value) { - body.cluster.nodes[node] = true; - } - - // populate pools - for (const pool of document.querySelector("#pools-enabled").value) { - body.cluster.pools[pool] = true; - } - - // TODO post to api - - console.log(body); -} diff --git a/scripts/utils.js b/scripts/utils.js index 9d5ac28..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 = { 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 63c58fb..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