Add input validation to SpiriLink

This commit is contained in:
Youssof 2024-10-28 13:42:25 +00:00
parent 7a804b8ba6
commit f74c3f6e1e
3 changed files with 226 additions and 147 deletions

View File

@ -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;
}

View File

@ -5,7 +5,7 @@
<title>SpiriLink Configuration</title>
<meta charset="utf-8">
<link href="../base1/bootstrap.min.css" type="text/css" rel="stylesheet">
<link href="../base1/style.css" type="text/css" rel="stylesheet">
<!-- <link href="../base1/style.css" type="text/css" rel="stylesheet"> -->
<script src="../base1/cockpit.js"></script>
<script src="../base1/jquery-3.7.1.min.js"></script>
</head>
@ -20,163 +20,177 @@
</div>
</div>
<!-- Save Button -->
<div class="row mt-4 mb-4">
<div class="col-6">
<button class="btn btn-primary" id="save">Save/Update</button>
<form class="row" id="spiriLinkForm" action="javascript:void(0);" novalidate>
<!-- Save Button -->
<div class="row mt-2 mb-3">
<div class="col-6">
<button class="btn btn-primary" id="save">Save/Update</button>
</div>
<div class="col-6 d-flex justify-content-end">
<button id="applyEncryptionFile" class="btn btn-primary">Apply Encryption File from Base Station</button>
</div>
</div>
<div class="col-6 d-flex justify-content-end">
<button id="applyEncryptionFile" class="btn btn-primary">Apply Encryption File from Base Station</button>
</div>
</div>
<pre id="result" class="mt-3 mb-3"></pre>
<pre id="result" class="ms-1"></pre>
<!-- WiFi Configuration -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<img src="assets/icons/settings_wifi.svg" class="me-2"/><span>WiFi Configuration</span>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-4">
<label for="wifiChannel" class="form-label">WiFi Channel</label>
<select id="wifiChannel" class="form-select"></select>
</div>
<div class="col-md-4">
<label for="wifiRegion" class="form-label">WiFi Region</label>
<select id="wifiRegion" class="form-select">
</select>
</div>
<div class="col-md-4">
<label for="wifiTxPower" class="form-label">WiFi Tx Power (dBm * 100)</label>
<input type="number" id="wifiTxPower" class="form-control"
placeholder="e.g., 3000 for 30dBm">
<!-- WiFi Configuration -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<img src="assets/icons/settings_wifi.svg" class="me-2"/><span>WiFi Configuration</span>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-4">
<label for="wifiChannel" class="form-label">WiFi Channel</label>
<select id="wifiChannel" class="form-select"></select>
</div>
<div class="col-md-4">
<label for="wifiRegion" class="form-label">WiFi Region</label>
<select id="wifiRegion" class="form-select">
</select>
</div>
<div class="col-md-4">
<label for="wifiTxPower" class="form-label">WiFi Tx Power (dBm * 100)</label>
<input type="number" id="wifiTxPower" class="form-control"
placeholder="e.g., 3000 for 30dBm">
<div class="invalid-feedback">Must be an integer.</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Temperature Configuration -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<img src="assets/icons/settings_temp.svg" class="me-2"/><span>Temperature Configuration</span>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="tempInterval" class="form-label">Measurement Interval (s)</label>
<input type="number" id="tempInterval" class="form-control" value="10">
</div>
<div class="col-md-6">
<label for="tempWarning" class="form-label">Overheat Warning (°C)</label>
<input type="number" id="tempWarning" class="form-control" value="60">
<!-- Temperature Configuration -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<img src="assets/icons/settings_temp.svg" class="me-2"/><span>Temperature Configuration</span>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-6">
<label for="tempInterval" class="form-label">Measurement Interval (s)</label>
<input type="number" id="tempInterval" class="form-control" required>
<div class="invalid-feedback">Must be an integer.</div>
</div>
<div class="col-md-6">
<label for="tempWarning" class="form-label">Overheat Warning (°C)</label>
<input type="number" id="tempWarning" class="form-control" step="0.1" required>
<div class="invalid-feedback">Must be a number with at most one decimal. </div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Drone Video Configuration -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<img src="assets/icons/settings_video.svg" class="me-2"/><span>Drone Video Configuration</span>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-3">
<label for="droneVideoFwmark" class="form-label">FWMark</label>
<input type="number" id="droneVideoFwmark" class="form-control">
</div>
<div class="col-md-3">
<label for="droneVideoType" class="form-label">Connection Type</label>
<select id="droneVideoType" class="form-select"></select>
</div>
<div class="col-md-3">
<label for="droneVideoIP" class="form-label">IP Address</label>
<input type="text" id="droneVideoIP" class="form-control">
</div>
<div class="col-md-3">
<label for="droneVideoPort" class="form-label">Port</label>
<input type="number" id="droneVideoPort" class="form-control">
<!-- Drone Video Configuration -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<img src="assets/icons/settings_video.svg" class="me-2"/><span>Drone Video Configuration</span>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-3">
<label for="droneVideoFwmark" class="form-label">FWMark</label>
<input type="number" id="droneVideoFwmark" class="form-control" required>
<div class="invalid-feedback">Must be an integer.</div>
</div>
<div class="col-md-3">
<label for="droneVideoType" class="form-label">Connection Type</label>
<select id="droneVideoType" class="form-select" required></select>
</div>
<div class="col-md-3">
<label for="droneVideoIP" class="form-label">IP Address</label>
<input type="text" id="droneVideoIP" class="form-control" required>
<div class="invalid-feedback">IP Address must be in the format xxx.xxx.xxx.xxx</div>
</div>
<div class="col-md-3">
<label for="droneVideoPort" class="form-label">Port</label>
<input type="number" id="droneVideoPort" class="form-control" required>
<div class="invalid-feedback">Port must be between 1 and 65535</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Drone MAVLink Configuration -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<img src="assets/icons/lan.svg" class="me-2"/><span>Drone MAVLink Configuration</span>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-3">
<label for="droneMavlinkFwmark" class="form-label">FWMark</label>
<input type="number" id="droneMavlinkFwmark" class="form-control">
</div>
<div class="col-md-3">
<label for="droneMavlinkType" class="form-label">Connection Type</label>
<select id="droneMavlinkType" class="form-select"></select>
</div>
<div class="col-md-3">
<label for="droneMavlinkIP" class="form-label">IP Address</label>
<input type="text" id="droneMavlinkIP" class="form-control">
</div>
<div class="col-md-3">
<label for="droneMavlinkPort" class="form-label">Port</label>
<input type="number" id="droneMavlinkPort" class="form-control">
<!-- Drone MAVLink Configuration -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<img src="assets/icons/lan.svg" class="me-2"/><span>Drone MAVLink Configuration</span>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-3">
<label for="droneMavlinkFwmark" class="form-label">FWMark</label>
<input type="number" id="droneMavlinkFwmark" class="form-control" required>
<div class="invalid-feedback">Must be an integer.</div>
</div>
<div class="col-md-3">
<label for="droneMavlinkType" class="form-label">Connection Type</label>
<select id="droneMavlinkType" class="form-select" required></select>
</div>
<div class="col-md-3">
<label for="droneMavlinkIP" class="form-label">IP Address</label>
<input type="text" id="droneMavlinkIP" class="form-control" required>
<div class="invalid-feedback">IP Address must be in the format xxx.xxx.xxx.xxx</div>
</div>
<div class="col-md-3">
<label for="droneMavlinkPort" class="form-label">Port</label>
<input type="number" id="droneMavlinkPort" class="form-control" required>
<div class="invalid-feedback">Port must be between 1 and 65535</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- MAVLink Configuration -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<img src="assets/icons/settings_ethernet.svg" class="me-2"/><span>MAVLink Settings</span>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-3">
<label for="injectRssi" class="form-label">Inject RSSI</label>
<input type="checkbox" id="injectRssi" class="form-check-input">
</div>
<div class="col-md-3">
<label for="mavlinkSysId" class="form-label">System ID</label>
<input type="number" id="mavlinkSysId" class="form-control" required>
</div>
<div class="col-md-3">
<label for="mavlinkCompId" class="form-label">Component ID</label>
<input type="number" id="mavlinkCompId" class="form-control">
</div>
<div class="col-md-3">
<label for="mavlinkTcpPort" class="form-label">MAVLink TCP Port</label>
<input type="number" id="mavlinkTcpPort" class="form-control"
placeholder="None if left blank">
<!-- MAVLink Configuration -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-header">
<img src="assets/icons/settings_ethernet.svg" class="me-2"/><span>MAVLink Settings</span>
</div>
<div class="card-body">
<div class="row mb-3">
<div class="col-md-3">
<label for="injectRssi" class="form-label">Inject RSSI</label>
<input type="checkbox" id="injectRssi" class="form-check-input">
</div>
<div class="col-md-3">
<label for="mavlinkSysId" class="form-label">System ID</label>
<input type="number" id="mavlinkSysId" class="form-control" min="1" max="255" required>
<div class="invalid-feedback">System ID must be between 1 and 255</div>
</div>
<div class="col-md-3">
<label for="mavlinkCompId" class="form-label">Component ID</label>
<input type="number" id="mavlinkCompId" class="form-control" min="1" max="255" required>
<div class="invalid-feedback">Comp ID must be between 1 and 255</div>
</div>
<div class="col-md-3">
<label for="mavlinkTcpPort" class="form-label">MAVLink TCP Port</label>
<input type="number" id="mavlinkTcpPort" class="form-control"
placeholder="None if left blank">
<div class="invalid-feedback">Port must be between 1 and 65535</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
<script src="spirilink.js"></script>

View File

@ -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;
}
}