diff --git a/src/components/MapIcons.tsx b/src/components/MapIcons.tsx
index e7d1602..118c988 100644
--- a/src/components/MapIcons.tsx
+++ b/src/components/MapIcons.tsx
@@ -2,17 +2,7 @@ import React from 'react';
import moment from "moment";
import { formatPublicKey } from '../lib/meshcore';
import { getNameIconLabel } from '../lib/meshcore-map-nodeutils';
-
-type NodePosition = {
- node_id: string;
- latitude: number;
- longitude: number;
- altitude?: number;
- last_seen?: string;
- type?: string;
- short_name?: string;
- name?: string | null;
-};
+import { NodePosition } from '../types/map';
interface NodeMarkerProps {
node: NodePosition;
@@ -163,6 +153,14 @@ export function PopupContent({ node }: PopupContentProps) {
) : (
Last seen: -
)}
+ {node.first_seen ? (
+
+ First seen: {moment.utc(node.first_seen).format('YYYY-MM-DD HH:mm:ss')} (UTC)
+ {moment.utc(node.first_seen).local().fromNow()}
+
+ ) : (
+ First seen: -
+ )}
);
}
diff --git a/src/components/MapView.tsx b/src/components/MapView.tsx
index c038ab6..58816d4 100644
--- a/src/components/MapView.tsx
+++ b/src/components/MapView.tsx
@@ -12,6 +12,7 @@ import RefreshButton from "@/components/RefreshButton";
import { NodeMarker, ClusterMarker, PopupContent } from "./MapIcons";
import { renderToString } from "react-dom/server";
import { buildApiUrl } from "../lib/api";
+import { NodePosition } from "../types/map";
const DEFAULT = {
lat: 46.56, // Center between Seattle and Portland
@@ -19,17 +20,6 @@ const DEFAULT = {
zoom: 7, // Zoom level to show both cities
};
-type NodePosition = {
- node_id: string;
- latitude: number;
- longitude: number;
- altitude?: number;
- last_seen?: string;
- type?: string;
- short_name?: string;
- name?: string | null;
-};
-
type ClusteredMarkersProps = { nodes: NodePosition[] };
// Individual marker component
diff --git a/src/lib/clickhouse/actions.ts b/src/lib/clickhouse/actions.ts
index 55638a5..c6d2e2e 100644
--- a/src/lib/clickhouse/actions.ts
+++ b/src/lib/clickhouse/actions.ts
@@ -33,7 +33,7 @@ export async function getNodePositions({ minLat, maxLat, minLng, maxLng, nodeTyp
where.push(`last_seen >= now() - INTERVAL {lastSeen:UInt32} SECOND`);
params.lastSeen = Number(lastSeen);
}
- const query = `SELECT node_id, name, short_name, latitude, longitude, last_seen, type FROM unified_latest_nodeinfo WHERE ${where.join(" AND ")}`;
+ const query = `SELECT node_id, name, short_name, latitude, longitude, last_seen, first_seen, type FROM unified_latest_nodeinfo WHERE ${where.join(" AND ")}`;
const resultSet = await clickhouse.query({ query, query_params: params, format: 'JSONEachRow' });
const rows = await resultSet.json();
return rows as Array<{
@@ -43,6 +43,7 @@ export async function getNodePositions({ minLat, maxLat, minLng, maxLng, nodeTyp
latitude: number;
longitude: number;
last_seen: string;
+ first_seen?: string;
type: string;
}>;
} catch (error) {
diff --git a/src/types/map.ts b/src/types/map.ts
new file mode 100644
index 0000000..2ac685d
--- /dev/null
+++ b/src/types/map.ts
@@ -0,0 +1,11 @@
+export type NodePosition = {
+ node_id: string;
+ latitude: number;
+ longitude: number;
+ altitude?: number;
+ last_seen?: string;
+ first_seen?: string;
+ type?: string;
+ short_name?: string;
+ name?: string | null;
+};