@ -64,7 +64,7 @@ AccountList::AccountList(DbManager* db, QWidget *parent)
, m_listView ( new QListView ( this ) )
, m_model ( new MemberListModel ( this ) )
, m_db ( db )
, m_detailText ( nullptr )
, m_detailPanel ( nullptr )
{
auto * layout = new QVBoxLayout ( this ) ;
layout - > setContentsMargins ( 0 , 0 , 0 , 0 ) ;
@ -83,17 +83,25 @@ AccountList::AccountList(DbManager* db, QWidget *parent)
m_listView - > setHorizontalScrollBarPolicy ( Qt : : ScrollBarAlwaysOff ) ;
m_listView - > setVerticalScrollMode ( QAbstractItemView : : ScrollPerPixel ) ;
m_listView - > setContextMenuPolicy ( Qt : : CustomContextMenu ) ;
m_detailText = new QTextEdit ( this ) ;
m_detailPanel = new QWidget ( this ) ;
auto * detailLayout = new QVBoxLayout ( m_detailPanel ) ;
detailLayout - > setContentsMargins ( 0 , 0 , 0 , 0 ) ;
detailLayout - > setSpacing ( 0 ) ;
m_detailHeader = new QLabel ( " Select a member " , m_detailPanel ) ;
m_detailHeader - > setWordWrap ( true ) ;
m_detailHeader - > setStyleSheet ( " font-weight: bold; padding: 4px 8px; background: palette(window); " ) ;
detailLayout - > addWidget ( m_detailHeader ) ;
m_detailText = new QTextEdit ( m_detailPanel ) ;
m_detailText - > setReadOnly ( true ) ;
m_detailText - > setFont ( QFont ( " monospace " , 10 ) ) ;
m_detailText - > setStyleSheet (
" QTextEdit { border-top: 1px solid palette(mid); border-bottom: none; "
" border-left: none; border-right: none; padding: 6px; background: palette(base); } " ) ;
m_detailText - > setPlaceholderText ( " Select a member " ) ;
m_detailText - > setFont ( QFont ( " monospace " , 9 ) ) ;
m_detailText - > setStyleSheet ( " QTextEdit { border: none; padding: 4px; background: palette(base); } " ) ;
detailLayout - > addWidget ( m_detailText , 1 ) ;
auto * memberSplitter = new QSplitter ( Qt : : Vertical , this ) ;
memberSplitter - > addWidget ( m_listView ) ;
memberSplitter - > addWidget ( m_detailText ) ;
memberSplitter - > addWidget ( m_detailPanel ) ;
memberSplitter - > setStretchFactor ( 0 , 3 ) ;
memberSplitter - > setStretchFactor ( 1 , 2 ) ;
memberSplitter - > setSizes ( { 360 , 240 } ) ;
@ -119,6 +127,19 @@ AccountList::AccountList(DbManager* db, QWidget *parent)
g_detailTarget = this ;
gui_bridge_set_member_detail_cb ( memberDetailCb ) ;
static bool nodeinfoRegistered = false ;
if ( ! nodeinfoRegistered ) {
gui_bridge_set_nodeinfo_update_cb ( [ ] ( const uint8_t * data , int len ) {
if ( g_detailTarget ) g_detailTarget - > onNodeinfoUpdate ( data , len ) ;
} ) ;
gui_bridge_set_bgp_info_cb ( [ ] ( const uint8_t * data , int len ) {
if ( ! g_detailTarget ) return ;
g_detailTarget - > m_bgpText = QString : : fromUtf8 ( ( const char * ) data , len ) ;
g_detailTarget - > updateDetailDisplay ( ) ;
} ) ;
nodeinfoRegistered = true ;
}
}
AccountList : : ~ AccountList ( ) {
@ -131,6 +152,7 @@ void AccountList::setChannel(const QString& channelId) {
m_detailTimer - > stop ( ) ;
m_linkTimer - > stop ( ) ;
m_detailNodeId = 0 ;
m_detailHeader - > setText ( " Select a member " ) ;
m_detailText - > clear ( ) ;
}
@ -157,6 +179,10 @@ void AccountList::showMemberDetail(quint64 nodeId) {
m_snapConnUp = false ;
m_snapConnInit = false ;
m_snapOwnTcpActive = 0 ;
m_snapChainRtt = 0 ;
m_snapHopCount = 0 ;
m_snapBgpNodes = 0 ;
m_bgpText . clear ( ) ;
m_snapLinks . clear ( ) ;
m_snapSocks . clear ( ) ;
@ -266,6 +292,7 @@ void AccountList::onMemberDetailData(uint64_t nodeId, const uint8_t* data, int l
lk . family = p [ 24 ] ;
lk . addr = QByteArray ( ( const char * ) p + 25 , 16 ) ;
memcpy ( & lk . port , p + 41 , 2 ) ;
memcpy ( lk . sockName , p + 43 , 20 ) ; lk . sockName [ 19 ] = ' \0 ' ;
p + = MEMBER_DETAIL_LINK_SIZE ;
m_snapLinks . append ( lk ) ;
}
@ -278,7 +305,8 @@ void AccountList::onMemberDetailData(uint64_t nodeId, const uint8_t* data, int l
MemberSockSnap s ;
memcpy ( & s . sockId , p , 4 ) ;
memcpy ( & s . port , p + 4 , 2 ) ;
memcpy ( & s . linkCount , p + 6 , 4 ) ;
uint16_t lu = 0 , ld = 0 ; memcpy ( & lu , p + 6 , 2 ) ; memcpy ( & ld , p + 8 , 2 ) ;
s . linksUp = lu ; s . linksDown = ld ;
s . ifname = QString : : fromUtf8 ( ( const char * ) p + 10 ) . trimmed ( ) ;
p + = MEMBER_DETAIL_SOCK_SIZE ;
m_snapSocks . append ( s ) ;
@ -302,68 +330,92 @@ void AccountList::onMemberDetailData(uint64_t nodeId, const uint8_t* data, int l
}
void AccountList : : updateDetailDisplay ( ) {
QStringList lines ;
lines . append ( QString ( " ID:0x%1 " ) . arg ( m_detailNodeId , 16 , 16 , QChar ( ' 0 ' ) ) ) ;
bool isSelf = m_db ? ( m_detailNodeId = = m_db - > myNodeId ( ) ) : false ;
QString nodeHex = QString ( " 0x%1 " ) . arg ( m_detailNodeId , 16 , 16 , QChar ( ' 0 ' ) ) ;
/* ── Header ── */
QString dotColor = m_detailOnline ? " #4CAF50 " : " #9E9E9E " ;
lines . append ( QString ( " <span style='color:%1;'>●</span> %2 " )
. arg ( dotColor , m_detailName . toHtmlEscaped ( ) ) ) ;
QStringList hdr ;
hdr . append ( QString ( " <span style='color:%1;'>●</span> %2 %3 " )
. arg ( dotColor , m_detailName . toHtmlEscaped ( ) , nodeHex ) ) ;
if ( m_detailLastSeen > 0 )
lines . append ( QString ( " ⌛ %1 " ) . arg ( relativeTimeStr ( m_detailLastSeen ) ) ) ;
hdr . append ( QString ( " seen %1 " ) . arg ( relativeTimeStr ( m_detailLastSeen ) ) ) ;
if ( m_detailCreated > 0 )
lines . append ( QString ( " 📅 %1 " ) . arg ( relativeTimeStr ( m_detailCreated ) ) ) ;
hdr . append ( QString ( " created %1 " ) . arg ( relativeTimeStr ( m_detailCreated ) ) ) ;
m_detailHeader - > setText ( hdr . join ( " " ) ) ;
bool isSelf = m_db ? ( m_detailNodeId = = m_db - > myNodeId ( ) ) : false ;
/* ── Body ── */
QStringList lines ;
if ( isSelf ) {
if ( ! m_detailAddrs . isEmpty ( ) ) {
lines . append ( " ── Addresses ── " ) ;
for ( const auto & a : m_detailAddrs ) {
QString proto ;
switch ( a . protocol ) { case 1 : proto = " UDP " ; break ; case 2 : proto = " TCP " ; break ; default : proto = " UDP/TCP " ; break ; }
int lc = 0 ;
if ( a . protocol = = 1 ) { for ( const auto & s : m_snapSocks ) { if ( s . port = = a . port ) { lc = s . linkCount ; break ; } } }
else if ( a . protocol = = 2 ) lc = m_snapOwnTcpActive ;
QString prefix = lc > 0 ? QString ( " [%1] " ) . arg ( lc ) : " " ;
if ( ! a . ifname . isEmpty ( ) )
lines . append ( QString ( " %1● %2 %3 " ) . arg ( prefix , proto , a . ifname ) ) ;
else
lines . append ( QString ( " %1● %2:%3:%4 " ) . arg ( prefix , proto , addrStr ( a . addr . constData ( ) , a . family ) ) . arg ( a . port ) ) ;
switch ( a . protocol ) { case 1 : proto = " UDP " ; break ; case 2 : proto = " TCP " ; break ; default : proto = " ? " ; break ; }
lines . append ( QString ( " %1 %2 %3:%4 " )
. arg ( proto )
. arg ( a . ifname )
. arg ( addrStr ( a . addr . constData ( ) , a . family ) )
. arg ( a . port ) ) ;
}
} else {
/* all live links with arrows */
for ( const auto & lk : m_snapLinks ) {
QString proto = lk . isTcp ? " TCP " : " UDP " ;
QString lc = ( lk . linkState = = 3 & & lk . linkStatus = = 1 ) ? " #4CAF50 " : " #E53935 " ;
QString left = QString ( " %1:%2 " ) . arg ( addrStr ( lk . localAddr . constData ( ) , lk . localFamily ) ) . arg ( lk . localPort ) ;
QString right = QString ( " %1:%2 " ) . arg ( addrStr ( lk . addr . constData ( ) , lk . family ) ) . arg ( lk . port ) ;
const char * arrow = lk . isServer
? " <b style='color:#FF9800;font-size:13pt;'>◀</b> "
: " <b style='color:#2196F3;font-size:13pt;'>▶</b> " ;
if ( lk . localFamily = = 6 | | lk . family = = 6 ) {
lines . append ( QString ( " <span style='color:%1;'>●</span> %2 %3:%4 " ) . arg ( lc , proto , addrStr ( lk . localAddr . constData ( ) , lk . localFamily ) ) . arg ( lk . localPort ) ) ;
lines . append ( QString ( " %1 %2:%3 " ) . arg ( arrow , addrStr ( lk . addr . constData ( ) , lk . family ) ) . arg ( lk . port ) ) ;
} else {
lines . append ( QString ( " <span style='color:%1;'>●</span> %2 %3 %4 %5 " )
. arg ( lc , proto , left , arrow , right ) ) ;
}
if ( isSelf ) {
lines . append ( " ── Listen ── " ) ;
for ( const auto & s : m_snapSocks ) {
const DetailAddress * a = nullptr ;
for ( const auto & da : m_detailAddrs ) {
if ( da . socket_id = = s . sockId ) { a = & da ; break ; }
}
QString proto = a ? ( a - > protocol = = 2 ? " TCP " : " UDP " ) : " ? " ;
QString ip = a ? addrStr ( a - > addr . constData ( ) , a - > family ) : " ? " ;
lines . append ( QString ( " [%1/%2] %3:%4 %5 " )
. arg ( s . linksUp ) . arg ( s . linksDown )
. arg ( ip ) . arg ( s . port )
. arg ( proto ) ) ;
}
/* DB addresses without live link */
for ( const auto & a : m_detailAddrs ) {
bool matched = false ;
} else {
if ( ! m_snapLinks . isEmpty ( ) ) {
lines . append ( " ── Connections ── " ) ;
for ( const auto & lk : m_snapLinks ) {
if ( lk . family = = a . family & & lk . port = = a . port & & lk . addr = = a . addr ) { matched = true ; break ; }
bool up = ( lk . linkState = = 3 & & lk . linkStatus = = 1 ) ;
QString c = up ? " #4CAF50 " : " #E53935 " ;
lines . append ( QString ( " <span style='color:%1;'>●</span> %2 %3:%4 %5 %6:%7 " )
. arg ( c )
. arg ( lk . isTcp ? " TCP " : " UDP " )
. arg ( addrStr ( lk . localAddr . constData ( ) , lk . localFamily ) ) . arg ( lk . localPort )
. arg ( lk . isServer ? " \u25c0 " : " \u25b6 " )
. arg ( addrStr ( lk . addr . constData ( ) , lk . family ) ) . arg ( lk . port ) ) ;
if ( lk . sockName [ 0 ] )
lines . append ( QString ( " %1 " ) . arg ( lk . sockName ) ) ;
}
if ( matched ) continue ;
QString proto ;
switch ( a . protocol ) { case 1 : proto = " UDP " ; break ; case 2 : proto = " TCP " ; break ; default : proto = " UDP/TCP " ; break ; }
lines . append ( QString ( " <span style='color:#9E9E9E;'>●</span> %1 %2:%3 " )
. arg ( proto , addrStr ( a . addr . constData ( ) , a . family ) ) . arg ( a . port ) ) ;
}
/* summary */
if ( ! m_bgpText . isEmpty ( ) ) {
lines . append ( m_bgpText ) ;
} else if ( m_snapConnUp ) {
lines . append ( " UP " ) ;
}
}
m_detailText - > setHtml ( lines . join ( " <br> " ) ) ;
if ( lines . isEmpty ( ) )
m_detailText - > clear ( ) ;
else
m_detailText - > setHtml ( QString ( " <pre style='margin:0;'> " ) + lines . join ( " \n " ) + " </pre> " ) ;
}
void AccountList : : onNodeinfoUpdate ( const uint8_t * data , int len ) {
if ( len < 16 | | m_detailNodeId = = 0 ) return ;
uint64_t nodeId ; memcpy ( & nodeId , data , 8 ) ;
if ( nodeId ! = m_detailNodeId ) return ;
m_snapConnPresent = ( data [ 8 ] & 1 ) ! = 0 ;
m_snapConnUp = ( data [ 9 ] & 1 ) ! = 0 ;
memcpy ( & m_snapChainRtt , data + 10 , 2 ) ;
m_snapHopCount = data [ 12 ] ;
memcpy ( & m_snapBgpNodes , data + 14 , 2 ) ;
/* data[13] is reserved/unused */
if ( m_snapConnPresent )
updateDetailDisplay ( ) ;
}
void AccountList : : onDetailTimerTick ( ) {