From 3ee065c2aa72090ac0dd363e560d6d24a9f0a9d5 Mon Sep 17 00:00:00 2001 From: ianmuchyri Date: Tue, 28 Nov 2023 13:51:28 +0300 Subject: [PATCH] split javascript files Signed-off-by: ianmuchyri --- ui/web/static/js/clipboard.js | 23 ++ ui/web/static/js/errors.js | 13 + ui/web/static/js/forms.js | 63 ++++ ui/web/static/js/infinitescroll.js | 70 ++++ ui/web/static/js/main.js | 512 ---------------------------- ui/web/static/js/update.js | 278 +++++++++++++++ ui/web/static/js/validation.js | 105 ++++++ ui/web/template/bootstrap.html | 9 +- ui/web/template/bootstraps.html | 85 ++--- ui/web/template/channel.html | 35 +- ui/web/template/channelgroups.html | 9 +- ui/web/template/channels.html | 275 ++++++++------- ui/web/template/channelthings.html | 9 +- ui/web/template/channelusers.html | 21 +- ui/web/template/error.html | 5 +- ui/web/template/group.html | 10 +- ui/web/template/groupchannels.html | 9 +- ui/web/template/groups.html | 266 +++++++-------- ui/web/template/groupusers.html | 21 +- ui/web/template/header.html | 37 +- ui/web/template/index.html | 5 +- ui/web/template/login.html | 5 +- ui/web/template/messagesread.html | 9 +- ui/web/template/resetpassword.html | 5 +- ui/web/template/terminal.html | 5 +- ui/web/template/thing.html | 38 ++- ui/web/template/thingchannels.html | 9 +- ui/web/template/things.html | 282 +++++++-------- ui/web/template/thingusers.html | 10 +- ui/web/template/updatepassword.html | 6 +- ui/web/template/user.html | 37 +- ui/web/template/userchannels.html | 21 +- ui/web/template/usergroups.html | 21 +- ui/web/template/users.html | 292 ++++++++-------- ui/web/template/userthings.html | 10 +- 35 files changed, 1376 insertions(+), 1234 deletions(-) create mode 100644 ui/web/static/js/clipboard.js create mode 100644 ui/web/static/js/errors.js create mode 100644 ui/web/static/js/forms.js create mode 100644 ui/web/static/js/infinitescroll.js delete mode 100644 ui/web/static/js/main.js create mode 100644 ui/web/static/js/update.js create mode 100644 ui/web/static/js/validation.js diff --git a/ui/web/static/js/clipboard.js b/ui/web/static/js/clipboard.js new file mode 100644 index 00000000..49a71acb --- /dev/null +++ b/ui/web/static/js/clipboard.js @@ -0,0 +1,23 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +//function to copy the ID to the clipboard +function copyToClipboard(button) { + var clientIDElement = button.previousElementSibling.firstChild; + var clientId = clientIDElement.textContent; + + navigator.clipboard.writeText(clientId).then( + function () { + //change the copy icon to indicate success + button.innerHTML = ``; + setTimeout(function () { + //revert the copy icon after a short delay + button.innerHTML = ``; + }, 1000); + }, + function (error) { + //handle error + console.error("failed to copy to clipboard: ", error); + }, + ); +} diff --git a/ui/web/static/js/errors.js b/ui/web/static/js/errors.js new file mode 100644 index 00000000..156ee545 --- /dev/null +++ b/ui/web/static/js/errors.js @@ -0,0 +1,13 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +export function displayErrorMessage(errorMessage, divName) { + const errorDiv = document.getElementById(divName); + errorDiv.style.display = "block"; + errorDiv.innerHTML = errorMessage; +} + +export function removeErrorMessage(divName) { + const errorDiv = document.getElementById(divName); + errorDiv.style.display = "none"; +} diff --git a/ui/web/static/js/forms.js b/ui/web/static/js/forms.js new file mode 100644 index 00000000..7f64a20e --- /dev/null +++ b/ui/web/static/js/forms.js @@ -0,0 +1,63 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +// config parameters are: formId, url, alertDiv, modal +export function submitCreateForm(config) { + const form = document.getElementById(config.formId); + form.addEventListener("submit", function (event) { + event.preventDefault(); + const formData = new FormData(form); + + fetch(config.url, { + method: "POST", + body: formData, + }) + .then(function (response) { + switch (response.status) { + case 409: + showAlert("entity already exists!", config.alertDiv); + break; + case 400: + showAlert("invalid file contents!", config.alertDiv); + break; + case 415: + showAlert("invalid file type!", config.alertDiv); + break; + default: + form.reset(); + config.modal.hide(); + window.location.reload(); + } + }) + .catch((error) => { + console.error("error submitting form: ", error); + }); + }); +} + +export function submitUpdateForm(config) { + fetch(config.url, { + method: "POST", + body: JSON.stringify(config.data), + headers: { + "Content-Type": "application/json", + }, + }).then((response) => { + switch (response.status) { + case 409: + showAlert("entity already exists!", config.alertDiv); + break; + default: + window.location.reload(); + } + }); +} + +function showAlert(errorMessage, alertDiv) { + const alert = document.getElementById(alertDiv); + alert.innerHTML = ` + `; +} diff --git a/ui/web/static/js/infinitescroll.js b/ui/web/static/js/infinitescroll.js new file mode 100644 index 00000000..ab5c5e4a --- /dev/null +++ b/ui/web/static/js/infinitescroll.js @@ -0,0 +1,70 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +export function fetchIndividualEntity(config) { + document.addEventListener("DOMContentLoaded", function () { + getEntities(config.item, ""); + infiniteScroll(config.item); + }); + + const input = document.getElementById(config.input); + + input.addEventListener("input", function (event) { + const itemSelect = document.getElementById(config.itemSelect); + if (event.target.value === "") { + itemSelect.innerHTML = ``; + getEntities(config.item, ""); + infiniteScroll(config.item); + } else { + itemSelect.innerHTML = ""; + getEntities(config.item, event.target.value); + } + }); +} + +function getEntities(item, name) { + fetchData(item, name, 1); +} + +function infiniteScroll(item) { + var selectElement = document.getElementById("infiniteScroll"); + var singleOptionHeight = selectElement.querySelector("option").offsetHeight; + var selectBoxHeight = selectElement.offsetHeight; + var numOptionsBeforeLoad = 2; + var lastScrollTop = 0; + var currentPageNo = 1; + var currentScroll = 0; + + selectElement.addEventListener("scroll", function () { + var st = selectElement.scrollTop; + var totalHeight = selectElement.querySelectorAll("option").length * singleOptionHeight; + + if (st > lastScrollTop) { + currentScroll = st + selectBoxHeight; + if (currentScroll + numOptionsBeforeLoad * singleOptionHeight >= totalHeight) { + currentPageNo++; + fetchData(item, "", currentPageNo); + } + } + + lastScrollTop = st; + }); +} + +let limit = 5; +function fetchData(item, name, page) { + fetch(`/entities?item=${item}&limit=${limit}&name=${name}&page=${page}`, { + method: "GET", + }) + .then((response) => response.json()) + .then((data) => { + const selectElement = document.getElementById("infiniteScroll"); + data.data.forEach((entity) => { + const option = document.createElement("option"); + option.value = entity.id; + option.text = entity.name; + selectElement.appendChild(option); + }); + }) + .catch((error) => console.error("Error:", error)); +} diff --git a/ui/web/static/js/main.js b/ui/web/static/js/main.js deleted file mode 100644 index 07b4ce06..00000000 --- a/ui/web/static/js/main.js +++ /dev/null @@ -1,512 +0,0 @@ -// Copyright (c) Abstract Machines -// SPDX-License-Identifier: Apache-2.0 - -//function to copy the ID to the clipboard -function copyToClipboard(button) { - var clientIDElement = button.previousElementSibling.firstChild; - var clientId = clientIDElement.textContent; - - navigator.clipboard.writeText(clientId).then( - function () { - //change the copy icon to indicate success - button.innerHTML = ``; - setTimeout(function () { - //revert the copy icon after a short delay - button.innerHTML = ``; - }, 1000); - }, - function (error) { - //handle error - console.error("failed to copy to clipboard: ", error); - }, - ); -} - -// Form validation functions - -function validateName(name, errorDiv, event) { - removeErrorMessage(errorDiv); - if (name.trim() === "") { - event.preventDefault(); - displayErrorMessage("Name is Required", errorDiv); - return false; - } - return true; -} - -const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; -function validateEmail(email, errorDiv, event) { - removeErrorMessage(errorDiv); - if (email.trim() === "") { - event.preventDefault(); - displayErrorMessage("Email is Required", errorDiv); - return false; - } else if (!email.match(emailRegex)) { - event.preventDefault(); - displayErrorMessage("Invalid email format", errorDiv); - return false; - } - return true; -} - -const minLength = 8; -function validatePassword(password, errorDiv, event) { - removeErrorMessage(errorDiv); - if (password.trim().length < minLength) { - event.preventDefault(); - var errorMessage = `Password must be at least ${minLength} characters long`; - displayErrorMessage(errorMessage, errorDiv); - return false; - } - return true; -} - -function validateJSON(data, errorDiv, event) { - removeErrorMessage(errorDiv); - try { - if (data.trim() !== "") { - JSON.parse(data); - } - } catch (error) { - event.preventDefault(); - displayErrorMessage("not a valid JSON object", errorDiv); - return false; - } - return true; -} - -function validateStringArray(tags, errorDiv, event) { - removeErrorMessage(errorDiv); - var tagsArray; - try { - if (tags.trim() !== "") { - tagsArray = JSON.parse(tags); - } - if ( - !Array.isArray(tagsArray) || - !tagsArray.every(function (tag) { - return typeof tag === "string"; - }) - ) { - event.preventDefault(); - displayErrorMessage("must be strings in an array", errorDiv); - return false; - } - } catch (error) { - event.preventDefault(); - displayErrorMessage("must be a string array", errorDiv); - return false; - } - - return true; -} - -function displayErrorMessage(errorMessage, divName) { - const errorDiv = document.getElementById(divName); - errorDiv.style.display = "block"; - errorDiv.innerHTML = errorMessage; -} - -function removeErrorMessage(divName) { - const errorDiv = document.getElementById(divName); - errorDiv.style.display = "none"; -} - -function attachValidationListener(config) { - const button = document.getElementById(config.buttonId); - - button.addEventListener("click", function (event) { - for (const key in config.validations) { - if (config.validations.hasOwnProperty(key)) { - const validationFunc = config.validations[key]; - const elementValue = document.getElementById(key).value; - validationFunc(elementValue, config.errorDivs[key], event); - } - } - }); -} - -// Form subsmission functions -// config parameters are: formId, url, alertDiv, modal -function submitCreateForm(config) { - const form = document.getElementById(config.formId); - form.addEventListener("submit", function (event) { - event.preventDefault(); - const formData = new FormData(form); - - fetch(config.url, { - method: "POST", - body: formData, - }) - .then(function (response) { - switch (response.status) { - case 409: - showAlert("entity already exists!", config.alertDiv); - break; - case 400: - showAlert("invalid file contents!", config.alertDiv); - break; - case 415: - showAlert("invalid file type!", config.alertDiv); - break; - default: - form.reset(); - config.modal.hide(); - window.location.reload(); - } - }) - .catch((error) => { - console.error("error submitting form: ", error); - }); - }); -} - -function showAlert(errorMessage, alertDiv) { - const alert = document.getElementById(alertDiv); - alert.innerHTML = ` - `; -} - -// Functions to make a row editable. - -// make a cell editable. -function makeEditable(cell) { - cell.setAttribute("contenteditable", "true"); - cell.dataset.originalContent = cell.innerHTML; -} - -// make cell uneditable. -function makeUneditable(cell) { - const originalContent = cell.dataset.originalContent; - cell.innerHTML = originalContent; - cell.setAttribute("contenteditable", "false"); -} - -// function show the save/cancel buttons and hide the edit button. -function showSaveCancelButtons(editBtn, saveCancelBtn) { - editBtn.style.display = "none"; - saveCancelBtn.style.display = "inline-block"; -} - -// function to show the edit button anf hide the save/cancel buttons. -function showEditButton(editBtn, saveCancelBtn) { - editBtn.style.display = "inline-block"; - saveCancelBtn.style.display = "none"; -} - -// config parameters are: button, field -function editRow(config) { - const button = document.getElementById(config.button); - - button.addEventListener("click", function () { - makeEditable(config.cell); - showSaveCancelButtons(config.editBtn, config.saveCancelBtn); - }); -} - -function cancelEditRow(config) { - const button = document.getElementById(config.button); - - button.addEventListener("click", function () { - makeUneditable(config.cell); - showEditButton(config.editBtn, config.saveCancelBtn); - removeErrorMessage(config.alertDiv); - }); -} - -function submitUpdateForm(config) { - fetch(config.url, { - method: "POST", - body: JSON.stringify(config.data), - headers: { - "Content-Type": "application/json", - }, - }).then((response) => { - switch (response.status) { - case 409: - showAlert("entity already exists!", config.alertDiv); - break; - default: - window.location.reload(); - } - }); -} - -function updateName(config) { - const button = document.getElementById(config.button); - - button.addEventListener("click", function (event) { - const updatedValue = config.cell.textContent.trim(); - if (validateName(updatedValue, config.alertDiv, event)) { - const url = `/${config.entity}/${config.id}`; - const data = { [config.field]: updatedValue }; - - submitUpdateForm({ - url: url, - data: data, - alertDiv: config.alertDiv, - }); - } - }); -} - -function updateIdentity(config) { - const button = document.getElementById(config.button); - - button.addEventListener("click", function (event) { - const updatedValue = config.cell.textContent.trim(); - if (validateEmail(updatedValue, config.alertDiv, event)) { - const url = `/${config.entity}/${config.id}/identity`; - const data = { [config.field]: updatedValue }; - - submitUpdateForm({ - url: url, - data: data, - alertDiv: config.alertDiv, - }); - } - }); -} - -function updateMetadata(config) { - const button = document.getElementById(config.button); - - button.addEventListener("click", function (event) { - const updatedValue = config.cell.textContent.trim(); - if (validateJSON(updatedValue, config.alertDiv, event)) { - const url = `/${config.entity}/${config.id}`; - const data = { [config.field]: JSON.parse(updatedValue) }; - - submitUpdateForm({ - url: url, - data: data, - alertDiv: config.alertDiv, - }); - } - }); -} - -function updateTags(config) { - const button = document.getElementById(config.button); - - button.addEventListener("click", function (event) { - const updatedValue = config.cell.textContent.trim(); - if (validateStringArray(updatedValue, config.alertDiv, event)) { - const url = `/${config.entity}/${config.id}/tags`; - const data = { [config.field]: JSON.parse(updatedValue) }; - - submitUpdateForm({ - url: url, - data: data, - alertDiv: config.alertDiv, - }); - } - }); -} - -function updateSecret(config) { - const button = document.getElementById(config.button); - - button.addEventListener("click", function (event) { - const updatedValue = config.cell.textContent.trim(); - if (validatePassword(updatedValue, config.alertDiv, event)) { - const url = `/${config.entity}/${config.id}/secret`; - const data = { [config.field]: updatedValue }; - - submitUpdateForm({ - url: url, - data: data, - alertDiv: config.alertDiv, - }); - } - }); -} - -function updateOwner(config) { - const button = document.getElementById(config.button); - - button.addEventListener("click", function () { - const updatedValue = config.cell.textContent.trim(); - const url = `/${config.entity}/${config.id}/owner`; - const data = { [config.field]: updatedValue }; - - submitUpdateForm({ - url: url, - data: data, - alertDiv: config.alertDiv, - }); - }); -} - -function updateDescription(config) { - const button = document.getElementById(config.button); - - button.addEventListener("click", function () { - const updatedValue = config.cell.textContent.trim(); - const url = `/${config.entity}/${config.id}`; - const data = { [config.field]: updatedValue }; - - submitUpdateForm({ - url: url, - data: data, - alertDiv: config.alertDiv, - }); - }); -} - -// Bootstrap update functions -function updateContent(config) { - const button = document.getElementById(config.button); - - button.addEventListener("click", function () { - const updatedValue = config.cell.textContent.trim(); - const url = `/${config.entity}/${config.id}`; - const data = { [config.field]: updatedValue }; - - submitUpdateForm({ - url: url, - data: data, - alertDiv: config.alertDiv, - }); - }); -} - -function updateClientCerts(config) { - const button = document.getElementById(config.button); - - button.addEventListener("click", function () { - const updatedValue = config.cell.textContent.trim(); - const url = `/${config.entity}/${config.id}/certs`; - const data = { [config.field]: updatedValue }; - - submitUpdateForm({ - url: url, - data: data, - alertDiv: config.alertDiv, - }); - }); -} - -function updateConnections(config) { - const button = document.getElementById(config.button); - - button.addEventListener("click", function (event) { - const updatedValue = config.cell.textContent.trim(); - - if (validateStringArray(updatedValue, config.alertDiv, event)) { - const url = `/${config.entity}/${config.id}/connections`; - const data = { [config.field]: JSON.parse(updatedValue) }; - - submitUpdateForm({ - url: url, - data: data, - alertDiv: config.alertDiv, - }); - } - }); -} - -function attachEditRowListener(config) { - for (const key in config.rows) { - if (config.rows.hasOwnProperty(key)) { - const cell = document.querySelector(`td[data-field="${key}"]`); - const editBtn = cell.parentNode.querySelector(".edit-btn"); - const saveCancelBtn = cell.parentNode.querySelector(".save-cancel-buttons"); - editRow({ - button: `edit-${key}`, - cell: cell, - editBtn: editBtn, - saveCancelBtn: saveCancelBtn, - }); - cancelEditRow({ - button: `cancel-${key}`, - cell: cell, - editBtn: editBtn, - saveCancelBtn: saveCancelBtn, - alertDiv: config.errorDiv, - }); - const saveRow = config.rows[key]; - saveRow({ - button: `save-${key}`, - field: key, - cell: cell, - editBtn: editBtn, - saveCancelBtn: saveCancelBtn, - id: config.id, - entity: config.entity, - alertDiv: config.errorDiv, - }); - } - } -} - -function fetchIndividualEntity(config) { - document.addEventListener("DOMContentLoaded", function () { - getEntities(config.item, ""); - infiniteScroll(config.item); - }); - - const input = document.getElementById(config.input); - - input.addEventListener("input", function (event) { - const itemSelect = document.getElementById(config.itemSelect); - if (event.target.value === "") { - itemSelect.innerHTML = ``; - getEntities(config.item, ""); - infiniteScroll(config.item); - } else { - itemSelect.innerHTML = ""; - getEntities(config.item, event.target.value); - } - }); -} - -function getEntities(item, name) { - fetchData(item, name, 1); -} - -function infiniteScroll(item) { - var selectElement = document.getElementById("infiniteScroll"); - var singleOptionHeight = selectElement.querySelector("option").offsetHeight; - var selectBoxHeight = selectElement.offsetHeight; - var numOptionsBeforeLoad = 2; - var lastScrollTop = 0; - var currentPageNo = 1; - var currentScroll = 0; - - selectElement.addEventListener("scroll", function () { - var st = selectElement.scrollTop; - var totalHeight = selectElement.querySelectorAll("option").length * singleOptionHeight; - - if (st > lastScrollTop) { - currentScroll = st + selectBoxHeight; - if (currentScroll + numOptionsBeforeLoad * singleOptionHeight >= totalHeight) { - currentPageNo++; - fetchData(item, "", currentPageNo); - } - } - - lastScrollTop = st; - }); -} - -let limit = 5; -function fetchData(item, name, page) { - fetch(`/entities?item=${item}&limit=${limit}&name=${name}&page=${page}`, { - method: "GET", - }) - .then((response) => response.json()) - .then((data) => { - const selectElement = document.getElementById("infiniteScroll"); - data.data.forEach((entity) => { - const option = document.createElement("option"); - option.value = entity.id; - option.text = entity.name; - selectElement.appendChild(option); - }); - }) - .catch((error) => console.error("Error:", error)); -} diff --git a/ui/web/static/js/update.js b/ui/web/static/js/update.js new file mode 100644 index 00000000..21d9f5a4 --- /dev/null +++ b/ui/web/static/js/update.js @@ -0,0 +1,278 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +import { submitUpdateForm } from "./forms.js"; +import { + validateName, + validateEmail, + validateJSON, + validateStringArray, + validatePassword, +} from "./validation.js"; + +function updateName(config) { + const button = document.getElementById(config.button); + + button.addEventListener("click", function (event) { + const updatedValue = config.cell.textContent.trim(); + if (validateName(updatedValue, config.alertDiv, event)) { + const url = `/${config.entity}/${config.id}`; + const data = { [config.field]: updatedValue }; + + submitUpdateForm({ + url: url, + data: data, + alertDiv: config.alertDiv, + }); + } + }); +} + +function updateIdentity(config) { + const button = document.getElementById(config.button); + + button.addEventListener("click", function (event) { + const updatedValue = config.cell.textContent.trim(); + if (validateEmail(updatedValue, config.alertDiv, event)) { + const url = `/${config.entity}/${config.id}/identity`; + const data = { [config.field]: updatedValue }; + + submitUpdateForm({ + url: url, + data: data, + alertDiv: config.alertDiv, + }); + } + }); +} + +function updateMetadata(config) { + const button = document.getElementById(config.button); + + button.addEventListener("click", function (event) { + const updatedValue = config.cell.textContent.trim(); + if (validateJSON(updatedValue, config.alertDiv, event)) { + const url = `/${config.entity}/${config.id}`; + const data = { [config.field]: JSON.parse(updatedValue) }; + + submitUpdateForm({ + url: url, + data: data, + alertDiv: config.alertDiv, + }); + } + }); +} + +function updateTags(config) { + const button = document.getElementById(config.button); + + button.addEventListener("click", function (event) { + const updatedValue = config.cell.textContent.trim(); + if (validateStringArray(updatedValue, config.alertDiv, event)) { + const url = `/${config.entity}/${config.id}/tags`; + const data = { [config.field]: JSON.parse(updatedValue) }; + + submitUpdateForm({ + url: url, + data: data, + alertDiv: config.alertDiv, + }); + } + }); +} + +function updateSecret(config) { + const button = document.getElementById(config.button); + + button.addEventListener("click", function (event) { + const updatedValue = config.cell.textContent.trim(); + if (validatePassword(updatedValue, config.alertDiv, event)) { + const url = `/${config.entity}/${config.id}/secret`; + const data = { [config.field]: updatedValue }; + + submitUpdateForm({ + url: url, + data: data, + alertDiv: config.alertDiv, + }); + } + }); +} + +function updateOwner(config) { + const button = document.getElementById(config.button); + + button.addEventListener("click", function () { + const updatedValue = config.cell.textContent.trim(); + const url = `/${config.entity}/${config.id}/owner`; + const data = { [config.field]: updatedValue }; + + submitUpdateForm({ + url: url, + data: data, + alertDiv: config.alertDiv, + }); + }); +} + +function updateDescription(config) { + const button = document.getElementById(config.button); + + button.addEventListener("click", function () { + const updatedValue = config.cell.textContent.trim(); + const url = `/${config.entity}/${config.id}`; + const data = { [config.field]: updatedValue }; + + submitUpdateForm({ + url: url, + data: data, + alertDiv: config.alertDiv, + }); + }); +} + +// Bootstrap update functions +function updateContent(config) { + const button = document.getElementById(config.button); + + button.addEventListener("click", function () { + const updatedValue = config.cell.textContent.trim(); + const url = `/${config.entity}/${config.id}`; + const data = { [config.field]: updatedValue }; + + submitUpdateForm({ + url: url, + data: data, + alertDiv: config.alertDiv, + }); + }); +} + +function updateClientCerts(config) { + const button = document.getElementById(config.button); + + button.addEventListener("click", function () { + const updatedValue = config.cell.textContent.trim(); + const url = `/${config.entity}/${config.id}/certs`; + const data = { [config.field]: updatedValue }; + + submitUpdateForm({ + url: url, + data: data, + alertDiv: config.alertDiv, + }); + }); +} + +function updateConnections(config) { + const button = document.getElementById(config.button); + + button.addEventListener("click", function (event) { + const updatedValue = config.cell.textContent.trim(); + + if (validateStringArray(updatedValue, config.alertDiv, event)) { + const url = `/${config.entity}/${config.id}/connections`; + const data = { [config.field]: JSON.parse(updatedValue) }; + + submitUpdateForm({ + url: url, + data: data, + alertDiv: config.alertDiv, + }); + } + }); +} + +// make a cell editable. +function makeEditable(cell) { + cell.setAttribute("contenteditable", "true"); + cell.dataset.originalContent = cell.innerHTML; +} + +// make cell uneditable. +function makeUneditable(cell) { + const originalContent = cell.dataset.originalContent; + cell.innerHTML = originalContent; + cell.setAttribute("contenteditable", "false"); +} + +// function show the save/cancel buttons and hide the edit button. +function showSaveCancelButtons(editBtn, saveCancelBtn) { + editBtn.style.display = "none"; + saveCancelBtn.style.display = "inline-block"; +} + +// function to show the edit button anf hide the save/cancel buttons. +function showEditButton(editBtn, saveCancelBtn) { + editBtn.style.display = "inline-block"; + saveCancelBtn.style.display = "none"; +} + +// config parameters are: button, field +function editRow(config) { + const button = document.getElementById(config.button); + + button.addEventListener("click", function () { + makeEditable(config.cell); + showSaveCancelButtons(config.editBtn, config.saveCancelBtn); + }); +} + +function cancelEditRow(config) { + const button = document.getElementById(config.button); + + button.addEventListener("click", function () { + makeUneditable(config.cell); + showEditButton(config.editBtn, config.saveCancelBtn); + removeErrorMessage(config.alertDiv); + }); +} + +function attachEditRowListener(config) { + for (const key in config.rows) { + if (config.rows.hasOwnProperty(key)) { + const cell = document.querySelector(`td[data-field="${key}"]`); + const editBtn = cell.parentNode.querySelector(".edit-btn"); + const saveCancelBtn = cell.parentNode.querySelector(".save-cancel-buttons"); + editRow({ + button: `edit-${key}`, + cell: cell, + editBtn: editBtn, + saveCancelBtn: saveCancelBtn, + }); + cancelEditRow({ + button: `cancel-${key}`, + cell: cell, + editBtn: editBtn, + saveCancelBtn: saveCancelBtn, + alertDiv: config.errorDiv, + }); + const saveRow = config.rows[key]; + saveRow({ + button: `save-${key}`, + field: key, + cell: cell, + editBtn: editBtn, + saveCancelBtn: saveCancelBtn, + id: config.id, + entity: config.entity, + alertDiv: config.errorDiv, + }); + } + } +} + +export { + updateName, + updateIdentity, + updateMetadata, + updateTags, + updateSecret, + updateOwner, + updateDescription, + updateContent, + updateClientCerts, + updateConnections, + attachEditRowListener, +}; diff --git a/ui/web/static/js/validation.js b/ui/web/static/js/validation.js new file mode 100644 index 00000000..8d83f245 --- /dev/null +++ b/ui/web/static/js/validation.js @@ -0,0 +1,105 @@ +// Copyright (c) Abstract Machines +// SPDX-License-Identifier: Apache-2.0 + +import { displayErrorMessage, removeErrorMessage } from "./errors.js"; + +const emailRegex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/; +const minLength = 8; + +function validateName(name, errorDiv, event) { + removeErrorMessage(errorDiv); + if (name.trim() === "") { + event.preventDefault(); + displayErrorMessage("Name is Required", errorDiv); + return false; + } + return true; +} + +function validateEmail(email, errorDiv, event) { + removeErrorMessage(errorDiv); + if (email.trim() === "") { + event.preventDefault(); + displayErrorMessage("Email is Required", errorDiv); + return false; + } else if (!email.match(emailRegex)) { + event.preventDefault(); + displayErrorMessage("Invalid email format", errorDiv); + return false; + } + return true; +} + +function validatePassword(password, errorDiv, event) { + removeErrorMessage(errorDiv); + if (password.trim().length < minLength) { + event.preventDefault(); + var errorMessage = `Password must be at least ${minLength} characters long`; + displayErrorMessage(errorMessage, errorDiv); + return false; + } + return true; +} + +function validateJSON(data, errorDiv, event) { + removeErrorMessage(errorDiv); + try { + if (data.trim() !== "") { + JSON.parse(data); + } + } catch (error) { + event.preventDefault(); + displayErrorMessage("not a valid JSON object", errorDiv); + return false; + } + return true; +} + +function validateStringArray(tags, errorDiv, event) { + removeErrorMessage(errorDiv); + var tagsArray; + try { + if (tags.trim() !== "") { + tagsArray = JSON.parse(tags); + } + if ( + !Array.isArray(tagsArray) || + !tagsArray.every(function (tag) { + return typeof tag === "string"; + }) + ) { + event.preventDefault(); + displayErrorMessage("must be strings in an array", errorDiv); + return false; + } + } catch (error) { + event.preventDefault(); + displayErrorMessage("must be a string array", errorDiv); + return false; + } + + return true; +} + +function attachValidationListener(config) { + const button = document.getElementById(config.buttonId); + + button.addEventListener("click", function (event) { + for (const key in config.validations) { + if (config.validations.hasOwnProperty(key)) { + const validationFunc = config.validations[key]; + const elementValue = document.getElementById(key).value; + validationFunc(elementValue, config.errorDivs[key], event); + } + } + }); +} + +export { + validateName, + validateEmail, + validatePassword, + validateJSON, + validateStringArray, + attachValidationListener, +}; diff --git a/ui/web/template/bootstrap.html b/ui/web/template/bootstrap.html index a377c346..a821146e 100644 --- a/ui/web/template/bootstrap.html +++ b/ui/web/template/bootstrap.html @@ -4,7 +4,11 @@ {{ define "bootstrap" }} - {{ template "header" }} + + Bootstrap + {{ template "header" }} + + {{ template "navbar" . }}
@@ -129,7 +133,8 @@
{{ template "footer" }} - + + + {{ template "navbar" . }}
@@ -32,7 +38,7 @@
@@ -194,54 +206,31 @@