Skip to content

Commit

Permalink
qml: Add PeerDetails page
Browse files Browse the repository at this point in the history
  • Loading branch information
johnny9 committed Jan 30, 2024
1 parent 67150ef commit 480c03f
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 0 deletions.
4 changes: 4 additions & 0 deletions src/Makefile.qt.include
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ QT_MOC_CPP = \
qml/models/moc_networktraffictower.cpp \
qml/models/moc_nodemodel.cpp \
qml/models/moc_options_model.cpp \
qml/models/moc_peerdetailsmodel.cpp \
qml/models/moc_peerlistsortproxy.cpp \
qml/moc_appmode.cpp \
qt/moc_addressbookpage.cpp \
Expand Down Expand Up @@ -120,6 +121,7 @@ BITCOIN_QT_H = \
qml/models/networktraffictower.h \
qml/models/nodemodel.h \
qml/models/options_model.h \
qml/models/peerdetailsmodel.h \
qml/models/peerlistsortproxy.h \
qml/appmode.h \
qml/bitcoin.h \
Expand Down Expand Up @@ -307,6 +309,7 @@ BITCOIN_QML_BASE_CPP = \
qml/models/networktraffictower.cpp \
qml/models/nodemodel.cpp \
qml/models/options_model.cpp \
qml/models/peerdetailsmodel.cpp \
qml/models/peerlistsortproxy.cpp \
qml/imageprovider.cpp \
qml/util.cpp
Expand Down Expand Up @@ -384,6 +387,7 @@ QML_RES_QML = \
qml/pages/node/NodeRunner.qml \
qml/pages/node/NodeSettings.qml \
qml/pages/node/Peers.qml \
qml/pages/node/PeerDetails.qml \
qml/pages/node/Shutdown.qml \
qml/pages/onboarding/OnboardingBlockclock.qml \
qml/pages/onboarding/OnboardingConnection.qml \
Expand Down
3 changes: 3 additions & 0 deletions src/qml/bitcoin.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <qml/models/networktraffictower.h>
#include <qml/models/nodemodel.h>
#include <qml/models/options_model.h>
#include <qml/models/peerdetailsmodel.h>
#include <qml/models/peerlistsortproxy.h>
#include <qml/imageprovider.h>
#include <qml/util.h>
Expand Down Expand Up @@ -292,6 +293,8 @@ int QmlGuiMain(int argc, char* argv[])
qmlRegisterSingletonInstance<AppMode>("org.bitcoincore.qt", 1, 0, "AppMode", &app_mode);
qmlRegisterType<BlockClockDial>("org.bitcoincore.qt", 1, 0, "BlockClockDial");
qmlRegisterType<LineGraph>("org.bitcoincore.qt", 1, 0, "LineGraph");
qmlRegisterUncreatableType<PeerDetailsModel>("org.bitcoincore.qt", 1, 0, "PeerDetailsModel", "");


engine.load(QUrl(QStringLiteral("qrc:///qml/pages/main.qml")));
if (engine.rootObjects().isEmpty()) {
Expand Down
1 change: 1 addition & 0 deletions src/qml/bitcoin_qml.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@
<file>pages/node/NodeRunner.qml</file>
<file>pages/node/NodeSettings.qml</file>
<file>pages/node/Peers.qml</file>
<file>pages/node/PeerDetails.qml</file>
<file>pages/node/Shutdown.qml</file>
<file>pages/onboarding/OnboardingBlockclock.qml</file>
<file>pages/onboarding/OnboardingConnection.qml</file>
Expand Down
43 changes: 43 additions & 0 deletions src/qml/models/peerdetailsmodel.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// Copyright (c) 2024 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <qml/models/peerdetailsmodel.h>

PeerDetailsModel::PeerDetailsModel(CNodeCombinedStats* nodeStats, const QModelIndex& index, PeerTableModel* parent)
: m_combinedStats{nodeStats}
, m_model{parent}
{
m_row = index.row();
connect(parent, &PeerTableModel::rowsRemoved, this, &PeerDetailsModel::onModelRowsRemoved);
connect(parent, &PeerTableModel::dataChanged, this, &PeerDetailsModel::onModelDataChanged);
}

void PeerDetailsModel::onModelRowsRemoved(const QModelIndex& parent, int first, int last)
{
for (int row = first; row <= last; ++row) {
QModelIndex index = m_model->index(row, 0, parent);
int nodeIdInRow = m_model->data(index, PeerTableModel::NetNodeId).toInt();
if (nodeIdInRow == this->nodeId()) {
Q_EMIT disconnected();
break;
}
}
}

void PeerDetailsModel::onModelDataChanged(const QModelIndex& /* top_left */, const QModelIndex& /* bottom_right */)
{
if (m_model->data(m_model->index(m_row, 0), PeerTableModel::NetNodeId).isNull() ||
m_model->data(m_model->index(m_row, 0), PeerTableModel::NetNodeId).toInt() != nodeId()) {
// This peer has been removed from the model
Q_EMIT disconnected();
return;
}

m_combinedStats = m_model->data(m_model->index(m_row, 0), PeerTableModel::StatsRole).value<CNodeCombinedStats*>();

// Only update when all information is available
if (m_combinedStats && m_combinedStats->fNodeStateStatsAvailable) {
Q_EMIT dataChanged();
}
}
93 changes: 93 additions & 0 deletions src/qml/models/peerdetailsmodel.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
// Copyright (c) 2024 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#ifndef BITCOIN_QML_MODELS_PEERDETAILSMODEL_H
#define BITCOIN_QML_MODELS_PEERDETAILSMODEL_H

#include <QObject>

#include <qml/models/peerlistsortproxy.h>
#include <qt/guiutil.h>
#include <qt/peertablemodel.h>
#include <qt/rpcconsole.h>
#include <util/time.h>

class PeerDetailsModel : public QObject
{
Q_OBJECT
Q_PROPERTY(int nodeId READ nodeId NOTIFY dataChanged)
Q_PROPERTY(QString address READ address NOTIFY dataChanged)
Q_PROPERTY(QString addressLocal READ addressLocal NOTIFY dataChanged)
Q_PROPERTY(QString type READ type NOTIFY dataChanged)
Q_PROPERTY(QString version READ version NOTIFY dataChanged)
Q_PROPERTY(QString userAgent READ userAgent NOTIFY dataChanged)
Q_PROPERTY(QString services READ services NOTIFY dataChanged)
Q_PROPERTY(bool transactionRelay READ transactionRelay NOTIFY dataChanged)
Q_PROPERTY(bool addressRelay READ addressRelay NOTIFY dataChanged)
Q_PROPERTY(QString startingHeight READ startingHeight NOTIFY dataChanged)
Q_PROPERTY(QString syncedHeaders READ syncedHeaders NOTIFY dataChanged)
Q_PROPERTY(QString syncedBlocks READ syncedBlocks NOTIFY dataChanged)
Q_PROPERTY(QString direction READ direction NOTIFY dataChanged)
Q_PROPERTY(QString lastSend READ lastSend NOTIFY dataChanged)
Q_PROPERTY(QString lastReceived READ lastReceived NOTIFY dataChanged)
Q_PROPERTY(QString bytesSent READ bytesSent NOTIFY dataChanged)
Q_PROPERTY(QString bytesReceived READ bytesReceived NOTIFY dataChanged)
Q_PROPERTY(QString pingTime READ pingTime NOTIFY dataChanged)
Q_PROPERTY(QString pingWait READ pingWait NOTIFY dataChanged)
Q_PROPERTY(QString pingMin READ pingMin NOTIFY dataChanged)
Q_PROPERTY(QString timeOffset READ timeOffset NOTIFY dataChanged)
Q_PROPERTY(QString mappedAS READ mappedAS NOTIFY dataChanged)
Q_PROPERTY(QString permission READ permission NOTIFY dataChanged)

public:
explicit PeerDetailsModel(CNodeCombinedStats* nodeStats, const QModelIndex& index, PeerTableModel* model);

int nodeId() const { return m_combinedStats->nodeStats.nodeid; }
QString address() const { return QString::fromStdString(m_combinedStats->nodeStats.m_addr_name); }
QString addressLocal() const { return QString::fromStdString(m_combinedStats->nodeStats.addrLocal); }
QString type() const { return GUIUtil::ConnectionTypeToQString(m_combinedStats->nodeStats.m_conn_type, /*prepend_direction=*/true); }
QString version() const { return QString::number(m_combinedStats->nodeStats.nVersion); }
QString userAgent() const { return QString::fromStdString(m_combinedStats->nodeStats.cleanSubVer); }
QString services() const { return GUIUtil::formatServicesStr(m_combinedStats->nodeStateStats.their_services); }
bool transactionRelay() const { return m_combinedStats->nodeStateStats.m_relay_txs; }
bool addressRelay() const { return m_combinedStats->nodeStateStats.m_addr_relay_enabled; }
QString startingHeight() const { return QString::number(m_combinedStats->nodeStats.m_starting_height); }
QString syncedHeaders() const { return QString::number(m_combinedStats->nodeStateStats.nSyncHeight); }
QString syncedBlocks() const { return QString::number(m_combinedStats->nodeStateStats.nCommonHeight); }
QString direction() const { return QString::fromStdString(m_combinedStats->nodeStats.fInbound ? "Inbound" : "Outbound"); }
QString lastSend() const { return GUIUtil::formatDurationStr(GetTime<std::chrono::seconds>() - m_combinedStats->nodeStats.m_last_send); }
QString lastReceived() const { return GUIUtil::formatDurationStr(GetTime<std::chrono::seconds>() - m_combinedStats->nodeStats.m_last_recv); }
QString bytesSent() const { return GUIUtil::formatBytes(m_combinedStats->nodeStats.nSendBytes); }
QString bytesReceived() const { return GUIUtil::formatBytes(m_combinedStats->nodeStats.nRecvBytes); }
QString pingTime() const { return GUIUtil::formatPingTime(m_combinedStats->nodeStats.m_last_ping_time); }
QString pingMin() const { return GUIUtil::formatPingTime(m_combinedStats->nodeStats.m_min_ping_time); }
QString pingWait() const { return GUIUtil::formatPingTime(m_combinedStats->nodeStateStats.m_ping_wait); }
QString timeOffset() const { return GUIUtil::formatTimeOffset(m_combinedStats->nodeStats.nTimeOffset); }
QString mappedAS() const { return m_combinedStats->nodeStats.m_mapped_as != 0 ? QString::number(m_combinedStats->nodeStats.m_mapped_as) : tr("N/A"); }
QString permission() const {
if (m_combinedStats->nodeStats.m_permission_flags == NetPermissionFlags::None) {
return tr("N/A");
}
QStringList permissions;
for (const auto& permission : NetPermissions::ToStrings(m_combinedStats->nodeStats.m_permission_flags)) {
permissions.append(QString::fromStdString(permission));
}
return permissions.join(" & ");
}

Q_SIGNALS:
void dataChanged();
void disconnected();

private Q_SLOTS:
void onModelRowsRemoved(const QModelIndex& parent, int first, int last);
void onModelDataChanged(const QModelIndex& top_left, const QModelIndex& bottom_right);

private:
int m_row;
CNodeCombinedStats* m_combinedStats;
PeerTableModel* m_model;
};

#endif // BITCOIN_QML_MODELS_PEERDETAILSMODEL_H
6 changes: 6 additions & 0 deletions src/qml/models/peerlistsortproxy.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

#include <qml/models/peerlistsortproxy.h>
#include <qml/models/peerdetailsmodel.h>
#include <qt/peertablemodel.h>

PeerListSortProxy::PeerListSortProxy(QObject* parent)
Expand All @@ -23,6 +24,7 @@ QHash<int, QByteArray> PeerListSortProxy::roleNames() const
roles[PeerTableModel::Sent] = "sent";
roles[PeerTableModel::Received] = "received";
roles[PeerTableModel::Subversion] = "subversion";
roles[PeerTableModel::StatsRole] = "stats";
return roles;
}

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

Expand Down
11 changes: 11 additions & 0 deletions src/qml/pages/node/NodeSettings.qml
Original file line number Diff line number Diff line change
Expand Up @@ -190,6 +190,17 @@ Item {
nodeSettingsView.pop()
peerTableModel.stopAutoRefresh();
}
onPeerSelected: (peerDetails) => {
nodeSettingsView.push(peer_details, {"details": peerDetails})
}
}
}
Component {
id: peer_details
PeerDetails {
onBackClicked: {
nodeSettingsView.pop()
}
}
}
Component {
Expand Down
111 changes: 111 additions & 0 deletions src/qml/pages/node/PeerDetails.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
// Copyright (c) 2024 The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.

import QtQuick 2.15
import QtQuick.Controls 2.15
import QtQuick.Layouts 1.15
import org.bitcoincore.qt 1.0
import "../../controls"
import "../../components"

Page {
signal backClicked()

property PeerDetailsModel details

Connections {
target: details
function onDisconnected() {
root.backClicked()
}
}

id: root
background: null

header: NavigationBar2 {
leftItem: NavButton {
iconSource: "image://images/caret-left"
text: qsTr("Back")
onClicked: root.backClicked()
}
centerItem: Header {
headerBold: true
headerSize: 18
header: qsTr("Peer " + details.nodeId)
}
}

component PeerKeyValueRow: Row {
width: parent.width
property string key: ""
property string value: ""
CoreText {
color: Theme.color.neutral9;
text: key;
width: 125;
horizontalAlignment: Qt.AlignLeft;
}
CoreText {
color: Theme.color.neutral9;
maximumLineCount: 1;
elide: Text.ElideRight;
wrapMode: Text.NoWrap;
text: value
width: parent.width - 125;
horizontalAlignment: Qt.AlignLeft;
}
}

ScrollView {
id: scrollView
width: parent.width
height: parent.height
clip: true
contentWidth: width

Column {
width: Math.min(parent.width - 40, 450)
anchors.horizontalCenter: parent.horizontalCenter
spacing: 20

CoreText { text: "Information"; bold: true; font.pixelSize: 18; horizontalAlignment: Qt.AlignLeft; }
Column {
width: parent.width
PeerKeyValueRow { key: qsTr("IP"); value: details.address }
PeerKeyValueRow { key: qsTr("VIA"); value: details.addressLocal }
PeerKeyValueRow { key: qsTr("Type"); value: details.type }
PeerKeyValueRow { key: qsTr("Permission"); value: details.permission }
PeerKeyValueRow { key: qsTr("Version"); value: details.version }
PeerKeyValueRow { key: qsTr("User agent"); value: details.userAgent }
PeerKeyValueRow { key: qsTr("Services"); value: details.services }
PeerKeyValueRow { key: qsTr("Transport relay"); value: details.transactionRelay }
PeerKeyValueRow { key: qsTr("Address relay"); value: details.addressRelay }
PeerKeyValueRow { key: qsTr("Mapped AS"); value: details.mappedAS }
}

CoreText { text: "Block data"; bold: true; font.pixelSize: 18; horizontalAlignment: Qt.AlignLeft; }
Column {
width: parent.width
PeerKeyValueRow { key: qsTr("Starting block"); value: details.startingHeight }
PeerKeyValueRow { key: qsTr("Synced headers"); value: details.syncedHeaders }
PeerKeyValueRow { key: qsTr("Synced blocks"); value: details.syncedBlocks }
}

CoreText { text: "Network traffic"; bold: true; font.pixelSize: 18; horizontalAlignment: Qt.AlignLeft; }
Column {
width: parent.width
PeerKeyValueRow { key: qsTr("Direction"); value: details.direction }
PeerKeyValueRow { key: qsTr("Last send"); value: details.lastSend }
PeerKeyValueRow { key: qsTr("Last receive"); value: details.lastReceived }
PeerKeyValueRow { key: qsTr("Sent"); value: details.bytesSent }
PeerKeyValueRow { key: qsTr("Received"); value: details.bytesReceived }
PeerKeyValueRow { key: qsTr("Ping time"); value: details.pingTime }
PeerKeyValueRow { key: qsTr("Ping wait"); value: details.pingWait }
PeerKeyValueRow { key: qsTr("Min ping"); value: details.pingMin }
PeerKeyValueRow { key: qsTr("Time offset"); value: details.timeOffset }
}
}
}
}
5 changes: 5 additions & 0 deletions src/qml/pages/node/Peers.qml
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import "../../components"

Page {
signal backClicked
signal peerSelected(PeerDetailsModel peerDetails)

id: root
background: null
Expand Down Expand Up @@ -161,6 +162,7 @@ Page {
required property string direction;
required property string connectionType;
required property string network;
required property PeerDetailsModel stats;
readonly property color stateColor: {
if (delegate.down) {
return Theme.color.orange
Expand Down Expand Up @@ -232,6 +234,9 @@ Page {
width: parent.width
}
}
onClicked: {
root.peerSelected(stats)
}
contentItem: ColumnLayout {
RowLayout {
Layout.fillWidth: true
Expand Down

0 comments on commit 480c03f

Please sign in to comment.