Skip to content

Commit dbba983

Browse files
committed
Updates for node status
1 parent ffe34a7 commit dbba983

File tree

5 files changed

+237
-25
lines changed

5 files changed

+237
-25
lines changed

frontend-react/src/components/Dashboard.tsx

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,22 +8,24 @@ import { MessageTypeFilter } from './MessageTypeFilter';
88
import { PeerFilter } from './PeerFilter';
99
import PaginationComponent from './Pagination';
1010
import WebSocketStatusComponent from './WebSocketStatus';
11-
import {
12-
BlockCard,
11+
import {
12+
BlockCard,
1313
// MiningCard, // Temporarily disabled
14-
SubtreeCard,
15-
HandshakeCard,
16-
RejectedTxCard
14+
SubtreeCard,
15+
HandshakeCard,
16+
RejectedTxCard,
17+
NodeStatusCard
1718
} from './MessageCards';
18-
import {
19-
Network,
20-
MessageType,
19+
import {
20+
Network,
21+
MessageType,
2122
DashboardFilters,
2223
Block,
2324
MiningOn,
2425
Subtree,
2526
Handshake,
2627
RejectedTx,
28+
NodeStatus,
2729
Message,
2830
PaginationInfo
2931
} from '../types/Message';
@@ -185,6 +187,8 @@ export const Dashboard: React.FC = () => {
185187
return <HandshakeCard key={item.ID || index} handshake={item as Handshake} isNew={isNew} />;
186188
case 'rejected_tx':
187189
return <RejectedTxCard key={item.ID || index} rejectedTx={item as RejectedTx} isNew={isNew} />;
190+
case 'node_status':
191+
return <NodeStatusCard key={item.ID || index} nodeStatus={item as NodeStatus} isNew={isNew} />;
188192
default:
189193
// Should not reach here - log error and return null
190194
console.error('Unknown message type:', messageType, 'for item:', item);

frontend-react/src/components/MessageCards.tsx

Lines changed: 198 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import { Block, MiningOn, Subtree, Handshake, RejectedTx } from '../types/Message';
2+
import { Block, MiningOn, Subtree, Handshake, RejectedTx, NodeStatus } from '../types/Message';
33
import { ClickablePeerNameDisplay } from './ClickablePeerNameDisplay';
44

55
interface MessageCardBaseProps {
@@ -323,7 +323,7 @@ export const RejectedTxCard: React.FC<RejectedTxCardProps> = ({ rejectedTx, isNe
323323
{rejectedTx.TxID || 'N/A'}
324324
</p>
325325
</div>
326-
326+
327327
<div>
328328
<span className="text-xs sm:text-sm font-medium text-gray-500">Reason</span>
329329
<p className="text-xs sm:text-sm text-gray-900 bg-red-50 p-2 rounded break-words">
@@ -333,3 +333,199 @@ export const RejectedTxCard: React.FC<RejectedTxCardProps> = ({ rejectedTx, isNe
333333
</MessageCardBase>
334334
);
335335

336+
interface NodeStatusCardProps {
337+
nodeStatus: NodeStatus;
338+
isNew?: boolean;
339+
}
340+
341+
export const NodeStatusCard: React.FC<NodeStatusCardProps> = ({ nodeStatus, isNew }) => {
342+
const formatUptime = (seconds: number): string => {
343+
if (!seconds || seconds <= 0) return 'N/A';
344+
345+
const days = Math.floor(seconds / 86400);
346+
const hours = Math.floor((seconds % 86400) / 3600);
347+
const minutes = Math.floor((seconds % 3600) / 60);
348+
349+
if (days > 0) {
350+
return `${days}d ${hours}h ${minutes}m`;
351+
} else if (hours > 0) {
352+
return `${hours}h ${minutes}m`;
353+
} else {
354+
return `${minutes}m`;
355+
}
356+
};
357+
358+
const formatStartTime = (timestamp: number): string => {
359+
if (!timestamp || timestamp <= 0) return 'N/A';
360+
try {
361+
const date = new Date(timestamp * 1000);
362+
return date.toLocaleString();
363+
} catch {
364+
return 'N/A';
365+
}
366+
};
367+
368+
const formatSyncTime = (timestamp: number): string => {
369+
if (!timestamp || timestamp <= 0) return 'N/A';
370+
try {
371+
const date = new Date(timestamp * 1000);
372+
const now = new Date();
373+
const diffInSeconds = Math.floor((now.getTime() - date.getTime()) / 1000);
374+
375+
if (diffInSeconds < 60) return `${diffInSeconds}s ago`;
376+
if (diffInSeconds < 3600) return `${Math.floor(diffInSeconds / 60)}m ago`;
377+
if (diffInSeconds < 86400) return `${Math.floor(diffInSeconds / 3600)}h ago`;
378+
return `${Math.floor(diffInSeconds / 86400)}d ago`;
379+
} catch {
380+
return 'N/A';
381+
}
382+
};
383+
384+
const getFSMStateColor = (state: string): string => {
385+
const stateColors: Record<string, string> = {
386+
'running': 'text-green-600',
387+
'syncing': 'text-blue-600',
388+
'stopped': 'text-red-600',
389+
'paused': 'text-yellow-600',
390+
'idle': 'text-gray-600'
391+
};
392+
return stateColors[state?.toLowerCase()] || 'text-gray-600';
393+
};
394+
395+
return (
396+
<MessageCardBase
397+
icon="🖥️"
398+
title={`Node Status - ${nodeStatus.Type || 'UNKNOWN'}`}
399+
network={nodeStatus.Network}
400+
receivedAt={nodeStatus.ReceivedAt}
401+
isNew={isNew}
402+
className="border-l-4 border-l-blue-400"
403+
sentFromPeer={nodeStatus.sentFromPeer}
404+
peerID={nodeStatus.PeerID}
405+
>
406+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
407+
<div>
408+
<span className="text-xs sm:text-sm font-medium text-gray-500">Client Name</span>
409+
<p className="text-xs sm:text-sm text-gray-900">{nodeStatus.ClientName || 'Unknown'}</p>
410+
</div>
411+
<div>
412+
<span className="text-xs sm:text-sm font-medium text-gray-500">Version</span>
413+
<p className="text-xs sm:text-sm text-gray-900">{nodeStatus.Version || 'N/A'}</p>
414+
</div>
415+
</div>
416+
417+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
418+
<div>
419+
<span className="text-xs sm:text-sm font-medium text-gray-500">Best Height</span>
420+
<p className="text-xs sm:text-sm text-gray-900">
421+
{nodeStatus.BestHeight?.toLocaleString() || 'N/A'}
422+
</p>
423+
</div>
424+
<div>
425+
<span className="text-xs sm:text-sm font-medium text-gray-500">FSM State</span>
426+
<p className={`text-xs sm:text-sm font-semibold ${getFSMStateColor(nodeStatus.FSMState)}`}>
427+
{nodeStatus.FSMState || 'Unknown'}
428+
</p>
429+
</div>
430+
</div>
431+
432+
<div>
433+
<span className="text-sm font-medium text-gray-500">Best Block Hash</span>
434+
<p className="font-mono text-sm text-gray-900 truncate" title={nodeStatus.BestBlockHash || ''}>
435+
{nodeStatus.BestBlockHash || 'N/A'}
436+
</p>
437+
</div>
438+
439+
{nodeStatus.CommitHash && (
440+
<div>
441+
<span className="text-sm font-medium text-gray-500">Commit Hash</span>
442+
<p className="font-mono text-sm text-gray-900 truncate" title={nodeStatus.CommitHash}>
443+
{nodeStatus.CommitHash.substring(0, 12)}
444+
</p>
445+
</div>
446+
)}
447+
448+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
449+
<div>
450+
<span className="text-xs sm:text-sm font-medium text-gray-500">Uptime</span>
451+
<p className="text-xs sm:text-sm text-gray-900">{formatUptime(nodeStatus.Uptime)}</p>
452+
</div>
453+
<div>
454+
<span className="text-xs sm:text-sm font-medium text-gray-500">Started</span>
455+
<p className="text-xs sm:text-sm text-gray-900">{formatStartTime(nodeStatus.StartTime)}</p>
456+
</div>
457+
</div>
458+
459+
{nodeStatus.MinerName && (
460+
<div>
461+
<span className="text-sm font-medium text-gray-500">Miner</span>
462+
<p className="text-sm text-gray-900">{nodeStatus.MinerName}</p>
463+
</div>
464+
)}
465+
466+
{nodeStatus.ListenMode && (
467+
<div>
468+
<span className="text-sm font-medium text-gray-500">Listen Mode</span>
469+
<p className="text-sm text-gray-900">{nodeStatus.ListenMode}</p>
470+
</div>
471+
)}
472+
473+
{nodeStatus.ChainWork && (
474+
<div>
475+
<span className="text-sm font-medium text-gray-500">Chain Work</span>
476+
<p className="font-mono text-xs text-gray-900 truncate" title={nodeStatus.ChainWork}>
477+
{nodeStatus.ChainWork}
478+
</p>
479+
</div>
480+
)}
481+
482+
{nodeStatus.MinMiningTxFee !== undefined && nodeStatus.MinMiningTxFee !== null && (
483+
<div>
484+
<span className="text-sm font-medium text-gray-500">Min Mining TX Fee</span>
485+
<p className="text-sm text-gray-900">
486+
{nodeStatus.MinMiningTxFee === 0 ? 'No fee' : nodeStatus.MinMiningTxFee.toFixed(8) + ' BSV/byte'}
487+
</p>
488+
</div>
489+
)}
490+
491+
{nodeStatus.SyncPeerID && (
492+
<div className="border-t border-gray-100 pt-3 space-y-2">
493+
<div className="font-semibold text-sm text-gray-700">Sync Information</div>
494+
<div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
495+
<div>
496+
<span className="text-xs sm:text-sm font-medium text-gray-500">Sync Peer</span>
497+
<div className="text-xs sm:text-sm text-gray-900">
498+
<ClickablePeerNameDisplay
499+
peerID={nodeStatus.SyncPeerID}
500+
showBoth={true}
501+
className="text-gray-700 hover:text-gray-900"
502+
/>
503+
</div>
504+
</div>
505+
<div>
506+
<span className="text-xs sm:text-sm font-medium text-gray-500">Sync Height</span>
507+
<p className="text-xs sm:text-sm text-gray-900">
508+
{nodeStatus.SyncPeerHeight?.toLocaleString() || 'N/A'}
509+
</p>
510+
</div>
511+
</div>
512+
{nodeStatus.SyncPeerBlockHash && (
513+
<div>
514+
<span className="text-sm font-medium text-gray-500">Sync Block Hash</span>
515+
<p className="font-mono text-xs text-gray-900 truncate" title={nodeStatus.SyncPeerBlockHash}>
516+
{nodeStatus.SyncPeerBlockHash}
517+
</p>
518+
</div>
519+
)}
520+
{nodeStatus.SyncConnectedAt && (
521+
<div>
522+
<span className="text-sm font-medium text-gray-500">Connected</span>
523+
<p className="text-sm text-gray-900">{formatSyncTime(nodeStatus.SyncConnectedAt)}</p>
524+
</div>
525+
)}
526+
</div>
527+
)}
528+
</MessageCardBase>
529+
);
530+
};
531+

frontend-react/src/components/MessageListSection.tsx

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,8 @@
11
import React, { useState, useEffect } from 'react';
2-
import { Message, ApiResponse, Block, MiningOn, Subtree, Handshake, RejectedTx } from '../types/Message';
2+
import { Message, ApiResponse, Block, MiningOn, Subtree, Handshake, RejectedTx, NodeStatus } from '../types/Message';
33
import { ApiService } from '../services/api';
44
import { MessageParser } from '../utils/messageParser';
5-
import { BlockCard, /* MiningCard, */ SubtreeCard, HandshakeCard, RejectedTxCard } from './MessageCards';
5+
import { BlockCard, /* MiningCard, */ SubtreeCard, HandshakeCard, RejectedTxCard, NodeStatusCard } from './MessageCards';
66
import Pagination from './Pagination';
77

88
interface MessageListSectionProps {
@@ -134,6 +134,8 @@ export const MessageListSection: React.FC<MessageListSectionProps> = ({ peerID,
134134
return <HandshakeCard key={message.ID} handshake={parsedMessage as Handshake} isNew={false} />;
135135
case 'rejected_tx':
136136
return <RejectedTxCard key={message.ID} rejectedTx={parsedMessage as RejectedTx} isNew={false} />;
137+
case 'node_status':
138+
return <NodeStatusCard key={message.ID} nodeStatus={parsedMessage as NodeStatus} isNew={false} />;
137139
default:
138140
// Skip unknown message types
139141
console.warn('Unknown message type:', messageType, 'for message:', message);

frontend-react/src/types/Message.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -206,6 +206,7 @@ export interface NodeStatus {
206206
SyncPeerHeight?: number;
207207
SyncPeerBlockHash?: string;
208208
SyncConnectedAt?: number;
209+
MinMiningTxFee?: number; // Minimum mining transaction fee
209210
ReceivedAt: string;
210211
Topic?: string; // For WebSocket messages
211212
}

frontend-react/src/utils/messageParser.ts

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -131,19 +131,28 @@ export class MessageParser {
131131

132132
private static parseNodeStatusMessage(base: any, data: any): NodeStatus {
133133
return {
134-
...base,
135-
PeerID: data.peer_id || data.PeerID || data.peerId || base.sentFromPeer || '', // Try to get peer ID from data, fallback to sender
136-
Type: data.type || data.Type || 'unknown',
137-
Version: data.version || data.Version || 'unknown',
138-
Uptime: data.uptime || data.Uptime || 0,
139-
Connections: data.connections || data.Connections || 0,
140-
Blocks: data.blocks || data.Blocks || 0,
141-
Peers: data.peers || data.Peers || 0,
142-
TxPoolSize: data.tx_pool_size || data.TxPoolSize || 0,
143-
MemoryUsageMB: data.memory_usage_mb || data.MemoryUsageMB || 0,
144-
CPUUsagePercent: data.cpu_usage_percent || data.CPUUsagePercent || 0,
145-
DataHubURL: data.data_hub_url || data.DataHubURL || ''
146-
}
134+
...base,
135+
PeerID: data.peer_id || data.PeerID || data.peerId || base.sentFromPeer || '',
136+
Type: data.type || data.Type || '',
137+
BaseURL: data.base_url || data.BaseURL || data.baseUrl || '',
138+
Version: data.version || data.Version || '',
139+
CommitHash: data.commit_hash || data.CommitHash || data.commitHash || '',
140+
BestBlockHash: data.best_block_hash || data.BestBlockHash || data.bestBlockHash || '',
141+
BestHeight: data.best_height || data.BestHeight || data.bestHeight || 0,
142+
BlockAssemblyDetails: data.block_assembly_details || data.BlockAssemblyDetails || data.blockAssemblyDetails || null,
143+
FSMState: data.fsm_state || data.FSMState || data.fsmState || '',
144+
StartTime: data.start_time || data.StartTime || data.startTime || 0,
145+
Uptime: data.uptime || data.Uptime || 0,
146+
ClientName: data.client_name || data.ClientName || data.clientName || '',
147+
MinerName: data.miner_name || data.MinerName || data.minerName || '',
148+
ListenMode: data.listen_mode || data.ListenMode || data.listenMode || '',
149+
ChainWork: data.chain_work || data.ChainWork || data.chainWork || '',
150+
SyncPeerID: data.sync_peer_id || data.SyncPeerID || data.syncPeerId,
151+
SyncPeerHeight: data.sync_peer_height || data.SyncPeerHeight || data.syncPeerHeight,
152+
SyncPeerBlockHash: data.sync_peer_block_hash || data.SyncPeerBlockHash || data.syncPeerBlockHash,
153+
SyncConnectedAt: data.sync_connected_at || data.SyncConnectedAt || data.syncConnectedAt,
154+
MinMiningTxFee: data.min_mining_tx_fee || data.MinMiningTxFee || data.minMiningTxFee
155+
};
147156
}
148157

149158
}

0 commit comments

Comments
 (0)