Merge pull request #16 from skinnyrad/refactor/gatewayip

Refactor/gatewayip
This commit is contained in:
Halcy0nic
2024-10-28 14:48:30 -06:00
committed by GitHub
4 changed files with 234 additions and 69 deletions
+3 -2
View File
@@ -71,12 +71,13 @@ From the configuration page, you can connect to a LoRa transmitter to start send
The 'Configure LoRaWAN Gateway' section allows you to set up to ten Dragino LPS8N LoRaWAN gateways. The 'Configure LoRaWAN Gateway' section allows you to set up to ten Dragino LPS8N LoRaWAN gateways.
- Click the "Configure Gateway" button to start configuring each gateway's IP address. - Click the "Update Gateways" button to update each gateway's IP address.
- Skip configuring a gateway or disconnect an existing one by leaving the input field empty. - Skip configuring a gateway by leaving the input field empty.
- All entered IP addresses are validated for correct formatting. - All entered IP addresses are validated for correct formatting.
- Once configured, the application automatically retrieves and stores LoRaWAN traffic from each active gateway. - Once configured, the application automatically retrieves and stores LoRaWAN traffic from each active gateway.
- Access and analyze stored traffic in 'survey mode'. - Access and analyze stored traffic in 'survey mode'.
![gateways](./doc/img/gateways.png) ![gateways](./doc/img/gateways.png)
## Analysis Mode ## Analysis Mode
+13 -8
View File
@@ -386,7 +386,7 @@ def index():
Returns: Returns:
str: The rendered HTML content for the index page. str: The rendered HTML content for the index page.
""" """
return render_template('index.html') return render_template('index.html', gateway_ips=gateway_ips)
@app.route('/analysis') @app.route('/analysis')
def analysis(): def analysis():
@@ -727,20 +727,25 @@ def set_gateways():
""" """
global gateway_ips global gateway_ips
data = request.form data = request.form
for key in [f'gateway{i}' for i in range(1, 11)]:
# Create an ordered dictionary of gateways
ordered_gateways = {}
for i in range(1, 11):
key = f'gateway{i}'
input_ip = data.get(key, '').strip() input_ip = data.get(key, '').strip()
if input_ip: ordered_gateways[key] = input_ip
try:
ipaddress.ip_address(input_ip) # Update the global gateway_ips with ordered data
gateway_ips[key] = input_ip gateway_ips.update(ordered_gateways)
except ValueError:
return jsonify({"error": f"Invalid IP address provided for {key}"}), 400
for gateway, ip_address in gateway_ips.items(): for gateway, ip_address in gateway_ips.items():
print(f"Gateway {gateway} has IP address: {ip_address}") print(f"Gateway {gateway} has IP address: {ip_address}")
return jsonify({"message": "Gateway IPs updated successfully"}), 200 return jsonify({"message": "Gateway IPs updated successfully"}), 200
@app.route('/get_gateways', methods=['GET'])
def get_gateways():
return jsonify(gateway_ips)
@app.route('/downloadPackets', methods=['GET']) @app.route('/downloadPackets', methods=['GET'])
def downloadPackets(): def downloadPackets():
Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 138 KiB

+218 -59
View File
@@ -202,7 +202,7 @@
}) })
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
Swal.fire('Oops!', 'Something went wrong. The port might already be in use.', 'error'); Swal.fire('Oops!', 'Something went wrong.', 'error');
}); });
} }
}); });
@@ -398,7 +398,7 @@
}) })
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
Swal.fire('Oops!', 'Something went wrong. The port might already be in use.', 'error'); Swal.fire('Oops!', 'Something went wrong.', 'error');
}); });
} }
}); });
@@ -594,7 +594,7 @@
}) })
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
Swal.fire('Oops!', 'Something went wrong. The port might already be in use.', 'error'); Swal.fire('Oops!', 'Something went wrong.', 'error');
}); });
} }
}); });
@@ -682,75 +682,234 @@
<br> <br>
<br> <br>
<div class="config-section"> <section class="config-section">
<h4>Configure LoRaWAN Gateways</h4> <h4>Configure LoRaWAN Gateways</h4>
<div class="description">
<p> <p>
The <strong>'Configure LoRaWAN Gateway'</strong> section allows you to set up to ten Dragino LPS8N LoRaWAN gateways. The <strong>Configure LoRaWAN Gateway</strong> section allows you to set up to ten Dragino LPS8N LoRaWAN gateways.
</p> </p>
<ul> <ul>
<li>Click the "Configure Gateway" button to start configuring each gateway's IP address.</li> <li>Enter the IP address for each gateway you want to configure.</li>
<li>Skip configuring a gateway or disconnect an existing one by leaving the input field empty.</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>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>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> <li>Access and analyze stored traffic in 'survey mode'.</li>
</ul> </ul>
<p class="note"><em>Empty values will be ignored, and the Gateway IP address will remain unchanged</em></p>
</div> </div>
<br> <form id="gatewayForm" class="mt-3">
<button class="btn btn-primary" id="configureGatewayBtn">Configure LoRaWAN Gateway</button> <div id="gatewayInputs"></div>
<button type="button" class="btn btn-primary mt-3" onclick="submitGatewayForm()">Update Gateways</button>
</form>
</section>
<script> <style>
document.getElementById('configureGatewayBtn').addEventListener('click', function() { .gateway-container {
let gatewayIPs = {}; position: relative;
}
const ipPrompt = (title) => {
return Swal.fire({ .status-indicator {
title: title, transition: background-color 0.3s ease;
input: 'text', box-shadow: 0 0 5px rgba(0,0,0,0.2);
inputPlaceholder: 'Leave empty to keep current IP', }
inputValidator: (value) => {
if (value && !value.match(/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/)) { .status-indicator[title] {
return 'Please enter a valid IP address or leave it empty'; 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);
} }
}); });
}; }
const promptGateways = async () => {
for (let i = 1; i <= 10; i++) {
const result = await ipPrompt(`Enter Gateway ${i} IP Address`);
if (result.value) gatewayIPs[`gateway${i}`] = result.value;
}
// Now send the IPs to the server only if they are not undefined
let queryString = Object.keys(gatewayIPs).reduce((acc, key) => {
if (gatewayIPs[key] !== undefined) {
acc.push(`${key}=${encodeURIComponent(gatewayIPs[key])}`);
}
return acc;
}, []).join('&');
if (queryString) {
fetch('/set_gateways', {
method: 'POST',
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
body: queryString
})
.then(response => response.json())
.then(data => Swal.fire('Success', 'Gateway IPs updated successfully', 'success'))
.catch(error => Swal.fire('Error', 'There was an issue updating the Gateway IPs', 'error'));
} else {
Swal.fire('No Changes', 'No IP addresses were changed.', 'info');
}
};
promptGateways();
}); });
</script> }
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> </div>
</section> </section>
</div> </div>