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.
 
 
 
 
 
 

184 lines
7.1 KiB

#include "accountlist.h"
#include "accountdelegate.h"
#include "memberlistmodel.h"
#include "memberpropsdialog.h"
#include "../db/db_manager.h"
#include <QVBoxLayout>
#include <QLabel>
#include <QDateTime>
#include <QFont>
#include "../../../lib/platform_compat.h"
static QString shortenKey(const QByteArray& key) {
if (key.size() < 8) return key.toHex(' ').toUpper();
return key.left(4).toHex(' ').toUpper() + ".." + key.right(4).toHex(' ').toUpper();
}
static QString addrStr(const void* addr, int family) {
if (family == 4) {
const uint8_t* a = (const uint8_t*)addr;
return QString("%1.%2.%3.%4").arg(a[0]).arg(a[1]).arg(a[2]).arg(a[3]);
}
if (family == 6) {
const uint16_t* a = (const uint16_t*)addr;
return QString("%1:%2:%3:%4:%5:%6:%7:%8")
.arg(ntohs(a[0]), 0, 16).arg(ntohs(a[1]), 0, 16)
.arg(ntohs(a[2]), 0, 16).arg(ntohs(a[3]), 0, 16)
.arg(ntohs(a[4]), 0, 16).arg(ntohs(a[5]), 0, 16)
.arg(ntohs(a[6]), 0, 16).arg(ntohs(a[7]), 0, 16);
}
return "?";
}
static QString protoStr(int protocol) {
switch (protocol) { case 1: return "UDP"; case 2: return "TCP"; case 3: return "UDP+TCP"; default: return QString::number(protocol); }
}
static QString addrTypeStr(int t) {
switch (t) { case 0: return "NETIF"; case 1: return "DIRECT"; case 2: return "NAT_EIM"; case 3: return "NAT_STRICT"; default: return QString("?%1").arg(t); }
}
AccountList::AccountList(DbManager* db, QWidget *parent)
: QWidget(parent)
, m_listView(new QListView(this))
, m_model(new MemberListModel(db, this))
, m_db(db)
, m_detailText(nullptr)
{
auto *layout = new QVBoxLayout(this);
layout->setContentsMargins(0, 0, 0, 0);
layout->setSpacing(0);
auto *header = new QLabel("Members", this);
header->setAlignment(Qt::AlignCenter);
header->setStyleSheet("font-weight: bold; padding: 8px; background: palette(window);");
layout->addWidget(header);
m_listView->setModel(m_model);
m_listView->setItemDelegate(new AccountDelegate(m_listView));
m_listView->setEditTriggers(QAbstractItemView::NoEditTriggers);
m_listView->setSelectionMode(QAbstractItemView::SingleSelection);
m_listView->setIconSize(QSize(24, 24));
m_listView->setHorizontalScrollBarPolicy(Qt::ScrollBarAlwaysOff);
m_listView->setVerticalScrollMode(QAbstractItemView::ScrollPerPixel);
m_listView->setContextMenuPolicy(Qt::CustomContextMenu);
layout->addWidget(m_listView, 3);
m_detailText = new QPlainTextEdit(this);
m_detailText->setReadOnly(true);
m_detailText->setFont(QFont("monospace", 9));
m_detailText->setStyleSheet(
"QPlainTextEdit { 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");
layout->addWidget(m_detailText, 2);
connect(m_listView, &QListView::clicked, this, &AccountList::onMemberClicked);
m_refreshTimer = new QTimer(this);
m_refreshTimer->start(2000);
connect(m_refreshTimer, &QTimer::timeout, m_model, &MemberListModel::refreshRtt);
connect(m_listView, &QWidget::customContextMenuRequested,
this, &AccountList::onMemberContextMenu);
}
void AccountList::setChannel(const QString& channelId) {
m_channelId = channelId;
m_model->setChannel(channelId);
m_detailText->clear();
}
void AccountList::refresh() { m_model->refresh(); }
void AccountList::onMemberClicked(const QModelIndex& index) {
if (!index.isValid()) return;
quint64 nodeId = m_model->nodeIdAt(index.row());
if (nodeId) showMemberDetail(nodeId);
}
void AccountList::showMemberDetail(quint64 nodeId) {
if (!m_db || !m_db->m_db) return;
sqlite3* d = m_db->m_db;
QString text;
sqlite3_stmt* st = nullptr;
if (sqlite3_prepare_v2(d,
"SELECT node_id, name, online, x25519_pubkey, ed25519_pubkey,"
" last_seen_at, created_at FROM nodes WHERE node_id=?",
-1, &st, nullptr) != SQLITE_OK) { m_detailText->clear(); return; }
sqlite3_bind_int64(st, 1, (sqlite3_int64)nodeId);
if (sqlite3_step(st) == SQLITE_ROW) {
QString name = DbManager::colText(st, 1);
int online = sqlite3_column_int(st, 2);
QByteArray x25 = DbManager::colBlob(st, 3);
QByteArray ed = DbManager::colBlob(st, 4);
qint64 last_seen = sqlite3_column_int64(st, 5);
qint64 created = sqlite3_column_int64(st, 6);
text += QString("Node: 0x%1").arg(nodeId, 16, 16, QChar('0'));
if (!name.isEmpty()) text += QString(" (%1)").arg(name);
text += QString("\n online: %1\n").arg(online ? "yes" : "no");
text += QString(" x25519: %1\n").arg(x25.isEmpty() ? "-" : shortenKey(x25));
text += QString(" ed25519: %1\n").arg(ed.isEmpty() ? "-" : shortenKey(ed));
if (last_seen > 0)
text += QString(" last_seen: %1\n").arg(QDateTime::fromSecsSinceEpoch(last_seen).toString("yyyy-MM-dd hh:mm:ss"));
if (created > 0)
text += QString(" created: %1\n").arg(QDateTime::fromSecsSinceEpoch(created).toString("yyyy-MM-dd hh:mm:ss"));
} else {
text = QString("Node 0x%1 not found in database.").arg(nodeId, 16, 16, QChar('0'));
}
sqlite3_finalize(st);
sqlite3_stmt* na = nullptr;
sqlite3_prepare_v2(d,
"SELECT family, protocol, address, port, rtt, addr_type"
" FROM node_addresses WHERE node_id=? ORDER BY family, port",
-1, &na, nullptr);
if (na) {
sqlite3_bind_int64(na, 1, (sqlite3_int64)nodeId);
int ac = 0;
while (sqlite3_step(na) == SQLITE_ROW) {
if (ac == 0) text += " addresses:\n";
int family = sqlite3_column_int(na, 0);
int proto = sqlite3_column_int(na, 1);
const void* addr = sqlite3_column_blob(na, 2);
int addrSz = sqlite3_column_bytes(na, 2);
int port = sqlite3_column_int(na, 3);
int rtt = sqlite3_column_int(na, 4);
int addrType = sqlite3_column_int(na, 5);
if (addr && addrSz > 0)
text += QString(" %1:%2 %3 %4 rtt=%5ms\n")
.arg(addrStr(addr, family))
.arg(port)
.arg(protoStr(proto))
.arg(addrTypeStr(addrType))
.arg(rtt > 0 ? QString::number(rtt) : "-");
ac++;
}
if (ac == 0) text += " addresses: none\n";
sqlite3_finalize(na);
}
m_detailText->setPlainText(text);
}
void AccountList::onMemberContextMenu(const QPoint& pos) {
QModelIndex index = m_listView->indexAt(pos);
if (!index.isValid()) return;
quint64 nodeId = m_model->nodeIdAt(index.row());
if (!nodeId) return;
QString name = m_model->data(index, Qt::DisplayRole).toString();
QMenu menu(m_listView);
QAction* editAction = menu.addAction("Edit");
QAction* chosen = menu.exec(m_listView->viewport()->mapToGlobal(pos));
if (chosen != editAction) return;
MemberPropsDialog dlg(m_db, m_channelId, nodeId, name, this);
dlg.exec();
}