You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

200 lines
6.8 KiB

#include <QCoreApplication>
// connmonitorwindow.cpp — ETCP connection monitor (standalone, non-modal window)
#include "connmonitorwindow.h"
#include "../transport/gui_bridge.h"
extern "C" {
#include "chat/chat_core.h"
#include "../../lib/mem.h"
}
#include <QVBoxLayout>
#include <QSplitter>
#include <QLabel>
#include <QFont>
#include <QScrollBar>
#include <QHash>
#include <QApplication>
#include <cstring>
#define CONN_LIST_ENTRY_SIZE 46
static ConnMonitorWindow* g_activeConnMonitor = nullptr;
static void connListCb(const uint8_t* data, int len) {
if (g_activeConnMonitor) g_activeConnMonitor->onConnList(data, len);
}
static void connMetricsCb(const uint8_t* data, int len) {
if (g_activeConnMonitor) g_activeConnMonitor->onConnMetrics(data, len);
}
ConnMonitorWindow::ConnMonitorWindow(QWidget* parent)
: QDialog(parent)
{
setWindowTitle(QCoreApplication::translate("ChatGui", "Connection Monitor"));
resize(900, 550);
setAttribute(Qt::WA_DeleteOnClose);
setWindowFlags(windowFlags() & ~Qt::WindowContextHelpButtonHint);
auto* mainLayout = new QVBoxLayout(this);
mainLayout->setSpacing(4);
mainLayout->setContentsMargins(8, 8, 8, 8);
auto* title = new QLabel(QCoreApplication::translate("ChatGui", "Active ETCP Connections (10 fps)"), this);
title->setStyleSheet("font-weight: bold; font-size: 13px;");
mainLayout->addWidget(title);
auto* splitter = new QSplitter(Qt::Horizontal, this);
splitter->setStyleSheet(
"QSplitter::handle { background: palette(mid); width: 2px; }");
m_connList = new QListWidget(splitter);
m_connList->setFixedWidth(280);
m_connList->setFont(QFont("monospace", 9));
m_connList->setStyleSheet(
"QListWidget { border: 1px solid palette(mid); border-radius: 4px;"
" background: palette(base); }"
"QListWidget::item { padding: 4px 6px; }"
"QListWidget::item:selected { background: #3390EC; color: white; }");
m_details = new QPlainTextEdit(splitter);
m_details->setReadOnly(true);
m_details->setFont(QFont("monospace", 9));
m_details->setStyleSheet(
"QPlainTextEdit { border: 1px solid palette(mid); border-radius: 4px;"
" padding: 8px; background: palette(base); }");
m_details->setPlaceholderText(QCoreApplication::translate("ChatGui", "Select a connection to view metrics"));
splitter->addWidget(m_connList);
splitter->addWidget(m_details);
splitter->setStretchFactor(0, 0);
splitter->setStretchFactor(1, 1);
mainLayout->addWidget(splitter, 1);
m_timer = new QTimer(this);
m_timer->setInterval(100);
connect(m_timer, &QTimer::timeout, this, &ConnMonitorWindow::onTimerTick);
connect(m_connList, &QListWidget::currentItemChanged,
this, &ConnMonitorWindow::onConnSelected);
g_activeConnMonitor = this;
gui_bridge_set_conn_list_cb(connListCb);
gui_bridge_set_conn_metrics_cb(connMetricsCb);
m_timer->start();
}
ConnMonitorWindow::~ConnMonitorWindow() {
m_timer->stop();
if (g_activeConnMonitor == this) g_activeConnMonitor = nullptr;
}
void ConnMonitorWindow::showEvent(QShowEvent* event) {
QDialog::showEvent(event);
m_timer->start();
}
void ConnMonitorWindow::hideEvent(QHideEvent* event) {
m_timer->stop();
QDialog::hideEvent(event);
}
void ConnMonitorWindow::onTimerTick() {
if (m_details->textCursor().hasSelection()) return;
if (QApplication::mouseButtons()) return;
gui_bridge_post_uasync_fn(chat_core_collect_conn_list_trampoline, gui_bridge_get_inst());
if (m_selectedNodeId) requestMetricsForSelected();
}
void ConnMonitorWindow::onConnSelected(QListWidgetItem* current, QListWidgetItem* previous) {
(void)previous;
if (!current) { m_selectedNodeId = 0; m_details->clear(); return; }
m_selectedNodeId = (uint64_t)current->data(Qt::UserRole).toULongLong();
requestMetricsForSelected();
}
void ConnMonitorWindow::requestMetricsForSelected() {
struct chat_node_arg* arg = (struct chat_node_arg*)u_malloc(sizeof(struct chat_node_arg));
if (!arg) return;
arg->inst = gui_bridge_get_inst();
arg->node_id = m_selectedNodeId;
gui_bridge_post_uasync_fn(chat_core_collect_conn_metrics_trampoline, arg);
}
void ConnMonitorWindow::onConnList(const uint8_t* data, int len) {
if (len < 2) return;
uint16_t count; memcpy(&count, data, 2);
const uint8_t* p = data + 2;
QHash<uint64_t, QListWidgetItem*> existing;
for (int i = 0; i < m_connList->count(); i++) {
auto* item = m_connList->item(i);
existing[(uint64_t)item->data(Qt::UserRole).toULongLong()] = item;
}
m_connList->blockSignals(true);
for (uint16_t i = 0; i < count && (p + CONN_LIST_ENTRY_SIZE) <= (data + len); i++) {
uint64_t nodeId; memcpy(&nodeId, p, 8); p += 8;
uint8_t flags = *p++;
uint8_t linkCount = *p++;
uint16_t rtt; memcpy(&rtt, p, 2); p += 2;
p += 2; /* inflightKb */
char name[33]; memcpy(name, p, 32); name[32] = '\0'; p += 32;
if (!nodeId) continue;
bool linksUp = (flags & 1) != 0;
bool initialized = (flags & 2) != 0;
bool proxyClient = (flags & 4) != 0;
bool proxyServer = (flags & 8) != 0;
QString color = linksUp ? "#2E7D32" : "#C62828";
QString status = linksUp ? "UP" : (initialized ? "init" : "DOWN");
QString proxyRole;
if (proxyClient && proxyServer) proxyRole = "CS";
else if (proxyClient) proxyRole = " C";
else if (proxyServer) proxyRole = " S";
QString nodeHex = QString("%1").arg(nodeId, 16, 16, QChar('0')).right(16).left(4);
QString rttStr = rtt > 0 ? QString::number(rtt / 10.0f, 'f', 1) : "-";
QString text = QString("[%1] %2 %3%4 %5L rtt=%6ms")
.arg(nodeHex)
.arg(QString::fromUtf8(name).left(16), -16)
.arg(status, -4)
.arg(proxyRole, -2)
.arg(linkCount)
.arg(rttStr);
auto it = existing.find(nodeId);
if (it != existing.end()) {
auto* item = *it;
item->setText(text);
item->setForeground(QColor(color));
existing.erase(it);
} else {
auto* item = new QListWidgetItem(text, m_connList);
item->setData(Qt::UserRole, QVariant((qulonglong)nodeId));
item->setForeground(QColor(color));
}
}
for (auto it = existing.begin(); it != existing.end(); ++it)
delete *it;
m_connList->blockSignals(false);
if (!m_selectedNodeId && m_connList->count() > 0) {
m_connList->setCurrentRow(0);
onConnSelected(m_connList->item(0), nullptr);
}
}
void ConnMonitorWindow::onConnMetrics(const uint8_t* data, int len) {
if (len <= 0) return;
int scrollVal = m_details->verticalScrollBar()->value();
m_details->setPlainText(QString::fromUtf8((const char*)data, len));
m_details->verticalScrollBar()->setValue(scrollVal);
}