basic working

This commit is contained in:
Will Bradley
2025-10-11 21:05:39 -07:00
parent 4767b67460
commit d19c071814
8 changed files with 57 additions and 25 deletions
+1
View File
@@ -11,6 +11,7 @@ MQTT_PORT=1883
MQTT_USERNAME=meshdev MQTT_USERNAME=meshdev
MQTT_PASSWORD=large4cats MQTT_PASSWORD=large4cats
MQTT_TOPIC=msh/US/# MQTT_TOPIC=msh/US/#
MQTT_PUB_TOPIC="msh/US/2/json/mqtt/"
# Optional: If you have your own MQTT broker # Optional: If you have your own MQTT broker
# MQTT_BROKER=mqtt://your-broker.com # MQTT_BROKER=mqtt://your-broker.com
+27
View File
@@ -0,0 +1,27 @@
# Server Configuration
PORT=3000
NODE_ENV=development
# Session Secret
SESSION_SECRET=test-secret-for-development-only
# MQTT Configuration (Meshtastic Public Broker)
MQTT_BROKER=mqtt://mqtt.meshtastic.org
MQTT_PORT=1883
MQTT_USERNAME=meshdev
MQTT_PASSWORD=large4cats
MQTT_TOPIC=#
MQTT_PUB_TOPIC="msh/US/2/json/mqtt/"
# Data Retention (in days)
DATA_RETENTION_DAYS=30
# Cron schedule for automatic data purging (daily at 2 AM)
PURGE_CRON_SCHEDULE=0 2 * * *
# Logging
LOG_LEVEL=info
# Rate Limiting
RATE_LIMIT_WINDOW_MS=900000
RATE_LIMIT_MAX_REQUESTS=1000
+12
View File
@@ -114,6 +114,18 @@ body {
font-size: 0.875rem; font-size: 0.875rem;
} }
.data-last-received {
display: flex;
align-items: center;
gap: 0.25rem;
font-size: 0.875rem;
color: var(--text-secondary);
}
.data-label {
opacity: 0.7;
}
/* Navigation Tabs */ /* Navigation Tabs */
.nav-tabs { .nav-tabs {
background: var(--surface-color); background: var(--surface-color);
+3 -10
View File
@@ -43,7 +43,7 @@
MQTT MQTT
</span> </span>
<span class="data-last-received" id="data-last-received" title="Time since last data update"> <span class="data-last-received" id="data-last-received" title="Time since last data update">
<span style="opacity: 0.7;">Last data:</span> <span id="data-timer">0s ago</span> <span class="data-label">Last data:</span> <span id="data-timer">0s ago</span>
</span> </span>
<span class="user-info" id="user-info"></span> <span class="user-info" id="user-info"></span>
<button id="logout-btn" class="btn btn-secondary">Logout</button> <button id="logout-btn" class="btn btn-secondary">Logout</button>
@@ -115,15 +115,8 @@
<form id="send-message-form" class="message-form"> <form id="send-message-form" class="message-form">
<div class="form-row"> <div class="form-row">
<input type="text" id="message-text" placeholder="Type your message..." required> <input type="text" id="message-text" placeholder="Type your message..." required>
<select id="message-channel"> <select id="message-from">
<option value="0">Channel 0</option> <option value="474572292">!1c496604</option>
<option value="1">Channel 1</option>
<option value="2">Channel 2</option>
<option value="3">Channel 3</option>
<option value="4">Channel 4</option>
<option value="5">Channel 5</option>
<option value="6">Channel 6</option>
<option value="7">Channel 7</option>
</select> </select>
<button type="submit" class="btn btn-primary">Send</button> <button type="submit" class="btn btn-primary">Send</button>
</div> </div>
+4 -4
View File
@@ -173,10 +173,10 @@ const api = {
return this.request(`/api/messages?limit=${limit}`); return this.request(`/api/messages?limit=${limit}`);
}, },
async sendMessage(text, channel = 0) { async sendMessage(from, text) {
return this.request('/api/messages/send', { return this.request('/api/messages/send', {
method: 'POST', method: 'POST',
body: JSON.stringify({ text, channel }) body: JSON.stringify({ text, from })
}); });
}, },
@@ -546,10 +546,10 @@ document.getElementById('send-message-form')?.addEventListener('submit', async (
e.preventDefault(); e.preventDefault();
const text = document.getElementById('message-text').value; const text = document.getElementById('message-text').value;
const channel = parseInt(document.getElementById('message-channel').value); const from = parseInt(document.getElementById('message-from').value);
try { try {
await api.sendMessage(text, channel); await api.sendMessage(text, from);
document.getElementById('message-text').value = ''; document.getElementById('message-text').value = '';
// Reload messages after a short delay // Reload messages after a short delay
+1
View File
@@ -26,6 +26,7 @@ module.exports = {
username: process.env.MQTT_USERNAME || 'meshdev', username: process.env.MQTT_USERNAME || 'meshdev',
password: process.env.MQTT_PASSWORD || 'large4cats', password: process.env.MQTT_PASSWORD || 'large4cats',
topic: process.env.MQTT_TOPIC || 'msh/US/#', topic: process.env.MQTT_TOPIC || 'msh/US/#',
pubTopic: process.env.MQTT_PUB_TOPIC || 'msh/US/2/json/mqtt/', // The ending / is apparently critical
options: { options: {
clientId: `meshtastic-dashboard-${Math.random().toString(16).substr(2, 8)}`, clientId: `meshtastic-dashboard-${Math.random().toString(16).substr(2, 8)}`,
clean: true, clean: true,
+7 -9
View File
@@ -88,8 +88,8 @@ class MeshtasticMQTTClient {
try { try {
const { from, to, channel, id, sender } = payload; const { from, to, channel, id, sender } = payload;
// Extract node ID (convert to hex string if it's a number) // Extract node ID (convert to hex string if it's a number, fallback to sender if none
const fromNode = sender || (from ? `!${from.toString(16).padStart(8, '0')}` : null); const fromNode = from ? `!${from.toString(16).padStart(8, '0')}` : sender;
const toNode = to ? `!${to.toString(16).padStart(8, '0')}` : null; const toNode = to ? `!${to.toString(16).padStart(8, '0')}` : null;
// Handle different message types based on actual JSON structure // Handle different message types based on actual JSON structure
@@ -344,18 +344,16 @@ class MeshtasticMQTTClient {
} }
// Send a text message // Send a text message
async sendTextMessage(text, channel = 0) { async sendTextMessage(from, text) {
try { try {
const message = JSON.stringify({ const message = JSON.stringify({
type: 'text', from: from,
text, type: 'sendtext',
channel, payload: text
timestamp: Date.now()
}); });
// Publish to the appropriate topic // Publish to the appropriate topic
const baseTopic = config.mqtt.topic.replace('/#', ''); await this.publish(config.mqtt.pubTopic, message);
await this.publish(`${baseTopic}/2/json/mqtt`, message);
return true; return true;
} catch (error) { } catch (error) {
+2 -2
View File
@@ -169,13 +169,13 @@ router.get('/messages/node/:nodeId', requireAuth, (req, res) => {
// Send a message // Send a message
router.post('/messages/send', requireAuth, async (req, res) => { router.post('/messages/send', requireAuth, async (req, res) => {
try { try {
const { text, channel } = req.body; const { text, from } = req.body;
if (!text) { if (!text) {
return res.status(400).json({ error: 'Message text required' }); return res.status(400).json({ error: 'Message text required' });
} }
await mqttClient.sendTextMessage(text, channel || 0); await mqttClient.sendTextMessage(text, from || 0);
// Log activity // Log activity
logActivity(req.session.userId, 'send_message', text, req.ip); logActivity(req.session.userId, 'send_message', text, req.ip);