mirror of
https://github.com/ajvpot/meshexplorer.git
synced 2026-07-06 01:30:58 +02:00
multiple relayers
This commit is contained in:
@@ -65,8 +65,8 @@ export default function MessagesPage() {
|
||||
const data = await res.json();
|
||||
if (Array.isArray(data) && data.length > 0) {
|
||||
// Only prepend messages that are not already present
|
||||
const existingKeys = new Set(messages.map((m) => m.ingest_timestamp + m.origin));
|
||||
const newMessages = data.filter((msg: any) => !existingKeys.has(msg.ingest_timestamp + msg.origin));
|
||||
const existingKeys = new Set(messages.map((m) => m.ingest_timestamp + (m.origins?.join(',') ?? '')));
|
||||
const newMessages = data.filter((msg: any) => !existingKeys.has(msg.ingest_timestamp + (msg.origins?.join(',') ?? '')));
|
||||
if (newMessages.length > 0) {
|
||||
setMessages((prev) => [...newMessages, ...prev]);
|
||||
}
|
||||
@@ -131,7 +131,7 @@ export default function MessagesPage() {
|
||||
<div className="text-gray-400 text-center py-8">No chat messages found.</div>
|
||||
)}
|
||||
{messages.map((msg, i) => (
|
||||
<ChatMessageItem key={msg.ingest_timestamp + msg.origin + i} msg={msg} showErrorRow showChannelId={true} />
|
||||
<ChatMessageItem key={msg.ingest_timestamp + (msg.origins?.join(',') ?? '') + i} msg={msg} showErrorRow showChannelId={true} />
|
||||
))}
|
||||
{loading && (
|
||||
<div className="text-center py-4 text-gray-400">Loading...</div>
|
||||
|
||||
@@ -122,7 +122,7 @@ export default function ChatBox() {
|
||||
<div className="text-gray-400 text-center mt-8">No chat messages found.</div>
|
||||
)}
|
||||
{messages.map((msg, i) => (
|
||||
<ChatMessageItem key={msg.ingest_timestamp + msg.origin + i} msg={msg} />
|
||||
<ChatMessageItem key={msg.ingest_timestamp + (msg.origins?.join(',') ?? '') + i} msg={msg} />
|
||||
))}
|
||||
{hasMore && (
|
||||
<button
|
||||
|
||||
@@ -5,7 +5,7 @@ import { decryptMeshcoreGroupMessage } from "../lib/meshcore_decrypt";
|
||||
|
||||
export interface ChatMessage {
|
||||
ingest_timestamp: string;
|
||||
origin: string;
|
||||
origins: string[];
|
||||
mesh_timestamp: string;
|
||||
packet: string;
|
||||
path_len: number;
|
||||
@@ -71,7 +71,7 @@ export default function ChatMessageItem({ msg, showErrorRow, showChannelId }: {
|
||||
{parsed.sender && ": "}
|
||||
<span>{parsed.text}</span>
|
||||
</div>
|
||||
<div className="text-xs text-gray-300">Relayed by: {msg.origin}</div>
|
||||
<div className="text-xs text-gray-300">Relayed by: {msg.origins && msg.origins.length > 0 ? msg.origins.map(o => o.slice(0, 6)).join(", ") : "-"}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -86,7 +86,7 @@ export default function ChatMessageItem({ msg, showErrorRow, showChannelId }: {
|
||||
<span className="text-xs text-purple-600 dark:text-purple-300 ml-2">channel: {msg.channel_hash}</span>
|
||||
)}
|
||||
</div>
|
||||
<div className="text-xs text-gray-300">Relayed by: {msg.origin}</div>
|
||||
<div className="text-xs text-gray-300">Relayed by: {msg.origins && msg.origins.length > 0 ? msg.origins.map(o => o.slice(0, 6)).join(", ") : "-"}</div>
|
||||
</div>
|
||||
);
|
||||
}else{
|
||||
@@ -103,7 +103,7 @@ export default function ChatMessageItem({ msg, showErrorRow, showChannelId }: {
|
||||
)}
|
||||
</div>
|
||||
<div className="w-full h-5 bg-gray-200 dark:bg-neutral-800 rounded animate-pulse my-2" />
|
||||
<div className="text-xs text-gray-300">Relayed by: {msg.origin}</div>
|
||||
<div className="text-xs text-gray-300">Relayed by: {msg.origins && msg.origins.length > 0 ? msg.origins.map(o => o.slice(0, 6)).join(", ") : "-"}</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
@@ -15,6 +15,7 @@ export type Config = {
|
||||
clustering?: boolean; // add clustering toggle
|
||||
showNodeNames?: boolean; // add show node names toggle
|
||||
meshcoreKeys?: MeshcoreKey[]; // meshcore private keys
|
||||
showMeshcoreCoverageOverlay?: boolean; // meshcore overlay toggle
|
||||
};
|
||||
|
||||
const TILE_LAYERS = [
|
||||
@@ -30,6 +31,7 @@ const DEFAULT_CONFIG: Config = {
|
||||
clustering: true, // default to clustering enabled
|
||||
showNodeNames: true, // default to show node names
|
||||
meshcoreKeys: [], // default empty
|
||||
showMeshcoreCoverageOverlay: false, // meshcore overlay default
|
||||
};
|
||||
|
||||
const LAST_SEEN_OPTIONS = [
|
||||
@@ -214,6 +216,16 @@ function ConfigPopover({ config, setConfig, onClose, anchorRef, onOpenKeyModal }
|
||||
<span>Show node names</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<label className="flex items-center gap-2">
|
||||
<input
|
||||
type="checkbox"
|
||||
checked={config.showMeshcoreCoverageOverlay === true}
|
||||
onChange={e => setConfig({ ...config, showMeshcoreCoverageOverlay: e.target.checked })}
|
||||
/>
|
||||
<span>Show meshcore coverage overlay</span>
|
||||
</label>
|
||||
</div>
|
||||
<div className="mb-2">
|
||||
<button
|
||||
className="px-3 py-2 bg-blue-600 text-white rounded hover:bg-blue-700 w-full"
|
||||
|
||||
@@ -397,6 +397,18 @@ export default function MapView() {
|
||||
maxZoom={selectedTileLayer.maxZoom}
|
||||
{...(selectedTileLayer.subdomains ? { subdomains: selectedTileLayer.subdomains } : {})}
|
||||
/>
|
||||
{config?.showMeshcoreCoverageOverlay && (
|
||||
<TileLayer
|
||||
url="https://tiles.w0z.is/tiles/{z}/{x}/{y}.png"
|
||||
attribution="Meshcore Coverage © <a href='https://w0z.is/'>w0z.is</a>"
|
||||
minZoom={1}
|
||||
maxZoom={22}
|
||||
minNativeZoom={11}
|
||||
maxNativeZoom={11}
|
||||
zIndex={1000}
|
||||
opacity={0.7}
|
||||
/>
|
||||
)}
|
||||
<ClusteredMarkers nodes={nodePositions} />
|
||||
</MapContainer>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { clickhouse } from "./clickhouse";
|
||||
|
||||
export async function getNodePositions({ minLat, maxLat, minLng, maxLng, nodeTypes, lastSeen }: { minLat?: string | null, maxLat?: string | null, minLng?: string | null, maxLng?: string | null, nodeTypes?: string[], lastSeen?: string | null } = {}) {
|
||||
try {
|
||||
let where = [
|
||||
"latitude IS NOT NULL",
|
||||
"longitude IS NOT NULL"
|
||||
@@ -43,36 +44,45 @@ export async function getNodePositions({ minLat, maxLat, minLng, maxLng, nodeTyp
|
||||
last_seen: string;
|
||||
type: string;
|
||||
}>;
|
||||
} catch (error) {
|
||||
console.error('ClickHouse error in getNodePositions:', error);
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
export async function getLatestChatMessages({ limit = 20, before, after, channelId }: { limit?: number, before?: string, after?: string, channelId?: string } = {}) {
|
||||
let where = [];
|
||||
const params: Record<string, any> = { limit };
|
||||
if (before) {
|
||||
where.push('ingest_timestamp < {before:DateTime64}');
|
||||
params.before = before;
|
||||
try {
|
||||
let where = [];
|
||||
const params: Record<string, any> = { limit };
|
||||
if (before) {
|
||||
where.push('ingest_timestamp < {before:DateTime64}');
|
||||
params.before = before;
|
||||
}
|
||||
if (after) {
|
||||
where.push('ingest_timestamp > {after:DateTime64}');
|
||||
params.after = after;
|
||||
}
|
||||
if (channelId) {
|
||||
where.push('channel_hash = {channelId:String}');
|
||||
params.channelId = channelId;
|
||||
}
|
||||
const whereClause = where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';
|
||||
const query = `SELECT ingest_timestamp, origins, mesh_timestamp, packet, path_len, path, channel_hash, mac, hex(encrypted_message) AS encrypted_message FROM meshcore_public_channel_messages ${whereClause} ORDER BY ingest_timestamp DESC LIMIT {limit:UInt32}`;
|
||||
const resultSet = await clickhouse.query({ query, query_params: params, format: 'JSONEachRow' });
|
||||
const rows = await resultSet.json();
|
||||
return rows as Array<{
|
||||
ingest_timestamp: string;
|
||||
origins: string[];
|
||||
mesh_timestamp: string;
|
||||
packet: string;
|
||||
path_len: number;
|
||||
path: string;
|
||||
channel_hash: string;
|
||||
mac: string;
|
||||
encrypted_message: string;
|
||||
}>;
|
||||
} catch (error) {
|
||||
console.error('ClickHouse error in getLatestChatMessages:', error);
|
||||
throw error;
|
||||
}
|
||||
if (after) {
|
||||
where.push('ingest_timestamp > {after:DateTime64}');
|
||||
params.after = after;
|
||||
}
|
||||
if (channelId) {
|
||||
where.push('channel_hash = {channelId:String}');
|
||||
params.channelId = channelId;
|
||||
}
|
||||
const whereClause = where.length > 0 ? `WHERE ${where.join(' AND ')}` : '';
|
||||
const query = `SELECT ingest_timestamp, origin, mesh_timestamp, packet, path_len, path, channel_hash, mac, hex(encrypted_message) AS encrypted_message FROM meshcore_public_channel_messages ${whereClause} ORDER BY ingest_timestamp DESC LIMIT {limit:UInt32}`;
|
||||
const resultSet = await clickhouse.query({ query, query_params: params, format: 'JSONEachRow' });
|
||||
const rows = await resultSet.json();
|
||||
return rows as Array<{
|
||||
ingest_timestamp: string;
|
||||
origin: string;
|
||||
mesh_timestamp: string;
|
||||
packet: string;
|
||||
path_len: number;
|
||||
path: string;
|
||||
channel_hash: string;
|
||||
mac: string;
|
||||
encrypted_message: string;
|
||||
}>;
|
||||
}
|
||||
Reference in New Issue
Block a user