update data purging, re-enable channel selection

This commit is contained in:
Will Bradley
2025-10-11 22:04:03 -07:00
parent d19c071814
commit 18d5114f6f
6 changed files with 59 additions and 28 deletions
+7 -1
View File
@@ -169,7 +169,7 @@ The dashboard provides several tabs:
1. Navigate to the **Messages** tab 1. Navigate to the **Messages** tab
2. Type your message in the text field 2. Type your message in the text field
3. Select the channel (0-7) 3. Choose a device to send **From**
4. Click **Send** 4. Click **Send**
Messages will be broadcast to the mesh network via MQTT. Messages will be broadcast to the mesh network via MQTT.
@@ -269,6 +269,12 @@ Log level can be configured with `LOG_LEVEL` in `.env` (debug, info, warn, error
3. Check that there is active mesh traffic on the topic 3. Check that there is active mesh traffic on the topic
4. Review logs for any error messages 4. Review logs for any error messages
### Messages sent over MQTT aren't sent over the radio
1. Ensure the sending radio (node) has a channel called "mqtt"
2. Ensure the node has Uplink and Downlink enabled on both the default channel (LongFast, etc) and the mqtt channel
3. Ensure the node's setting under LoRa is "Ok to MQTT"
### Database errors ### Database errors
1. Ensure the `data/` directory is writable 1. Ensure the `data/` directory is writable
+17 -7
View File
@@ -118,6 +118,11 @@
<select id="message-from"> <select id="message-from">
<option value="474572292">!1c496604</option> <option value="474572292">!1c496604</option>
</select> </select>
<select id="message-channel">
<option value="0">LongFast (public)</option>
<option value="1">Aether (private)</option>
<option value="2">Other (2)</option>
</select>
<button type="submit" class="btn btn-primary">Send</button> <button type="submit" class="btn btn-primary">Send</button>
</div> </div>
</form> </form>
@@ -159,13 +164,18 @@
<p>Purge old data from the database to free up space.</p> <p>Purge old data from the database to free up space.</p>
<form id="purge-form" class="settings-form"> <form id="purge-form" class="settings-form">
<div class="form-group"> <div class="form-group">
<label for="purge-days">Keep data from the last:</label> <label for="purge-hours">Keep data from the last:</label>
<select id="purge-days"> <select id="purge-hours">
<option value="7">7 days</option> <option value="1">1 hour</option>
<option value="14">14 days</option> <option value="6">6 hours</option>
<option value="30" selected>30 days</option> <option value="12">12 hours</option>
<option value="60">60 days</option> <option value="24">1 day</option>
<option value="90">90 days</option> <option value="72">3 days</option>
<option value="168">7 days</option>
<option value="336">14 days</option>
<option value="720" selected>30 days</option>
<option value="1440">60 days</option>
<option value="2160">90 days</option>
</select> </select>
</div> </div>
<button type="submit" class="btn btn-danger">Purge Old Data</button> <button type="submit" class="btn btn-danger">Purge Old Data</button>
+18 -8
View File
@@ -173,17 +173,17 @@ const api = {
return this.request(`/api/messages?limit=${limit}`); return this.request(`/api/messages?limit=${limit}`);
}, },
async sendMessage(from, text) { async sendMessage(from, text, channel) {
return this.request('/api/messages/send', { return this.request('/api/messages/send', {
method: 'POST', method: 'POST',
body: JSON.stringify({ text, from }) body: JSON.stringify({ from, text, channel })
}); });
}, },
async purgeData(days) { async purgeData(hours) {
return this.request('/api/purge', { return this.request('/api/purge', {
method: 'POST', method: 'POST',
body: JSON.stringify({ days }) body: JSON.stringify({ hours })
}); });
}, },
@@ -547,9 +547,10 @@ document.getElementById('send-message-form')?.addEventListener('submit', async (
const text = document.getElementById('message-text').value; const text = document.getElementById('message-text').value;
const from = parseInt(document.getElementById('message-from').value); const from = parseInt(document.getElementById('message-from').value);
const channel = parseInt(document.getElementById('message-channel').value);
try { try {
await api.sendMessage(text, from); await api.sendMessage(from, text, channel);
document.getElementById('message-text').value = ''; document.getElementById('message-text').value = '';
// Reload messages after a short delay // Reload messages after a short delay
@@ -1045,14 +1046,23 @@ document.getElementById('refresh-map-btn')?.addEventListener('click', loadMap);
document.getElementById('purge-form')?.addEventListener('submit', async (e) => { document.getElementById('purge-form')?.addEventListener('submit', async (e) => {
e.preventDefault(); e.preventDefault();
const days = parseInt(document.getElementById('purge-days').value); const hours = parseInt(document.getElementById('purge-hours').value);
if (!confirm(`Are you sure you want to delete data older than ${days} days? This cannot be undone.`)) { // Format time period for display
let timePeriod;
if (hours < 24) {
timePeriod = `${hours} hour${hours !== 1 ? 's' : ''}`;
} else {
const days = hours / 24;
timePeriod = `${days} day${days !== 1 ? 's' : ''}`;
}
if (!confirm(`Are you sure you want to delete data older than ${timePeriod}? This cannot be undone.`)) {
return; return;
} }
try { try {
const result = await api.purgeData(days); const result = await api.purgeData(hours);
alert(`Successfully purged:\n${result.deleted.messages} messages\n${result.deleted.positions} positions\n${result.deleted.telemetry} telemetry records`); alert(`Successfully purged:\n${result.deleted.messages} messages\n${result.deleted.positions} positions\n${result.deleted.telemetry} telemetry records`);
// Reload stats // Reload stats
+3 -3
View File
@@ -95,7 +95,7 @@ const positionQueries = {
`), `),
deleteOldPositions: db.prepare(` deleteOldPositions: db.prepare(`
DELETE FROM positions WHERE timestamp < datetime('now', '-' || ? || ' days') DELETE FROM positions WHERE timestamp < datetime('now', '-' || ? || ' hours')
`), `),
getPositionTrails: db.prepare(` getPositionTrails: db.prepare(`
@@ -170,7 +170,7 @@ const messageQueries = {
`), `),
deleteOldMessages: db.prepare(` deleteOldMessages: db.prepare(`
DELETE FROM messages WHERE created_at < datetime('now', '-' || ? || ' days') DELETE FROM messages WHERE created_at < datetime('now', '-' || ? || ' hours')
`) `)
}; };
@@ -199,7 +199,7 @@ const telemetryQueries = {
`), `),
deleteOldTelemetry: db.prepare(` deleteOldTelemetry: db.prepare(`
DELETE FROM telemetry WHERE timestamp < datetime('now', '-' || ? || ' days') DELETE FROM telemetry WHERE timestamp < datetime('now', '-' || ? || ' hours')
`) `)
}; };
+2 -1
View File
@@ -344,10 +344,11 @@ class MeshtasticMQTTClient {
} }
// Send a text message // Send a text message
async sendTextMessage(from, text) { async sendTextMessage(from, text, channel=0) {
try { try {
const message = JSON.stringify({ const message = JSON.stringify({
from: from, from: from,
channel: channel,
type: 'sendtext', type: 'sendtext',
payload: text payload: text
}); });
+12 -8
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, from } = req.body; const { from, text, channel } = 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, from || 0); await mqttClient.sendTextMessage(from, text, channel);
// Log activity // Log activity
logActivity(req.session.userId, 'send_message', text, req.ip); logActivity(req.session.userId, 'send_message', text, req.ip);
@@ -224,20 +224,24 @@ router.get('/stats', requireAuth, (req, res) => {
// Purge old data // Purge old data
router.post('/purge', requireAuth, (req, res) => { router.post('/purge', requireAuth, (req, res) => {
try { try {
const { days } = req.body; const { hours } = req.body;
const daysToKeep = days || 30; const hoursToKeep = hours || 720; // Default to 30 days (720 hours)
const messagesDeleted = messageQueries.deleteOldMessages.run(daysToKeep); const messagesDeleted = messageQueries.deleteOldMessages.run(hoursToKeep);
const positionsDeleted = positionQueries.deleteOldPositions.run(daysToKeep); const positionsDeleted = positionQueries.deleteOldPositions.run(hoursToKeep);
const telemetryDeleted = telemetryQueries.deleteOldTelemetry.run(daysToKeep); const telemetryDeleted = telemetryQueries.deleteOldTelemetry.run(hoursToKeep);
logger.info(`Data purged: ${messagesDeleted.changes} messages, ${positionsDeleted.changes} positions, ${telemetryDeleted.changes} telemetry records`); logger.info(`Data purged: ${messagesDeleted.changes} messages, ${positionsDeleted.changes} positions, ${telemetryDeleted.changes} telemetry records`);
// Log activity // Log activity
const timePeriod = hoursToKeep < 24
? `${hoursToKeep} hour${hoursToKeep !== 1 ? 's' : ''}`
: `${hoursToKeep / 24} day${hoursToKeep / 24 !== 1 ? 's' : ''}`;
logActivity( logActivity(
req.session.userId, req.session.userId,
'purge_data', 'purge_data',
`Purged data older than ${daysToKeep} days`, `Purged data older than ${timePeriod}`,
req.ip req.ip
); );