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

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.
- Click the "Configure Gateway" button to start configuring each gateway's IP address.
- Skip configuring a gateway or disconnect an existing one by leaving the input field empty.
- Click the "Update Gateways" button to update each gateway's IP address.
- Skip configuring a gateway by leaving the input field empty.
- All entered IP addresses are validated for correct formatting.
- Once configured, the application automatically retrieves and stores LoRaWAN traffic from each active gateway.
- Access and analyze stored traffic in 'survey mode'.
![gateways](./doc/img/gateways.png)
## Analysis Mode

21
app.py
View File

@@ -386,7 +386,7 @@ def index():
Returns:
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')
def analysis():
@@ -727,20 +727,25 @@ def set_gateways():
"""
global gateway_ips
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()
if input_ip:
try:
ipaddress.ip_address(input_ip)
gateway_ips[key] = input_ip
except ValueError:
return jsonify({"error": f"Invalid IP address provided for {key}"}), 400
ordered_gateways[key] = input_ip
# Update the global gateway_ips with ordered data
gateway_ips.update(ordered_gateways)
for gateway, ip_address in gateway_ips.items():
print(f"Gateway {gateway} has IP address: {ip_address}")
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'])
def downloadPackets():

Binary file not shown.

Before

Width:  |  Height:  |  Size: 166 KiB

After

Width:  |  Height:  |  Size: 138 KiB

View File

@@ -202,7 +202,7 @@
})
.catch(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 => {
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 => {
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>
<div class="config-section">
<h4>Configure LoRaWAN Gateways</h4>
<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.
The <strong>Configure LoRaWAN Gateway</strong> section allows you to set up to ten Dragino LPS8N LoRaWAN gateways.
</p>
<ul>
<li>Click the "Configure Gateway" button to start configuring each gateway's IP address.</li>
<li>Skip configuring a gateway or disconnect an existing one by leaving the input field empty.</li>
<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 be ignored, and the Gateway IP address will remain unchanged</em></p>
</div>
<br>
<button class="btn btn-primary" id="configureGatewayBtn">Configure LoRaWAN Gateway</button>
<form id="gatewayForm" class="mt-3">
<div id="gatewayInputs"></div>
<button type="button" class="btn btn-primary mt-3" onclick="submitGatewayForm()">Update Gateways</button>
</form>
</section>
<script>
document.getElementById('configureGatewayBtn').addEventListener('click', function() {
let gatewayIPs = {};
const ipPrompt = (title) => {
return Swal.fire({
title: title,
input: 'text',
inputPlaceholder: 'Leave empty to keep current IP',
inputValidator: (value) => {
if (value && !value.match(/^(?:[0-9]{1,3}\.){3}[0-9]{1,3}$/)) {
return 'Please enter a valid IP address or leave it empty';
}
<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);
}
});
};
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>
</section>
</section>
</div>