201 lines
6.8 KiB
C++
201 lines
6.8 KiB
C++
#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);
|
|
}
|