Files
Lora-Scanner/templates/index.html
2024-10-28 15:34:35 -06:00

924 lines
53 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no" />
<meta name="description" content="" />
<meta name="author" content="" />
<script type="text/javascript" src="static/js/socket.io.js"></script>
<title>LoRa Scanner</title>
<link rel="icon" type="image/x-icon" href="assets/img/favicon.ico" />
<!-- Font Awesome icons (free version)-->
<script src="static/js/all.js" crossorigin="anonymous"></script>
<!-- Google fonts-->
<!-- Core theme CSS (includes Bootstrap)-->
<link href="static/css/styles.css" rel="stylesheet" />
<!-- Include SweetAlert2 CSS -->
<link rel="stylesheet" href="static/css/sweetalert2.css">
<!-- Include SweetAlert2 JS -->
<script src="static/js/sweetalert2.js"></script>
<script src="static/js/jquery.min.js"></script>
</head>
<body id="page-top">
<!-- Navigation-->
<nav class="navbar navbar-expand-lg navbar-dark bg-primary fixed-top" id="sideNav">
<a class="navbar-brand js-scroll-trigger" href="#page-top">
<span class="d-block d-lg-none">LoRa Scanner</span>
<span class="d-none d-lg-block"><img class="img-fluid img-profile rounded-circle mx-auto mb-2" src="static/assets/img/profile.jpg" alt="..." /></span>
</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarResponsive" aria-controls="navbarResponsive" aria-expanded="false" aria-label="Toggle navigation"><span class="navbar-toggler-icon"></span></button>
<div class="collapse navbar-collapse" id="navbarResponsive">
<ul class="navbar-nav">
<li class="nav-item"><a class="nav-link js-scroll-trigger" href="#about">Home</a></li>
<li class="nav-item"><a class="nav-link js-scroll-trigger" href="#config">Configuration</a></li>
<li class="nav-item"><a class="nav-link js-scroll-trigger" href="/analysis">Analysis Mode</a></li>
<li class="nav-item"><a class="nav-link js-scroll-trigger" href="/survey">Survey Mode</a></li>
<li class="nav-item"><a class="nav-link js-scroll-trigger" href="/tracking">Tracking Mode</a></li>
</ul>
</div>
</nav>
<!-- Page Content-->
<div class="container-fluid p-0">
<!-- About-->
<section class="resume-section" id="about">
<div class="resume-section-content">
<h1 class="mb-0">
LoRa Scanner
</h1>
<h2 class="lead mb-5">Skinny Research and Development</h2>
<p>LoRa, short for Long Range, is a wireless communication technology designed for long-range, low-power communication between devices in the Internet of Things (IoT) and machine-to-machine (M2M) applications. Developed to address the specific needs of IoT devices, LoRa technology enables efficient, low-cost, and long-range communication in various scenarios.
<br><br>LoRa operates on unlicensed radio frequencies, such as those in the Industrial, Scientific, and Medical (ISM) bands, providing the flexibility for deployment without the need for a dedicated spectrum license. It utilizes a modulation technique called Chirp Spread Spectrum (CSS), which allows for long-range communication with minimal power consumption.
<br><br>One of the key features of LoRa technology is its ability to provide extended communication ranges, often reaching several kilometers in open environments. This makes it suitable for applications where devices need to communicate over long distances while conserving battery life, such as in agriculture, smart cities, asset tracking, and environmental monitoring.
<br><br>LoRa devices typically consist of two main components: LoRa nodes (sensors or devices) and LoRa gateways. The nodes collect and transmit data, while gateways receive and forward this data to the central server or cloud infrastructure. The open LoRaWAN (Long Range Wide Area Network) protocol standardizes communication between devices and gateways, ensuring interoperability and facilitating the deployment of large-scale IoT networks.
<br><br>Due to its low power consumption, long-range capabilities, and suitability for diverse IoT applications, LoRa technology has gained popularity as a reliable and cost-effective solution for connecting a wide range of devices and sensors in the evolving landscape of the Internet of Things.
</p>
</div>
</section>
<!-- Add this form within the body where it fits best, possibly under the About section -->
<section class="resume-section" id="config">
<div class="resume-section-content">
<div class="config-section">
<h4>Configure Transceivers</h4>
<p>
The <strong>'Configure Transceiver'</strong> buttons allow you to attach to the serial port of an Adafruit Feather M0, enabling you to capture LoRa traffic on that frequency.
</p>
</div>
<!-- 433 -->
<div class="resume-section-content">
<div class="collapse" id="collapse433">
<div class="d-flex flex-column flex-md-row justify-content-between mb-5">
<div class="flex-grow-1">
<h3 class="mb-0">433 MHz LoRa Transceiver</h3><br>
<div class="serial-data" id="serial-data-port1">
</div>
<div style="text-align:center;">
<button class="styled-button" onclick="promptUser433()">Select Device</button>
<button class="disconnect-button" onclick="deleteSerial433()">Disconnect Device</button>
<button class="transmit-button" onclick="transmit433()">Transmit Data</button><br><br>
<button class="transmit-button" onclick="clearDataPort1()">Clear Data</button>
<input type="checkbox" id="autoscroll-port1" checked>
<label for="autoscroll-port1">Autoscroll</label>
<p id="status-label433" style="text-align: center">Status: Checking...</p>
<div class="indicator-container">
<div id="indicator433" class="indicator"></div>
</div>
</div>
<script>
function clearDataPort1() {
var serialDataDiv1 = document.getElementById('serial-data-port1');
serialDataDiv1.innerHTML = '';
}
function clearDataPort2() {
var serialDataDiv2 = document.getElementById('serial-data-port2');
serialDataDiv2.innerHTML = '';
}
function clearDataPort3() {
var serialDataDiv3 = document.getElementById('serial-data-port3');
serialDataDiv3.innerHTML = '';
}
</script>
<!-- JavaScript for updating the indicator -->
<script>
function updateIndicator433() {
// Fetch the status from the Flask route
$.ajax({
url: '/checkSer',
type: 'GET',
data: {
port: 'port1',
},
success: function(response) {
if (response['result'] === 'True') {
$('#indicator433').removeClass('red').addClass('green');
$('#status-label433').text('Status: Connected');
} else {
$('#indicator433').removeClass('green').addClass('red');
$('#status-label433').text('Serial Port Status: Disconnected');
}
},
error: function() {
$('#indicator433').removeClass('green').addClass('red');
$('#status-label433').text('Serial Port Status: Disconnected');
}
});
}
// Initial update of the indicator
updateIndicator433();
// Periodically check and update the indicator (e.g., every 5 seconds)
setInterval(updateIndicator433, 5000);
</script>
<script type="text/javascript">
var socket = io.connect('http://' + document.domain + ':' + location.port);
// For port1
socket.on('initial_serial_data_port1', function(data) {
var serialDataDiv = document.getElementById('serial-data-port1');
data.data.forEach(function(line) {
serialDataDiv.innerHTML += line + '<br>';
});
// Scroll to the bottom for new data
//serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
});
socket.on('serial_data_port1', function(data) {
var serialDataDiv = document.getElementById('serial-data-port1');
serialDataDiv.innerHTML += data.data + '<br>';
var autoscrollCheckbox = document.getElementById('autoscroll-port1');
if (autoscrollCheckbox.checked) {
serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
}
});
</script>
<script>
function promptUser433() {
// First, fetch the list of available ports
fetch('/get_serial_ports')
.then(response => response.json())
.then(data => {
const ports = data.ports;
// Create the select options
const selectOptions = ports.map(port => `<option value="${port}">${port}</option>`).join('');
Swal.fire({
title: 'Select Serial Port:',
html: `<select id="port-select" class="swal2-input">${selectOptions}</select>`,
showCancelButton: true,
confirmButtonText: 'Connect',
cancelButtonText: 'Cancel',
preConfirm: () => {
const selectedPort = document.getElementById('port-select').value;
return fetch(`/attach_serial_433?user_input=${encodeURIComponent(selectedPort)}`)
.then(response => response.json())
.then(data => {
Swal.fire({
title: 'Result',
text: data.result,
icon: 'info'
});
})
.catch(error => {
console.error('Error:', error);
Swal.fire('Oops!', 'Something went wrong.', 'error');
});
}
});
})
.catch(error => {
console.error('Error fetching ports:', error);
Swal.fire('Oops!', 'Failed to fetch available ports.', 'error');
});
}
</script>
<script>
function deleteSerial433() {
Swal.fire({
title: 'Are you sure you want to disconnect the 433 MHz port?',
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Yes',
cancelButtonText: 'No',
}).then((result) => {
if (result.isConfirmed) {
// User clicked "Yes" - Send HTTP GET request to Flask
fetch('/delete_serial_433')
.then(response => response.json())
.then(data => {
Swal.fire({
title: 'Confirmation Result',
text: data.result,
icon: 'info'
});
})
.catch(error => {
console.error('Error:', error);
Swal.fire('Oops!', 'Something went wrong.', 'error');
});
}
});
}
</script>
<script>
function transmit433() {
Swal.fire({
title: 'Enter data to transmit:',
input: 'text',
inputAttributes: {
autocapitalize: 'off'
},
showCancelButton: true,
confirmButtonText: 'Submit',
cancelButtonText: 'Cancel',
showLoaderOnConfirm: true,
preConfirm: (value) => {
// Make an HTTP POST request to Flask
return fetch('/transmit433', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ user_input: value })
})
.then(response => response.json())
.catch(error => {
console.error('Error:', error);
Swal.fire('Oops!', 'Something went wrong. Maybe the serial port is disconnected?', 'error');
});
},
allowOutsideClick: () => !Swal.isLoading()
}).then((result) => {
if (result.isConfirmed) {
Swal.fire({
title: 'Data Transmitted',
icon: 'info'
});
}
});
}
</script>
</div>
</div>
</div>
<!-- Collapsible button -->
<a class="btn btn-primary" data-bs-toggle="collapse" href="#collapse433" role="button" aria-expanded="false" aria-controls="collapse433">
Configure 433 MHz Transceiver
</a>
</div>
<br><br>
<div class="resume-section-content">
<div class="collapse" id="collapse868">
<div class="d-flex flex-column flex-md-row justify-content-between mb-5">
<div class="flex-grow-1">
<h3 class="mb-0">868 MHz LoRa Transceiver</h3><br>
<div class="serial-data" id="serial-data-port2">
</div>
<div style="text-align:center;">
<button class="styled-button" onclick="promptUser868()">Select Device</button>
<button class="disconnect-button" onclick="deleteSerial868()">Disconnect Device</button>
<button class="transmit-button" onclick="transmit868()">Transmit Data</button><br><br>
<button class="transmit-button" onclick="clearDataPort2()">Clear Data</button>
<input type="checkbox" id="autoscroll-port2" checked>
<label for="autoscroll-port2">Autoscroll</label>
<p id="status-label868" style="text-align: center">Status: Checking...</p>
<div class="indicator-container">
<div id="indicator868" class="indicator"></div>
</div>
</div>
</div>
<script>
function updateIndicator868() {
// Fetch the status from the Flask route
$.ajax({
url: '/checkSer',
type: 'GET',
data: {
port: 'port2',
},
success: function(response) {
if (response['result'] === 'True') {
$('#indicator868').removeClass('red').addClass('green');
$('#status-label868').text('Status: Connected');
} else {
$('#indicator868').removeClass('green').addClass('red');
$('#status-label868').text('Serial Port Status: Disconnected');
}
},
error: function() {
$('#indicator868').removeClass('green').addClass('red');
$('#status-label868a').text('Serial Port Status: Disconnected');
}
});
}
// Initial update of the indicator
updateIndicator868();
// Periodically check and update the indicator (e.g., every 5 seconds)
setInterval(updateIndicator868, 5000);
</script>
<script>
// For port2
var socket = io.connect('http://' + document.domain + ':' + location.port);
socket.on('initial_serial_data_port2', function(data) {
var serialDataDiv2 = document.getElementById('serial-data-port2');
data.data.forEach(function(line) {
serialDataDiv2.innerHTML += line + '<br>';
});
// Scroll to the bottom for new data
//serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
});
socket.on('serial_data_port2', function(data) {
var serialDataDiv2 = document.getElementById('serial-data-port2');
serialDataDiv2.innerHTML += data.data + '<br>';
var autoscrollCheckbox2 = document.getElementById('autoscroll-port2');
if (autoscrollCheckbox2.checked) {
serialDataDiv2.scrollTop = serialDataDiv2.scrollHeight;
}
});
</script>
<script>
function promptUser868() {
// First, fetch the list of available ports
fetch('/get_serial_ports')
.then(response => response.json())
.then(data => {
const ports = data.ports;
// Create the select options
const selectOptions = ports.map(port => `<option value="${port}">${port}</option>`).join('');
Swal.fire({
title: 'Select Serial Port:',
html: `<select id="port-select" class="swal2-input">${selectOptions}</select>`,
showCancelButton: true,
confirmButtonText: 'Connect',
cancelButtonText: 'Cancel',
preConfirm: () => {
const selectedPort = document.getElementById('port-select').value;
return fetch(`/attach_serial_868?user_input=${encodeURIComponent(selectedPort)}`)
.then(response => response.json())
.then(data => {
Swal.fire({
title: 'Result',
text: data.result,
icon: 'info'
});
})
.catch(error => {
console.error('Error:', error);
Swal.fire('Oops!', 'Something went wrong.', 'error');
});
}
});
})
.catch(error => {
console.error('Error fetching ports:', error);
Swal.fire('Oops!', 'Failed to fetch available ports.', 'error');
});
}
</script>
<script>
function deleteSerial868() {
Swal.fire({
title: 'Are you sure you want to disconnect the 868 MHz port?',
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Yes',
cancelButtonText: 'No',
}).then((result) => {
if (result.isConfirmed) {
// User clicked "Yes" - Send HTTP GET request to Flask
fetch('/delete_serial_868')
.then(response => response.json())
.then(data => {
Swal.fire({
title: 'Confirmation Result',
text: data.result,
icon: 'info'
});
})
.catch(error => {
console.error('Error:', error);
Swal.fire('Oops!', 'Something went wrong.', 'error');
});
}
});
}
</script>
<script>
function transmit868() {
Swal.fire({
title: 'Enter data to transmit:',
input: 'text',
inputAttributes: {
autocapitalize: 'off'
},
showCancelButton: true,
confirmButtonText: 'Submit',
cancelButtonText: 'Cancel',
showLoaderOnConfirm: true,
preConfirm: (value) => {
// Make an HTTP POST request to Flask
return fetch('/transmit868', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ user_input: value })
})
.then(response => response.json())
.catch(error => {
console.error('Error:', error);
Swal.fire('Oops!', 'Something went wrong. Maybe the serial port is disconnected?', 'error');
});
},
allowOutsideClick: () => !Swal.isLoading()
}).then((result) => {
if (result.isConfirmed) {
Swal.fire({
title: 'Data Transmitted',
icon: 'info'
});
}
});
}
</script>
</div>
</div>
<!-- Collapsible button -->
<a class="btn btn-primary" data-bs-toggle="collapse" href="#collapse868" role="button" aria-expanded="false" aria-controls="collapse868">
Configure 868 MHz Transceiver
</a>
</div>
<br><br>
<div class="resume-section-content">
<div class="collapse" id="collapse915">
<div class="d-flex flex-column flex-md-row justify-content-between mb-5">
<div class="flex-grow-1">
<h3 class="mb-0">915 MHZ LoRa Transceiver</h3><br>
<div class="serial-data" id="serial-data-port3">
</div>
<div style="text-align:center;">
<button class="styled-button" onclick="promptUser915()">Select Device</button>
<button class="disconnect-button" onclick="deleteSerial915()">Disconnect Device</button>
<button class="transmit-button" onclick="transmit915()">Transmit Data</button><br><br>
<button class="transmit-button" onclick="clearDataPort3()">Clear Data</button>
<input type="checkbox" id="autoscroll-port3" checked>
<label for="autoscroll-port3">Autoscroll</label>
<p id="status-label915" style="text-align: center">Status: Checking...</p>
<div class="indicator-container">
<div id="indicator915" class="indicator"></div>
</div>
</div>
</div>
<script>
function updateIndicator915() {
// Fetch the status from the Flask route
$.ajax({
url: '/checkSer',
type: 'GET',
data: {
port: 'port3',
},
success: function(response) {
if (response['result'] === 'True') {
$('#indicator915').removeClass('red').addClass('green');
$('#status-label915').text('Status: Connected');
} else {
$('#indicator915').removeClass('green').addClass('red');
$('#status-label915').text('Serial Port Status: Disconnected');
}
},
error: function() {
$('#indicator915').removeClass('green').addClass('red');
$('#status-label915').text('Serial Port Status: Disconnected');
}
});
}
// Initial update of the indicator
updateIndicator915();
// Periodically check and update the indicator (e.g., every 5 seconds)
setInterval(updateIndicator915, 5000);
</script>
<script>
// For port3
var socket = io.connect('http://' + document.domain + ':' + location.port);
socket.on('initial_serial_data_port3', function(data) {
var serialDataDiv3 = document.getElementById('serial-data-port3');
data.data.forEach(function(line) {
serialDataDiv3.innerHTML += line + '<br>';
});
// Scroll to the bottom for new data
//serialDataDiv.scrollTop = serialDataDiv.scrollHeight;
});
socket.on('serial_data_port3', function(data) {
var serialDataDiv3 = document.getElementById('serial-data-port3');
serialDataDiv3.innerHTML += data.data + '<br>';
var autoscrollCheckbox3 = document.getElementById('autoscroll-port3');
if (autoscrollCheckbox3.checked) {
serialDataDiv3.scrollTop = serialDataDiv3.scrollHeight;
}
});
</script>
<script>
function promptUser915() {
// First, fetch the list of available ports
fetch('/get_serial_ports')
.then(response => response.json())
.then(data => {
const ports = data.ports;
// Create the select options
const selectOptions = ports.map(port => `<option value="${port}">${port}</option>`).join('');
Swal.fire({
title: 'Select Serial Port:',
html: `<select id="port-select" class="swal2-input">${selectOptions}</select>`,
showCancelButton: true,
confirmButtonText: 'Connect',
cancelButtonText: 'Cancel',
preConfirm: () => {
const selectedPort = document.getElementById('port-select').value;
return fetch(`/attach_serial_915?user_input=${encodeURIComponent(selectedPort)}`)
.then(response => response.json())
.then(data => {
Swal.fire({
title: 'Result',
text: data.result,
icon: 'info'
});
})
.catch(error => {
console.error('Error:', error);
Swal.fire('Oops!', 'Something went wrong.', 'error');
});
}
});
})
.catch(error => {
console.error('Error fetching ports:', error);
Swal.fire('Oops!', 'Failed to fetch available ports.', 'error');
});
}
</script>
<script>
function deleteSerial915() {
Swal.fire({
title: 'Are you sure you want to disconnect the 915 MHz port?',
icon: 'question',
showCancelButton: true,
confirmButtonText: 'Yes',
cancelButtonText: 'No',
}).then((result) => {
if (result.isConfirmed) {
// User clicked "Yes" - Send HTTP GET request to Flask
fetch('/delete_serial_915')
.then(response => response.json())
.then(data => {
Swal.fire({
title: 'Confirmation Result',
text: data.result,
icon: 'info'
});
})
.catch(error => {
console.error('Error:', error);
Swal.fire('Oops!', 'Something went wrong.', 'error');
});
}
});
}
</script>
<script>
function transmit915() {
Swal.fire({
title: 'Enter data to transmit:',
input: 'text',
inputAttributes: {
autocapitalize: 'off'
},
showCancelButton: true,
confirmButtonText: 'Submit',
cancelButtonText: 'Cancel',
showLoaderOnConfirm: true,
preConfirm: (value) => {
// Make an HTTP POST request to Flask
return fetch('/transmit915', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ user_input: value })
})
.then(response => response.json())
.catch(error => {
console.error('Error:', error);
Swal.fire('Oops!', 'Something went wrong. Maybe the serial port is disconnected?', 'error');
});
},
allowOutsideClick: () => !Swal.isLoading()
}).then((result) => {
if (result.isConfirmed) {
Swal.fire({
title: 'Data Transmitted',
icon: 'info'
});
}
});
}
</script>
</div>
</div>
<!-- Collapsible button -->
<a class="btn btn-primary" data-bs-toggle="collapse" href="#collapse915" role="button" aria-expanded="false" aria-controls="collapse915">
Configure 915 MHz Transceiver
</a>
</div>
<br>
<br>
<section class="config-section">
<h4>Configure LoRaWAN Gateways</h4>
<div class="description">
<p>
The <strong>Configure LoRaWAN Gateway</strong> section allows you to set up to ten Dragino LPS8N LoRaWAN gateways.
</p>
<ul>
<li>Enter the IP address for each gateway you want to configure.</li>
<li>Leave the input field empty to keep the current IP or disconnect an existing one.</li>
<li>All entered IP addresses are validated for correct formatting.</li>
<li>Once configured, the application automatically retrieves and stores LoRaWAN traffic from each active gateway.</li>
<li>Access and analyze stored traffic in 'survey mode'.</li>
</ul>
<p class="note"><em>Empty values will erase that particular gateway IP address</em></p>
</div>
<form id="gatewayForm" class="mt-3">
<div id="gatewayInputs"></div>
<button type="button" class="btn btn-primary mt-3" onclick="submitGatewayForm()">Configure Gateways</button>
</form>
</section>
<style>
.gateway-container {
position: relative;
}
.status-indicator {
transition: background-color 0.3s ease;
box-shadow: 0 0 5px rgba(0,0,0,0.2);
}
.status-indicator[title] {
cursor: pointer;
}
.note {
font-style: italic;
color: #666;
margin-top: 1rem;
}
.config-section {
padding: 20px;
background-color: #f8f9fa;
border-radius: 5px;
margin-bottom: 20px;
}
.form-control {
margin-bottom: 0 !important;
}
</style>
<script>
class GatewayManager {
constructor() {
this.gatewayIPs = Object.fromEntries(
Array.from({length: 10}, (_, i) => [`gateway${i + 1}`, ''])
);
this.init();
}
createGatewayInputs() {
const container = document.getElementById('gatewayInputs');
container.innerHTML = Array.from({length: 10}, (_, i) => {
const key = `gateway${i + 1}`;
return `
<div class="gateway-container d-flex align-items-center mb-2">
<input type="text"
id="${key}"
class="form-control"
placeholder="Gateway ${i + 1} IP Address"
value="${this.gatewayIPs[key]}">
<div id="${key}-status" class="status-indicator ml-2"
style="width: 12px; height: 12px; border-radius: 50%; margin-left: 10px;">
</div>
</div>
`;
}).join('');
}
async checkGatewayStatus(gatewayIP) {
if (!gatewayIP) return false;
try {
const response = await fetch(`http://${gatewayIP}:8000/cgi-bin/log-traffic.has`, {
method: 'HEAD',
mode: 'no-cors',
timeout: 5000
});
return true;
} catch (error) {
return false;
}
}
updateStatusIndicator(gatewayKey, isOnline) {
const statusElement = document.getElementById(`${gatewayKey}-status`);
if (statusElement) {
statusElement.style.backgroundColor = isOnline ? '#4BD28F' : '#FF4D4D';
statusElement.title = isOnline ? 'Gateway Online' : 'Gateway Offline';
}
}
async checkAllGateways() {
for (const [key, ip] of Object.entries(this.gatewayIPs)) {
if (ip) {
const isOnline = await this.checkGatewayStatus(ip);
this.updateStatusIndicator(key, isOnline);
} else {
this.updateStatusIndicator(key, false);
}
}
}
validateIPAddress(ip) {
if (!ip) return true;
const regex = /^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/;
if (!regex.test(ip)) return false;
return ip.split('.').every(octet => {
const num = parseInt(octet);
return num >= 0 && num <= 255;
});
}
async submitGatewayForm() {
const formData = new FormData();
let hasValidationError = false;
// Explicitly set each gateway value in the correct order
for (let i = 1; i <= 10; i++) {
const key = `gateway${i}`;
const input = document.getElementById(key);
const value = input.value.trim();
if (value && !this.validateIPAddress(value)) {
hasValidationError = true;
input.classList.add('is-invalid');
Swal.fire('Error', `Invalid IP address for ${key}`, 'error');
return;
}
// Always append the key to FormData, even if empty
formData.append(key, value);
if (value) {
input.classList.remove('is-invalid');
}
}
if (hasValidationError) return;
console.log(formData)
try {
const response = await fetch('/set_gateways', {
method: 'POST',
body: formData
});
if (!response.ok) throw new Error('Network response was not ok');
await response.json();
Swal.fire('Success', 'Gateway IPs updated successfully', 'success');
// Update local state with new values
for (let i = 1; i <= 10; i++) {
const key = `gateway${i}`;
this.gatewayIPs[key] = document.getElementById(key).value.trim();
}
// Check status of all gateways after successful update
await this.checkAllGateways();
} catch (error) {
console.error('Error:', error);
Swal.fire('Error', 'There was an issue updating the Gateway IPs', 'error');
}
}
setupEventListeners() {
document.querySelector('#gatewayForm button')
.addEventListener('click', () => this.submitGatewayForm());
Object.keys(this.gatewayIPs).forEach(key => {
const input = document.getElementById(key);
if (input) {
input.addEventListener('change', async () => {
const ip = input.value.trim();
if (ip) {
const isOnline = await this.checkGatewayStatus(ip);
this.updateStatusIndicator(key, isOnline);
} else {
this.updateStatusIndicator(key, false);
}
});
}
});
}
async loadGatewayIPs() {
try {
const response = await fetch('/get_gateways');
if (!response.ok) throw new Error('Failed to fetch gateway IPs');
const data = await response.json();
this.gatewayIPs = data;
} catch (error) {
console.error('Error loading gateway IPs:', error);
Swal.fire('Error', 'Failed to load gateway configurations', 'error');
}
}
async init() {
await this.loadGatewayIPs(); // Load IPs first
this.createGatewayInputs(); // Then create inputs with loaded values
this.setupEventListeners();
await this.checkAllGateways(); // Check gateway statuses
}
}
// Initialize the gateway manager
document.addEventListener('DOMContentLoaded', () => {
new GatewayManager();
});
</script>
</div>
</section>
</div>
<!-- Bootstrap core JS-->
<script src="static/js/bootstrap.bundle.min.js"></script>
<!-- Core theme JS-->
<script src="static/js/scripts.js"></script>
</body>
</html>