Skip to content

Commit b4f8fc1

Browse files
committed
qml: Add PeerDetails page
1 parent 67150ef commit b4f8fc1

File tree

9 files changed

+310
-0
lines changed

9 files changed

+310
-0
lines changed

src/Makefile.qt.include

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ QT_MOC_CPP = \
4141
qml/models/moc_networktraffictower.cpp \
4242
qml/models/moc_nodemodel.cpp \
4343
qml/models/moc_options_model.cpp \
44+
qml/models/moc_peerdetailsmodel.cpp \
4445
qml/models/moc_peerlistsortproxy.cpp \
4546
qml/moc_appmode.cpp \
4647
qt/moc_addressbookpage.cpp \
@@ -120,6 +121,7 @@ BITCOIN_QT_H = \
120121
qml/models/networktraffictower.h \
121122
qml/models/nodemodel.h \
122123
qml/models/options_model.h \
124+
qml/models/peerdetailsmodel.h \
123125
qml/models/peerlistsortproxy.h \
124126
qml/appmode.h \
125127
qml/bitcoin.h \
@@ -307,6 +309,7 @@ BITCOIN_QML_BASE_CPP = \
307309
qml/models/networktraffictower.cpp \
308310
qml/models/nodemodel.cpp \
309311
qml/models/options_model.cpp \
312+
qml/models/peerdetailsmodel.cpp \
310313
qml/models/peerlistsortproxy.cpp \
311314
qml/imageprovider.cpp \
312315
qml/util.cpp
@@ -384,6 +387,7 @@ QML_RES_QML = \
384387
qml/pages/node/NodeRunner.qml \
385388
qml/pages/node/NodeSettings.qml \
386389
qml/pages/node/Peers.qml \
390+
qml/pages/node/PeerDetails.qml \
387391
qml/pages/node/Shutdown.qml \
388392
qml/pages/onboarding/OnboardingBlockclock.qml \
389393
qml/pages/onboarding/OnboardingConnection.qml \

src/qml/bitcoin.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
#include <qml/models/networktraffictower.h>
2525
#include <qml/models/nodemodel.h>
2626
#include <qml/models/options_model.h>
27+
#include <qml/models/peerdetailsmodel.h>
2728
#include <qml/models/peerlistsortproxy.h>
2829
#include <qml/imageprovider.h>
2930
#include <qml/util.h>
@@ -292,6 +293,8 @@ int QmlGuiMain(int argc, char* argv[])
292293
qmlRegisterSingletonInstance<AppMode>("org.bitcoincore.qt", 1, 0, "AppMode", &app_mode);
293294
qmlRegisterType<BlockClockDial>("org.bitcoincore.qt", 1, 0, "BlockClockDial");
294295
qmlRegisterType<LineGraph>("org.bitcoincore.qt", 1, 0, "LineGraph");
296+
qmlRegisterUncreatableType<PeerDetailsModel>("org.bitcoincore.qt", 1, 0, "PeerDetailsModel", "");
297+
295298

296299
engine.load(QUrl(QStringLiteral("qrc:///qml/pages/main.qml")));
297300
if (engine.rootObjects().isEmpty()) {

src/qml/bitcoin_qml.qrc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@
4545
<file>pages/node/NodeRunner.qml</file>
4646
<file>pages/node/NodeSettings.qml</file>
4747
<file>pages/node/Peers.qml</file>
48+
<file>pages/node/PeerDetails.qml</file>
4849
<file>pages/node/Shutdown.qml</file>
4950
<file>pages/onboarding/OnboardingBlockclock.qml</file>
5051
<file>pages/onboarding/OnboardingConnection.qml</file>

src/qml/models/peerdetailsmodel.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// Copyright (c) 2024 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#include <qml/models/peerdetailsmodel.h>
6+
7+
PeerDetailsModel::PeerDetailsModel(CNodeCombinedStats* nodeStats, PeerTableModel* parent)
8+
: m_combinedStats{nodeStats}
9+
, m_model{parent}
10+
, m_disconnected{false}
11+
{
12+
for (int row = 0; row < m_model->rowCount(); ++row) {
13+
QModelIndex index = m_model->index(row, 0);
14+
int nodeIdInRow = m_model->data(index, PeerTableModel::NetNodeId).toInt();
15+
if (nodeIdInRow == m_combinedStats->nodeStats.nodeid) {
16+
m_row = row;
17+
break;
18+
}
19+
}
20+
connect(parent, &PeerTableModel::rowsRemoved, this, &PeerDetailsModel::onModelRowsRemoved);
21+
connect(parent, &PeerTableModel::dataChanged, this, &PeerDetailsModel::onModelDataChanged);
22+
}
23+
24+
void PeerDetailsModel::onModelRowsRemoved(const QModelIndex& parent, int first, int last)
25+
{
26+
for (int row = first; row <= last; ++row) {
27+
QModelIndex index = m_model->index(row, 0, parent);
28+
int nodeIdInRow = m_model->data(index, PeerTableModel::NetNodeId).toInt();
29+
if (nodeIdInRow == this->nodeId()) {
30+
if (!m_disconnected) {
31+
m_disconnected = true;
32+
Q_EMIT disconnected();
33+
}
34+
break;
35+
}
36+
}
37+
}
38+
39+
void PeerDetailsModel::onModelDataChanged(const QModelIndex& /* top_left */, const QModelIndex& /* bottom_right */)
40+
{
41+
if (m_model->data(m_model->index(m_row, 0), PeerTableModel::NetNodeId).isNull() ||
42+
m_model->data(m_model->index(m_row, 0), PeerTableModel::NetNodeId).toInt() != nodeId()) {
43+
if (!m_disconnected) {
44+
m_disconnected = true;
45+
Q_EMIT disconnected();
46+
}
47+
return;
48+
}
49+
50+
m_combinedStats = m_model->data(m_model->index(m_row, 0), PeerTableModel::StatsRole).value<CNodeCombinedStats*>();
51+
52+
// Only update when all information is available
53+
if (m_combinedStats && m_combinedStats->fNodeStateStatsAvailable) {
54+
Q_EMIT dataChanged();
55+
}
56+
}

src/qml/models/peerdetailsmodel.h

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
// Copyright (c) 2024 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
#ifndef BITCOIN_QML_MODELS_PEERDETAILSMODEL_H
6+
#define BITCOIN_QML_MODELS_PEERDETAILSMODEL_H
7+
8+
#include <QObject>
9+
10+
#include <qt/guiutil.h>
11+
#include <qt/peertablemodel.h>
12+
#include <qt/rpcconsole.h>
13+
#include <util/time.h>
14+
15+
class PeerDetailsModel : public QObject
16+
{
17+
Q_OBJECT
18+
Q_PROPERTY(int nodeId READ nodeId NOTIFY dataChanged)
19+
Q_PROPERTY(QString address READ address NOTIFY dataChanged)
20+
Q_PROPERTY(QString addressLocal READ addressLocal NOTIFY dataChanged)
21+
Q_PROPERTY(QString type READ type NOTIFY dataChanged)
22+
Q_PROPERTY(QString version READ version NOTIFY dataChanged)
23+
Q_PROPERTY(QString userAgent READ userAgent NOTIFY dataChanged)
24+
Q_PROPERTY(QString services READ services NOTIFY dataChanged)
25+
Q_PROPERTY(bool transactionRelay READ transactionRelay NOTIFY dataChanged)
26+
Q_PROPERTY(bool addressRelay READ addressRelay NOTIFY dataChanged)
27+
Q_PROPERTY(QString startingHeight READ startingHeight NOTIFY dataChanged)
28+
Q_PROPERTY(QString syncedHeaders READ syncedHeaders NOTIFY dataChanged)
29+
Q_PROPERTY(QString syncedBlocks READ syncedBlocks NOTIFY dataChanged)
30+
Q_PROPERTY(QString direction READ direction NOTIFY dataChanged)
31+
Q_PROPERTY(QString lastSend READ lastSend NOTIFY dataChanged)
32+
Q_PROPERTY(QString lastReceived READ lastReceived NOTIFY dataChanged)
33+
Q_PROPERTY(QString bytesSent READ bytesSent NOTIFY dataChanged)
34+
Q_PROPERTY(QString bytesReceived READ bytesReceived NOTIFY dataChanged)
35+
Q_PROPERTY(QString pingTime READ pingTime NOTIFY dataChanged)
36+
Q_PROPERTY(QString pingWait READ pingWait NOTIFY dataChanged)
37+
Q_PROPERTY(QString pingMin READ pingMin NOTIFY dataChanged)
38+
Q_PROPERTY(QString timeOffset READ timeOffset NOTIFY dataChanged)
39+
Q_PROPERTY(QString mappedAS READ mappedAS NOTIFY dataChanged)
40+
Q_PROPERTY(QString permission READ permission NOTIFY dataChanged)
41+
42+
public:
43+
explicit PeerDetailsModel(CNodeCombinedStats* nodeStats, PeerTableModel* model);
44+
45+
int nodeId() const { return m_combinedStats->nodeStats.nodeid; }
46+
QString address() const { return QString::fromStdString(m_combinedStats->nodeStats.m_addr_name); }
47+
QString addressLocal() const { return QString::fromStdString(m_combinedStats->nodeStats.addrLocal); }
48+
QString type() const { return GUIUtil::ConnectionTypeToQString(m_combinedStats->nodeStats.m_conn_type, /*prepend_direction=*/true); }
49+
QString version() const { return QString::number(m_combinedStats->nodeStats.nVersion); }
50+
QString userAgent() const { return QString::fromStdString(m_combinedStats->nodeStats.cleanSubVer); }
51+
QString services() const { return GUIUtil::formatServicesStr(m_combinedStats->nodeStateStats.their_services); }
52+
bool transactionRelay() const { return m_combinedStats->nodeStateStats.m_relay_txs; }
53+
bool addressRelay() const { return m_combinedStats->nodeStateStats.m_addr_relay_enabled; }
54+
QString startingHeight() const { return QString::number(m_combinedStats->nodeStateStats.m_starting_height); }
55+
QString syncedHeaders() const { return QString::number(m_combinedStats->nodeStateStats.nSyncHeight); }
56+
QString syncedBlocks() const { return QString::number(m_combinedStats->nodeStateStats.nCommonHeight); }
57+
QString direction() const { return QString::fromStdString(m_combinedStats->nodeStats.fInbound ? "Inbound" : "Outbound"); }
58+
QString lastSend() const { return GUIUtil::formatDurationStr(GetTime<std::chrono::seconds>() - m_combinedStats->nodeStats.m_last_send); }
59+
QString lastReceived() const { return GUIUtil::formatDurationStr(GetTime<std::chrono::seconds>() - m_combinedStats->nodeStats.m_last_recv); }
60+
QString bytesSent() const { return GUIUtil::formatBytes(m_combinedStats->nodeStats.nSendBytes); }
61+
QString bytesReceived() const { return GUIUtil::formatBytes(m_combinedStats->nodeStats.nRecvBytes); }
62+
QString pingTime() const { return GUIUtil::formatPingTime(m_combinedStats->nodeStats.m_last_ping_time); }
63+
QString pingMin() const { return GUIUtil::formatPingTime(m_combinedStats->nodeStats.m_min_ping_time); }
64+
QString pingWait() const { return GUIUtil::formatPingTime(m_combinedStats->nodeStateStats.m_ping_wait); }
65+
QString timeOffset() const { return GUIUtil::formatTimeOffset(m_combinedStats->nodeStats.nTimeOffset); }
66+
QString mappedAS() const { return m_combinedStats->nodeStats.m_mapped_as != 0 ? QString::number(m_combinedStats->nodeStats.m_mapped_as) : tr("N/A"); }
67+
QString permission() const {
68+
if (m_combinedStats->nodeStats.m_permission_flags == NetPermissionFlags::None) {
69+
return tr("N/A");
70+
}
71+
QStringList permissions;
72+
for (const auto& permission : NetPermissions::ToStrings(m_combinedStats->nodeStats.m_permission_flags)) {
73+
permissions.append(QString::fromStdString(permission));
74+
}
75+
return permissions.join(" & ");
76+
}
77+
78+
Q_SIGNALS:
79+
void dataChanged();
80+
void disconnected();
81+
82+
private Q_SLOTS:
83+
void onModelRowsRemoved(const QModelIndex& parent, int first, int last);
84+
void onModelDataChanged(const QModelIndex& top_left, const QModelIndex& bottom_right);
85+
86+
private:
87+
int m_row;
88+
CNodeCombinedStats* m_combinedStats;
89+
PeerTableModel* m_model;
90+
bool m_disconnected;
91+
};
92+
93+
#endif // BITCOIN_QML_MODELS_PEERDETAILSMODEL_H

src/qml/models/peerlistsortproxy.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
44

55
#include <qml/models/peerlistsortproxy.h>
6+
#include <qml/models/peerdetailsmodel.h>
67
#include <qt/peertablemodel.h>
78

89
PeerListSortProxy::PeerListSortProxy(QObject* parent)
@@ -23,6 +24,7 @@ QHash<int, QByteArray> PeerListSortProxy::roleNames() const
2324
roles[PeerTableModel::Sent] = "sent";
2425
roles[PeerTableModel::Received] = "received";
2526
roles[PeerTableModel::Subversion] = "subversion";
27+
roles[PeerTableModel::StatsRole] = "stats";
2628
return roles;
2729
}
2830

@@ -40,6 +42,10 @@ int PeerListSortProxy::RoleNameToIndex(const QString & name) const
4042
QVariant PeerListSortProxy::data(const QModelIndex& index, int role) const
4143
{
4244
if (role == PeerTableModel::StatsRole) {
45+
auto stats = PeerTableSortProxy::data(index, role);
46+
auto details = new PeerDetailsModel(stats.value<CNodeCombinedStats*>(), qobject_cast<PeerTableModel*>(sourceModel()));
47+
return QVariant::fromValue(details);
48+
} else if (role == PeerTableModel::NetNodeId) {
4349
return PeerTableSortProxy::data(index, role);
4450
}
4551

src/qml/pages/node/NodeSettings.qml

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,17 @@ Item {
190190
nodeSettingsView.pop()
191191
peerTableModel.stopAutoRefresh();
192192
}
193+
onPeerSelected: (peerDetails) => {
194+
nodeSettingsView.push(peer_details, {"details": peerDetails})
195+
}
196+
}
197+
}
198+
Component {
199+
id: peer_details
200+
PeerDetails {
201+
onBackClicked: {
202+
nodeSettingsView.pop()
203+
}
193204
}
194205
}
195206
Component {

src/qml/pages/node/PeerDetails.qml

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
// Copyright (c) 2024 The Bitcoin Core developers
2+
// Distributed under the MIT software license, see the accompanying
3+
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
4+
5+
import QtQuick 2.15
6+
import QtQuick.Controls 2.15
7+
import QtQuick.Layouts 1.15
8+
import org.bitcoincore.qt 1.0
9+
import "../../controls"
10+
import "../../components"
11+
12+
Page {
13+
signal backClicked()
14+
15+
property PeerDetailsModel details
16+
17+
Connections {
18+
target: details
19+
function onDisconnected() {
20+
root.backClicked()
21+
}
22+
}
23+
24+
id: root
25+
background: null
26+
27+
header: NavigationBar2 {
28+
leftItem: NavButton {
29+
iconSource: "image://images/caret-left"
30+
text: qsTr("Back")
31+
onClicked: root.backClicked()
32+
}
33+
centerItem: Header {
34+
headerBold: true
35+
headerSize: 18
36+
header: qsTr("Peer " + details.nodeId)
37+
}
38+
}
39+
40+
component PeerKeyValueRow: Row {
41+
width: parent.width
42+
property string key: ""
43+
property string value: ""
44+
CoreText {
45+
color: Theme.color.neutral9;
46+
text: key;
47+
width: 125;
48+
horizontalAlignment: Qt.AlignLeft;
49+
}
50+
CoreText {
51+
color: Theme.color.neutral9;
52+
elide: Text.ElideRight;
53+
wrapMode: Text.WordWrap;
54+
text: value
55+
width: parent.width - 125;
56+
horizontalAlignment: Qt.AlignLeft;
57+
}
58+
}
59+
60+
ScrollView {
61+
id: scrollView
62+
width: parent.width
63+
height: parent.height
64+
clip: true
65+
contentWidth: width
66+
67+
Column {
68+
width: Math.min(parent.width - 40, 450)
69+
anchors.horizontalCenter: parent.horizontalCenter
70+
spacing: 10
71+
72+
CoreText {
73+
text: "Information";
74+
bold: true;
75+
font.pixelSize: 18;
76+
horizontalAlignment: Qt.AlignLeft;
77+
color: Theme.color.neutral9;
78+
}
79+
Column {
80+
width: parent.width
81+
spacing: 5
82+
PeerKeyValueRow { key: qsTr("Address"); value: details.address }
83+
PeerKeyValueRow { key: qsTr("VIA"); value: details.addressLocal }
84+
PeerKeyValueRow { key: qsTr("Type"); value: details.type }
85+
PeerKeyValueRow { key: qsTr("Permission"); value: details.permission }
86+
PeerKeyValueRow { key: qsTr("Version"); value: details.version }
87+
PeerKeyValueRow { key: qsTr("User agent"); value: details.userAgent }
88+
PeerKeyValueRow { key: qsTr("Services"); value: details.services }
89+
PeerKeyValueRow { key: qsTr("Transaction relay"); value: details.transactionRelay }
90+
PeerKeyValueRow { key: qsTr("Address relay"); value: details.addressRelay }
91+
PeerKeyValueRow { key: qsTr("Mapped AS"); value: details.mappedAS }
92+
}
93+
94+
CoreText {
95+
text: "Block data";
96+
bold: true;
97+
font.pixelSize: 18;
98+
horizontalAlignment: Qt.AlignLeft;
99+
color: Theme.color.neutral9;
100+
}
101+
Column {
102+
width: parent.width
103+
spacing: 5
104+
PeerKeyValueRow { key: qsTr("Starting block"); value: details.startingHeight }
105+
PeerKeyValueRow { key: qsTr("Synced headers"); value: details.syncedHeaders }
106+
PeerKeyValueRow { key: qsTr("Synced blocks"); value: details.syncedBlocks }
107+
}
108+
109+
CoreText {
110+
text: "Network traffic";
111+
bold: true;
112+
font.pixelSize: 18;
113+
horizontalAlignment: Qt.AlignLeft;
114+
color: Theme.color.neutral9;
115+
}
116+
Column {
117+
width: parent.width
118+
spacing: 5
119+
PeerKeyValueRow { key: qsTr("Direction"); value: details.direction }
120+
PeerKeyValueRow { key: qsTr("Last send"); value: details.lastSend }
121+
PeerKeyValueRow { key: qsTr("Last receive"); value: details.lastReceived }
122+
PeerKeyValueRow { key: qsTr("Sent"); value: details.bytesSent }
123+
PeerKeyValueRow { key: qsTr("Received"); value: details.bytesReceived }
124+
PeerKeyValueRow { key: qsTr("Ping time"); value: details.pingTime }
125+
PeerKeyValueRow { key: qsTr("Ping wait"); value: details.pingWait }
126+
PeerKeyValueRow { key: qsTr("Min ping"); value: details.pingMin }
127+
PeerKeyValueRow { key: qsTr("Time offset"); value: details.timeOffset }
128+
}
129+
}
130+
}
131+
}

0 commit comments

Comments
 (0)