android: member list screen + link status dots, ipv6 autoconnect fixes

Android chatgui:
- New MemberListScreen with member list, detail card, edit dialog
- Link status dots (green/red/grey) per address in detail card (1s poll)
- Self-node name coloured green
- ChatScreen: people icon in TopAppBar

IPv6 autoconnect fixes (3 bugs in conn_mgr/topo):
- topo_node_sqlite_node_load: load v6 addresses from DB
- topo_node_registry_store: merge v6_addrs into existing node
- cm_sock_v6_classify: wildcard [::] treated as CM_V6_DIR
- cm_add_v6_link: htons(port) was missing

Memory fixes:
- strdup→u_strdup in bridge (7 instances)
- u_realloc cap update before success check (3 instances)
- char sql[256]→[512] for truncated SELECT

Chat member hash now includes addresses for change detection

Linux chatgui:
- Link status dots in AccountList detail panel (1s poll)
- Self-node name in green in member list
This commit is contained in:
evgeny committed 2026-08-01 23:40:58 +03:00
1 parent 6719a1e23c
commit 50156e12af
19 files changed
+1022 -23

No files matched your search

+42 -1
View File
@@ -10,6 +10,13 @@
#include <QFont>
#include "../../../lib/platform_compat.h"
extern "C" {
#include "utun_instance.h"
#include "etcp.h"
#include "etcp_connections.h"
#include "../lib/socket_compat.h"
}
QString AccountList::addrStr(const void* addr, int family) {
if (family == 4) {
const uint8_t* a = (const uint8_t*)addr;
@@ -84,12 +91,19 @@ AccountList::AccountList(DbManager* db, QWidget *parent)
m_detailTimer = new QTimer(this);
m_detailTimer->setInterval(5000);
connect(m_detailTimer, &QTimer::timeout, this, &AccountList::onDetailTimerTick);
m_linkTimer = new QTimer(this);
m_linkTimer->setInterval(1000);
connect(m_linkTimer, &QTimer::timeout, this, [this]{
if (m_detailNodeId != 0) updateDetailDisplay();
});
}
void AccountList::setChannel(const QString& channelId) {
m_channelId = channelId;
m_model->setChannel(channelId);
m_detailTimer->stop();
m_linkTimer->stop();
m_detailNodeId = 0;
m_detailText->clear();
}
@@ -106,6 +120,7 @@ void AccountList::showMemberDetail(quint64 nodeId) {
if (!m_db || !m_db->m_db) return;
m_detailTimer->stop();
m_linkTimer->stop();
m_detailNodeId = nodeId;
m_detailName.clear();
m_detailOnline = false;
@@ -166,6 +181,7 @@ void AccountList::showMemberDetail(quint64 nodeId) {
}
m_detailTimer->start();
m_linkTimer->start();
updateDetailDisplay();
}
@@ -184,10 +200,35 @@ void AccountList::updateDetailDisplay() {
if (m_detailCreated > 0)
lines.append(QString("📅 %1").arg(relativeTimeStr(m_detailCreated)));
struct ETCP_CONN* conn = nullptr;
if (m_inst) conn = instance_find_conn(m_inst, m_detailNodeId);
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; }
lines.append(QString("%1:%2:%3").arg(proto, addrStr(a.addr.constData(), a.family)).arg(a.port));
QString linkColor = "#9E9E9E";
if (conn) {
for (struct ETCP_LINK* lk = conn->links; lk; lk = lk->next) {
const struct sockaddr_storage* sa = &lk->remote_addr;
int lfm = 0; QString lip; uint16_t lpo = 0;
char ipbuf[INET6_ADDRSTRLEN];
if (sa->ss_family == AF_INET) {
struct sockaddr_in* sin = (struct sockaddr_in*)sa;
inet_ntop(AF_INET, &sin->sin_addr, ipbuf, sizeof(ipbuf));
lfm = 4; lpo = ntohs(sin->sin_port); lip = QString::fromLocal8Bit(ipbuf);
} else if (sa->ss_family == AF_INET6) {
struct sockaddr_in6* sin6 = (struct sockaddr_in6*)sa;
inet_ntop(AF_INET6, &sin6->sin6_addr, ipbuf, sizeof(ipbuf));
lfm = 6; lpo = ntohs(sin6->sin6_port); lip = QString::fromLocal8Bit(ipbuf);
}
if (lfm == a.family && lpo == (uint16_t)a.port && lip == addrStr(a.addr.constData(), a.family)) {
linkColor = (lk->link_state == 3 && lk->link_status == 1) ? "#4CAF50" : "#E53935";
break;
}
}
}
lines.append(QString("<span style='color:%1;'>●</span> %2:%3:%4")
.arg(linkColor, proto, addrStr(a.addr.constData(), a.family)).arg(a.port));
}
m_detailText->setHtml(lines.join("<br>"));