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
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); |
|
}
|
|
|