From f74c3f6e1ea1bb02046488951de95f46e1ead5ba Mon Sep 17 00:00:00 2001 From: Youssof Date: Mon, 28 Oct 2024 13:42:25 +0000 Subject: [PATCH] Add input validation to SpiriLink --- ui/general/mavlink.js | 30 ++--- ui/general/spirilink.html | 266 ++++++++++++++++++++------------------ ui/general/spirilink.js | 77 ++++++++++- 3 files changed, 226 insertions(+), 147 deletions(-) diff --git a/ui/general/mavlink.js b/ui/general/mavlink.js index c5d3d30..9e5513c 100644 --- a/ui/general/mavlink.js +++ b/ui/general/mavlink.js @@ -32,26 +32,26 @@ enabled = true; // Runs the initPage when the document is loaded document.onload = InitPage(); // Save file button -document.getElementById("save").addEventListener("click", SaveSettings); +document.getElementById("save").addEventListener("click", saveSettings); // This attempts to read the conf file, if it exists, then it will parse it and fill out the table // if it fails then the values are loaded with defaults. function InitPage() { cockpit.script(scriptLocation + "cockpitScript.sh -v") .then((content) => version.innerHTML=content) - .catch(error => Fail(error)); + .catch(error => displayFail(error)); cockpit.script(scriptLocation + "cockpitScript.sh -u") .then(function(content) { ipsubnet1.innerHTML=content; ipsubnet2.innerHTML=content; }) - .catch(error => Fail(error)); + .catch(error => displayFail(error)); file_location.innerHTML = confLocation + "main.conf"; cockpit.file(confLocation + "main.conf") .read().then((content, tag) => SuccessReadFile(content)) - .catch(error => FailureReadFile(error)); + .catch(error => failureReadFile(error)); } function getValueByKey(text, sectionName, sectionLength, key) { @@ -158,7 +158,7 @@ function SuccessReadFile(content) { var currentbaudRate = getValueByKey(content, "[UartEndpoint alpha]", 2, "Baud"); cockpit.script(scriptLocation + "cockpitScript.sh -s") .then((content) => AddDropDown(fmuDevice, content.split("\n"), currentfmuDevice)) - .catch(error => Fail(error)); + .catch(error => displayFail(error)); AddDropDown(baudrate, baudRateArray, currentbaudRate); //UDP Telemetry @@ -225,7 +225,7 @@ function SuccessReadFile(content) { } } catch(e) { - FailureReadFile(e); + failureReadFile(e); } } @@ -249,11 +249,11 @@ function AddDropDown(box, theArray, defaultValue) { } } catch(e) { - Fail(e) + displayFail(e) } } -function FailureReadFile(error) { +function failureReadFile(error) { // Display error message console.log("Error : " + error.message); output.innerHTML = "Error : " + error.message; @@ -267,7 +267,7 @@ function FailureReadFile(error) { } -function SaveSettings() { +function saveSettings() { //lets do some validation @@ -306,7 +306,7 @@ function SaveSettings() { //open the file for writing, and callback function for modification cockpit.file(confLocation + "main.conf") .read().then((content, tag) => SuccessReadforSaveFile(content)) - .catch(error => FailureReadFile(error)); + .catch(error => failureReadFile(error)); } function SuccessReadforSaveFile(content) { @@ -335,23 +335,23 @@ function SuccessReadforSaveFile(content) { content = setValueByKey(content, "[UartEndpoint alpha]",2, "Baud", baudrate.value ) cockpit.file(confLocation + "main.conf", { superuser : "try" }).replace(content) - .then(Success) - .catch(Fail); + .then(displaySuccess) + .catch(displayFail); cockpit.spawn(["systemctl", "restart", "mavlink-router"], { superuser : "try" }); } catch(e) { - FailureReadFile(e); + failureReadFile(e); } } -function Success() { +function displaySuccess() { result.style.color = "green"; result.innerHTML = "Success, mavlink-router restarting..."; setTimeout(() => result.innerHTML = "", 5000); } -function Fail(error) { +function displayFail(error) { result.style.color = "red"; result.innerHTML = error.message; } diff --git a/ui/general/spirilink.html b/ui/general/spirilink.html index 891072f..1b2f2ee 100644 --- a/ui/general/spirilink.html +++ b/ui/general/spirilink.html @@ -5,7 +5,7 @@ SpiriLink Configuration - + @@ -20,163 +20,177 @@ - -
-
- +
+ +
+
+ +
+
+ +
-
- -
-
-

+            

 
-        
-        
-
-
-
- WiFi Configuration -
-
-
-
- - -
-
- - -
-
- - + +
+
+
+
+ WiFi Configuration +
+
+
+
+ + +
+
+ + +
+
+ + +
Must be an integer.
+
-
- -
-
-
-
- Temperature Configuration -
-
-
-
- - -
-
- - + +
+
+
+
+ Temperature Configuration +
+
+
+
+ + +
Must be an integer.
+
+
+ + +
Must be a number with at most one decimal.
+
-
- -
-
-
-
- Drone Video Configuration -
-
-
-
- - -
-
- - -
-
- - -
-
- - + +
+
+
+
+ Drone Video Configuration +
+
+
+
+ + +
Must be an integer.
+
+
+ + +
+
+ + +
IP Address must be in the format xxx.xxx.xxx.xxx
+
+
+ + +
Port must be between 1 and 65535
+
-
- -
-
-
-
- Drone MAVLink Configuration -
-
-
-
- - -
-
- - -
-
- - -
-
- - + +
+
+
+
+ Drone MAVLink Configuration +
+
+
+
+ + +
Must be an integer.
+
+
+ + +
+
+ + +
IP Address must be in the format xxx.xxx.xxx.xxx
+
+
+ + +
Port must be between 1 and 65535
+
-
- -
-
-
-
- MAVLink Settings -
-
-
-
- - -
-
- - -
-
- - -
-
- - + +
+
+
+
+ MAVLink Settings +
+
+
+
+ + +
+
+ + +
System ID must be between 1 and 255
+
+
+ + +
Comp ID must be between 1 and 255
+
+
+ + +
Port must be between 1 and 65535
+
-
+
diff --git a/ui/general/spirilink.js b/ui/general/spirilink.js index c4b1ee0..b8ed8db 100644 --- a/ui/general/spirilink.js +++ b/ui/general/spirilink.js @@ -23,6 +23,7 @@ const mavlinkSysId = document.getElementById("mavlinkSysId"); const mavlinkCompId = document.getElementById("mavlinkCompId"); const mavlinkTcpPort = document.getElementById("mavlinkTcpPort"); +const spiriLinkForm = document.getElementById("spiriLinkForm"); const output = document.getElementById("output"); const wifiChannels = [ @@ -74,7 +75,7 @@ function initPage() { const inputs = document.querySelectorAll("input, select, textarea"); inputs.forEach(input => { - input.addEventListener("focus", clearResult); + input.addEventListener("focus", resetForm); }); cockpit.file(confLocation) @@ -85,6 +86,7 @@ function initPage() { // Load configuration values from the configuration file function successReadFile(content) { try { + // WiFi Configuration // Remove surrounding quotes from wifi_region value if present const currentWifiChannel = getValueByKey(content, "common", "wifi_channel"); @@ -131,14 +133,69 @@ function successReadFile(content) { } } +// Log error message and display it +// TODO: Add more error handling for failed file reads function failureReadFile(error) { - // Display error message - console.log("Error : " + error.message); + console.error("Error : " + error.message); displayFail(error.message) } +// Validate the form using non-standard input validation conditions. +// Additional checks for IP, port, and system/component ID fields +// Returns true if all fields are valid, false otherwise +function validateSpiriLinkForm(form) { + const ipformat = /^(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$/; + const portformat = /^([1-9][0-9]{0,3}|[1-5][0-9]{4}|6[0-4][0-9]{3}|65[0-4][0-9]{2}|655[0-2][0-9]|6553[0-5])$/; + let isValid = true; + + defaultValidation = form.checkValidity(); + + const inputs = form.querySelectorAll("input, select, textarea"); + inputs.forEach(input => { + if (input.value === "") { + isValid = !input.hasAttribute("required"); + setValidationVisuals(input, isValid); + } else { + if (input.id === "droneMavlinkIP" || input.id === "droneVideoIP") { + isValid = input.value.match(ipformat); + setValidationVisuals(input, isValid); + } + else if (input.id === "droneMavlinkPort" || input.id === "droneVideoPort" || input.id === "mavlinkTcpPort") { + isValid = input.value.match(portformat); + setValidationVisuals(input, isValid); + } + else if (input.id === "mavlinkSysId" || input.id === "mavlinkCompId") { + isValid = input.value > 1 || input.value < 255; + setValidationVisuals(input, isValid); + } + } + }); + + form.classList.add("was-validated"); + if (!isValid || !defaultValidation) return false; + return true; +} + +// Set validation visuals for input fields +// Adds or removes is-invalid class based on the validity of the input +// The setCustomValidity function triggers the actual field visuals. +// Giving it an empty string assumes the input is valid. +function setValidationVisuals(input, isValid) { + if (isValid) { + input.classList.remove("is-invalid"); + input.setCustomValidity(""); + } else { + input.classList.add("is-invalid"); + input.setCustomValidity("Invalid input"); + } +} + // Save configuration values to the configuration file function saveSettings() { + + const dataIsValid = validateSpiriLinkForm(spiriLinkForm); + if (!dataIsValid) return; + try { cockpit.file(confLocation) .read() @@ -231,9 +288,18 @@ function displayFail(error) { result.innerHTML = "Error : " + error; } -// Clear the result message -function clearResult() { +// Reset & clear form data +function resetForm() { result.innerHTML = ""; + const inputs = spiriLinkForm.querySelectorAll("input, select, textarea"); + inputs.forEach(input => { + input.setCustomValidity(""); + input.classList.remove("is-invalid"); + input.classList.remove("is-valid"); + }); + + spiriLinkForm.classList.remove("was-validated"); + spiriLinkForm.classList.add("needs-validation"); } function addDropDown(box, pairs, defaultValue) { @@ -245,7 +311,6 @@ function addDropDown(box, pairs, defaultValue) { option.text = pairs[i][1]; box.add(option); if (defaultValue === option.value) { - console.log("found") box.value = option.value; } }