Skip to content

Commit 72ec14e

Browse files
Yerazeclaude
andauthored
fix: packet log millisecond timestamps and date column (#2403)
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
1 parent 214d0d3 commit 72ec14e

File tree

6 files changed

+56
-28
lines changed

6 files changed

+56
-28
lines changed

public/locales/en.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1170,6 +1170,8 @@
11701170
"packet_monitor.loading": "Loading packets...",
11711171
"packet_monitor.no_packets": "No packets logged yet",
11721172
"packet_monitor.loading_more": "Loading more packets...",
1173+
"packet_monitor.column.date": "Date",
1174+
"packet_monitor.today": "Today",
11731175
"packet_monitor.column.time": "Time",
11741176
"packet_monitor.column.from": "From",
11751177
"packet_monitor.column.to": "To",

src/components/PacketMonitorPanel.tsx

Lines changed: 34 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ const PacketMonitorPanel: React.FC<PacketMonitorPanelProps> = ({ onClose, onNode
226226
event.stopPropagation(); // Prevent row click
227227
// Set relay node to 0 if undefined/null (triggers "unknown relay" mode in modal)
228228
setSelectedRelayNode(packet.relay_node ?? 0);
229-
setSelectedRxTime(new Date(packet.timestamp * 1000));
229+
setSelectedRxTime(new Date(toMs(packet.timestamp)));
230230
setSelectedMessageRssi(packet.rssi ?? undefined);
231231

232232
// Fetch direct neighbor stats (refresh to ensure up-to-date data)
@@ -295,38 +295,45 @@ const PacketMonitorPanel: React.FC<PacketMonitorPanelProps> = ({ onClose, onNode
295295
}
296296
};
297297

298-
// Format timestamp — prepend short date for entries before today
299-
const formatTimestamp = (timestamp: number): string => {
300-
const date = new Date(timestamp * 1000);
301-
const time = date.toLocaleTimeString('en-US', {
302-
hour12: timeFormat === '12',
303-
hour: '2-digit',
304-
minute: '2-digit',
305-
second: '2-digit',
306-
});
307-
const ms = String(date.getMilliseconds()).padStart(3, '0');
308-
const timeStr = `${time}.${ms}`;
298+
// Convert timestamp to milliseconds (handles both old seconds and new ms data)
299+
const toMs = (ts: number) => ts < 10_000_000_000 ? ts * 1000 : ts;
309300

301+
// Format date column — "Today" or short date
302+
const formatDateColumn = (timestamp: number): string => {
303+
const date = new Date(toMs(timestamp));
310304
const now = new Date();
311305
const isToday = date.getFullYear() === now.getFullYear() &&
312306
date.getMonth() === now.getMonth() &&
313307
date.getDate() === now.getDate();
314308

315309
if (isToday) {
316-
return timeStr;
310+
return t('packet_monitor.today', 'Today');
317311
}
318312

319313
const month = String(date.getMonth() + 1).padStart(2, '0');
320314
const day = String(date.getDate()).padStart(2, '0');
321-
let dateStr: string;
322315
if (dateFormat === 'DD/MM/YYYY') {
323-
dateStr = `${day}/${month}`;
316+
return `${day}/${month}`;
324317
} else if (dateFormat === 'YYYY-MM-DD') {
325-
dateStr = `${month}-${day}`;
326-
} else {
327-
dateStr = `${month}/${day}`;
318+
return `${month}-${day}`;
328319
}
329-
return `${dateStr} ${timeStr}`;
320+
return `${month}/${day}`;
321+
};
322+
323+
// Format time column — time with milliseconds
324+
const formatTimestamp = (timestamp: number): string => {
325+
const date = new Date(toMs(timestamp));
326+
const time = date.toLocaleTimeString('en-US', {
327+
hour12: timeFormat === '12',
328+
hour: '2-digit',
329+
minute: '2-digit',
330+
second: '2-digit',
331+
});
332+
const ms = String(date.getMilliseconds()).padStart(3, '0');
333+
// Insert ms before AM/PM if 12h format (e.g. "12:09:55 PM" → "12:09:55.979 PM")
334+
return timeFormat === '12'
335+
? time.replace(/(\d{2}:\d{2}:\d{2})\s*(AM|PM)/i, `$1.${ms} $2`)
336+
: `${time}.${ms}`;
330337
};
331338

332339
// Calculate hops
@@ -642,6 +649,7 @@ const PacketMonitorPanel: React.FC<PacketMonitorPanelProps> = ({ onClose, onNode
642649
<th style={{ width: '60px' }}>#</th>
643650
<th style={{ width: '35px' }}>{t('packet_monitor.column.dir')}</th>
644651
<th style={{ width: '45px' }}>{t('packet_monitor.column.via')}</th>
652+
<th style={{ width: '55px' }}>{t('packet_monitor.column.date')}</th>
645653
<th style={{ width: '110px' }}>{t('packet_monitor.column.time')}</th>
646654
<th style={{ width: '140px' }}>{t('packet_monitor.column.from')}</th>
647655
<th style={{ width: '140px' }}>{t('packet_monitor.column.to')}</th>
@@ -743,10 +751,16 @@ const PacketMonitorPanel: React.FC<PacketMonitorPanelProps> = ({ onClose, onNode
743751
>
744752
{getTransportMechanismName(packet.transport_mechanism).short}
745753
</td>
754+
<td
755+
className="date"
756+
style={{ width: '55px' }}
757+
>
758+
{formatDateColumn(packet.timestamp)}
759+
</td>
746760
<td
747761
className="timestamp"
748762
style={{ width: '110px' }}
749-
title={formatDateTime(new Date(packet.timestamp * 1000), timeFormat, dateFormat)}
763+
title={formatDateTime(new Date(toMs(packet.timestamp)), timeFormat, dateFormat)}
750764
>
751765
{formatTimestamp(packet.timestamp)}
752766
</td>

src/db/repositories/misc.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -925,7 +925,7 @@ export class MiscRepository extends BaseRepository {
925925
LEFT JOIN nodes from_nodes ON pl.from_node = from_nodes.${nodeNum}
926926
LEFT JOIN nodes to_nodes ON pl.to_node = to_nodes.${nodeNum}
927927
WHERE ${whereClause}
928-
ORDER BY pl.timestamp DESC, pl.created_at DESC LIMIT ${limit} OFFSET ${offset}
928+
ORDER BY pl.timestamp DESC, pl.id DESC LIMIT ${limit} OFFSET ${offset}
929929
`;
930930

931931
const rows = await this.executeQuery(joinQuery);

src/server/meshtasticManager.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ class MeshtasticManager {
817817
: `!${destination.toString(16).padStart(8, '0')}`;
818818

819819
packetLogService.logPacket({
820-
timestamp: Math.floor(Date.now() / 1000),
820+
timestamp: Date.now(),
821821
from_node: localNodeNum,
822822
from_node_id: localNodeId,
823823
to_node: destination,
@@ -3658,7 +3658,7 @@ class MeshtasticManager {
36583658

36593659
packetLogService.logPacket({
36603660
packet_id: meshPacket.id ?? undefined,
3661-
timestamp: Math.floor(Date.now() / 1000), // Use server time for consistent ordering (rxTime preserved in metadata.rx_time)
3661+
timestamp: Date.now(), // Use server time in ms for consistent ordering (rxTime preserved in metadata.rx_time)
36623662
from_node: fromNum,
36633663
from_node_id: fromNodeId ?? undefined,
36643664
to_node: toNum ?? undefined,

src/server/routes/packetRoutes.ts

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ import { PortNum } from '../constants/meshtastic.js';
88

99
const BROADCAST_NODE = 4294967295; // 0xFFFFFFFF
1010

11+
/** Normalize a `since` timestamp to milliseconds (auto-detect seconds vs ms) */
12+
function normalizeSinceToMs(value: string): number {
13+
const n = parseInt(value, 10);
14+
return n < 10_000_000_000 ? n * 1000 : n;
15+
}
16+
1117
const router = express.Router();
1218

1319
/**
@@ -140,7 +146,7 @@ router.get('/', requirePacketPermissions, async (req, res) => {
140146
const to_node = req.query.to_node ? parseInt(req.query.to_node as string, 10) : undefined;
141147
const channel = req.query.channel ? parseInt(req.query.channel as string, 10) : undefined;
142148
const encrypted = req.query.encrypted === 'true' ? true : req.query.encrypted === 'false' ? false : undefined;
143-
const since = req.query.since ? parseInt(req.query.since as string, 10) : undefined;
149+
const since = req.query.since ? normalizeSinceToMs(req.query.since as string) : undefined;
144150
const relay_node = req.query.relay_node === 'unknown' ? 'unknown' as const : req.query.relay_node ? parseInt(req.query.relay_node as string, 10) : undefined;
145151

146152
const isAdmin = (req as any).isAdmin;
@@ -214,7 +220,7 @@ router.get('/stats', requirePacketPermissions, async (_req, res) => {
214220
* GET /api/packets/stats/distribution
215221
* Get packet distribution by device and by type
216222
* Query params:
217-
* - since: Unix timestamp (seconds) to filter packets from
223+
* - since: Unix timestamp (seconds or milliseconds, auto-detected) to filter packets from
218224
*/
219225
router.get('/stats/distribution', requirePacketPermissions, async (req, res) => {
220226
try {
@@ -230,7 +236,7 @@ router.get('/stats/distribution', requirePacketPermissions, async (req, res) =>
230236
});
231237
}
232238

233-
const since = req.query.since ? parseInt(req.query.since as string, 10) : undefined;
239+
const since = req.query.since ? normalizeSinceToMs(req.query.since as string) : undefined;
234240
const from_node = req.query.from_node ? parseInt(req.query.from_node as string, 10) : undefined;
235241
const portnum = req.query.portnum ? parseInt(req.query.portnum as string, 10) : undefined;
236242

@@ -280,7 +286,7 @@ router.get('/export', requirePacketPermissions, async (req, res) => {
280286
const to_node = req.query.to_node ? parseInt(req.query.to_node as string, 10) : undefined;
281287
const channel = req.query.channel ? parseInt(req.query.channel as string, 10) : undefined;
282288
const encrypted = req.query.encrypted === 'true' ? true : req.query.encrypted === 'false' ? false : undefined;
283-
const since = req.query.since ? parseInt(req.query.since as string, 10) : undefined;
289+
const since = req.query.since ? normalizeSinceToMs(req.query.since as string) : undefined;
284290
const relay_node = req.query.relay_node === 'unknown' ? 'unknown' as const : req.query.relay_node ? parseInt(req.query.relay_node as string, 10) : undefined;
285291

286292
const isAdmin = (req as any).isAdmin;

src/server/routes/v1/packets.ts

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,12 @@ import express from 'express';
88
import packetLogService from '../../services/packetLogService.js';
99
import { logger } from '../../../utils/logger.js';
1010

11+
/** Normalize a `since` timestamp to milliseconds (auto-detect seconds vs ms) */
12+
function normalizeSinceToMs(value: string): number {
13+
const n = parseInt(value, 10);
14+
return n < 10_000_000_000 ? n * 1000 : n;
15+
}
16+
1117
const router = express.Router();
1218

1319
/**
@@ -44,7 +50,7 @@ router.get('/', async (req, res) => {
4450
const to_node = req.query.to_node ? parseInt(req.query.to_node as string, 10) : undefined;
4551
const channel = req.query.channel ? parseInt(req.query.channel as string, 10) : undefined;
4652
const encrypted = req.query.encrypted === 'true' ? true : req.query.encrypted === 'false' ? false : undefined;
47-
const since = req.query.since ? parseInt(req.query.since as string, 10) : undefined;
53+
const since = req.query.since ? normalizeSinceToMs(req.query.since as string) : undefined;
4854

4955
const filterOptions = { portnum, from_node, to_node, channel, encrypted, since };
5056

0 commit comments

Comments
 (0)