mirror of
https://github.com/logos-blockchain/logos-blockchain-ui.git
synced 2026-07-08 08:49:35 +00:00
Merge e6b5d5affdc762d89676f35bcca383cec5edbc48 into 920d9b282140ed4be4472c018ed770b11daa9566
This commit is contained in:
commit
c0d5bc3d33
@ -112,6 +112,7 @@ void BlockModel::appendRaw(const QString& timestamp, const QString& rawJson)
|
||||
|
||||
const QJsonObject header = block.value(QStringLiteral("header")).toObject();
|
||||
e.version = header.value(QStringLiteral("version")).toString();
|
||||
e.blockId = header.value(QStringLiteral("id")).toString();
|
||||
e.parentBlock = header.value(QStringLiteral("parent_block")).toString();
|
||||
|
||||
const QJsonValue slotV = header.value(QStringLiteral("slot"));
|
||||
@ -156,6 +157,41 @@ void BlockModel::appendRaw(const QString& timestamp, const QString& rawJson)
|
||||
emit countChanged();
|
||||
}
|
||||
|
||||
QVariantMap BlockModel::findTransaction(const QString& txId) const
|
||||
{
|
||||
// Normalise for comparison: trim, drop an optional 0x prefix, lowercase.
|
||||
auto normalize = [](const QString& s) {
|
||||
QString t = s.trimmed();
|
||||
if (t.startsWith(QStringLiteral("0x"), Qt::CaseInsensitive))
|
||||
t = t.mid(2);
|
||||
return t.toLower();
|
||||
};
|
||||
|
||||
const QString needle = normalize(txId);
|
||||
if (needle.isEmpty())
|
||||
return QVariantMap{{"found", false}};
|
||||
|
||||
for (const Entry& e : m_entries) {
|
||||
for (const QString& txJson : e.transactions) {
|
||||
QJsonParseError err{};
|
||||
const QJsonDocument doc = QJsonDocument::fromJson(txJson.toUtf8(), &err);
|
||||
if (err.error != QJsonParseError::NoError || !doc.isObject())
|
||||
continue;
|
||||
const QString id = doc.object().value(QStringLiteral("id")).toString();
|
||||
if (!id.isEmpty() && normalize(id) == needle) {
|
||||
return QVariantMap{
|
||||
{"found", true},
|
||||
{"value", txJson},
|
||||
{"blockId", e.blockId},
|
||||
{"slot", e.slot},
|
||||
{"timestamp", e.timestamp},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
return QVariantMap{{"found", false}};
|
||||
}
|
||||
|
||||
void BlockModel::clear()
|
||||
{
|
||||
if (m_entries.isEmpty())
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <QAbstractListModel>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QVariantMap>
|
||||
#include <QVector>
|
||||
|
||||
// Structured model of recent blockchain blocks, fed by the backend's `newBlock`
|
||||
@ -43,6 +44,15 @@ public:
|
||||
Q_INVOKABLE void appendRaw(const QString& timestamp, const QString& rawJson);
|
||||
Q_INVOKABLE void clear();
|
||||
|
||||
// Search the retained blocks for a transaction whose `id` matches `txId`
|
||||
// (case-insensitive, `0x`-tolerant). The blocks fed by the node's newBlock
|
||||
// subscription carry a per-transaction `id`; the node cannot look a mined
|
||||
// transaction up by hash (its tx-by-hash store is mempool-only and pruned
|
||||
// shortly after inclusion), so this in-memory scan is how a tx copied from
|
||||
// the blocks view is resolved. Returns { found, value, blockId, slot,
|
||||
// timestamp }; `value` is the prettified transaction JSON when found.
|
||||
QVariantMap findTransaction(const QString& txId) const;
|
||||
|
||||
signals:
|
||||
void countChanged();
|
||||
|
||||
@ -58,6 +68,7 @@ private:
|
||||
QString proof;
|
||||
QString voucherCm;
|
||||
QString signature;
|
||||
QString blockId;
|
||||
int txCount = 0;
|
||||
QStringList transactions;
|
||||
QString rawJson;
|
||||
|
||||
@ -219,6 +219,43 @@ QVariantMap BlockchainBackend::getCryptarchiaInfo()
|
||||
BLOCKCHAIN_MODULE_NAME, QStringLiteral("get_cryptarchia_info"))));
|
||||
}
|
||||
|
||||
QVariantMap BlockchainBackend::getBlock(QString headerIdHex)
|
||||
{
|
||||
if (!m_blockchainClient)
|
||||
return result::toVariantMap(result::err(QStringLiteral("Module not initialized.")));
|
||||
|
||||
return result::toVariantMap(result::toLogosResult(m_blockchainClient->invokeRemoteMethod(
|
||||
BLOCKCHAIN_MODULE_NAME, QStringLiteral("get_block"), headerIdHex.trimmed())));
|
||||
}
|
||||
|
||||
QVariantMap BlockchainBackend::getTransaction(QString txHashHex)
|
||||
{
|
||||
if (!m_blockchainClient)
|
||||
return result::toVariantMap(result::err(QStringLiteral("Module not initialized.")));
|
||||
|
||||
return result::toVariantMap(result::toLogosResult(m_blockchainClient->invokeRemoteMethod(
|
||||
BLOCKCHAIN_MODULE_NAME, QStringLiteral("get_transaction"), txHashHex.trimmed())));
|
||||
}
|
||||
|
||||
QVariantMap BlockchainBackend::findTransactionInBlocks(QString txHashHex)
|
||||
{
|
||||
// Local, in-memory resolution against the blocks currently held by the
|
||||
// model. The node's get_transaction only serves mempool (pending / very
|
||||
// recently mined) transactions, so a tx copied from the blocks view — which
|
||||
// is already mined — is looked up here instead. Returns the same shape as
|
||||
// the remote calls: { success, value, ... } with block context on success.
|
||||
const QVariantMap hit = m_blockModel->findTransaction(txHashHex);
|
||||
QVariantMap out;
|
||||
out.insert("success", hit.value("found").toBool());
|
||||
out.insert("value", hit.value("value"));
|
||||
out.insert("blockId", hit.value("blockId"));
|
||||
out.insert("slot", hit.value("slot"));
|
||||
out.insert("timestamp", hit.value("timestamp"));
|
||||
if (!out.value("success").toBool())
|
||||
out.insert("error", QStringLiteral("Not in loaded blocks."));
|
||||
return out;
|
||||
}
|
||||
|
||||
QVariantMap BlockchainBackend::getPeerId()
|
||||
{
|
||||
if (!m_blockchainClient)
|
||||
|
||||
@ -46,6 +46,9 @@ public slots:
|
||||
QVariantMap transferFunds(QString fromKeyHex, QString toKeyHex, QString amountStr) override;
|
||||
QVariantMap claimLeaderRewards() override;
|
||||
QVariantMap getCryptarchiaInfo() override;
|
||||
QVariantMap getBlock(QString headerIdHex) override;
|
||||
QVariantMap getTransaction(QString txHashHex) override;
|
||||
QVariantMap findTransactionInBlocks(QString txHashHex) override;
|
||||
QVariantMap getPeerId() override;
|
||||
QVariantMap getClaimableVouchers() override;
|
||||
QVariantMap generateConfig(QString outputPath, QStringList initialPeers, int netPort,
|
||||
|
||||
@ -16,6 +16,9 @@ class BlockchainBackend
|
||||
SLOT(QVariantMap transferFunds(QString fromKeyHex, QString toKeyHex, QString amountStr))
|
||||
SLOT(QVariantMap claimLeaderRewards())
|
||||
SLOT(QVariantMap getCryptarchiaInfo())
|
||||
SLOT(QVariantMap getBlock(QString headerIdHex))
|
||||
SLOT(QVariantMap getTransaction(QString txHashHex))
|
||||
SLOT(QVariantMap findTransactionInBlocks(QString txHashHex))
|
||||
SLOT(QVariantMap getPeerId())
|
||||
SLOT(QVariantMap getClaimableVouchers())
|
||||
SLOT(QVariantMap generateConfig(QString outputPath, QStringList initialPeers, int netPort, int blendPort, QString httpAddr, QString externalAddress, bool noPublicIpCheck, int deploymentMode, QString deploymentConfigPath, QString statePath))
|
||||
|
||||
@ -333,6 +333,10 @@ Rectangle {
|
||||
text: qsTr("Operations")
|
||||
enabled: opPage.nodeRunning
|
||||
}
|
||||
LogosTabButton {
|
||||
text: qsTr("Explorer")
|
||||
enabled: opPage.nodeRunning
|
||||
}
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
@ -581,6 +585,62 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Tab 2: Explorer (block / transaction lookup) ----
|
||||
ExplorerView {
|
||||
id: explorerView
|
||||
nodeRunning: opPage.nodeRunning
|
||||
|
||||
// Auto-detect the id kind. The node can't fetch a mined
|
||||
// transaction by hash (its tx store is mempool-only, pruned
|
||||
// ~10 min after inclusion), so resolve a tx in this order:
|
||||
// 1. loaded blocks — the blocks view already holds each
|
||||
// tx and its id, so a copied tx id resolves locally;
|
||||
// 2. get_block — the id is a block header id;
|
||||
// 3. get_transaction — a still-pending mempool tx.
|
||||
onSearchRequested: function(id) {
|
||||
if (!root.backend) return
|
||||
|
||||
// Every backend call is remoted through QtRO, so each
|
||||
// must be resolved via logos.watch (even the local scan,
|
||||
// whose search runs synchronously on the source side).
|
||||
|
||||
// Step 1: scan the loaded blocks for the tx by its id.
|
||||
logos.watch(
|
||||
root.backend.findTransactionInBlocks(id),
|
||||
function(local) {
|
||||
if (local.success) {
|
||||
explorerView.setTransactionResult(id, local.value, local.slot, local.blockId)
|
||||
return
|
||||
}
|
||||
// Step 2: block by header id.
|
||||
logos.watch(
|
||||
root.backend.getBlock(id),
|
||||
function(blockResult) {
|
||||
if (blockResult.success) {
|
||||
explorerView.setBlockResult(id, blockResult.value)
|
||||
return
|
||||
}
|
||||
// Step 3: pending transaction via the node.
|
||||
logos.watch(
|
||||
root.backend.getTransaction(id),
|
||||
function(txResult) {
|
||||
if (txResult.success)
|
||||
explorerView.setTransactionResult(id, txResult.value)
|
||||
else
|
||||
explorerView.setNotFound(id)
|
||||
},
|
||||
function(error) { explorerView.setError(id, _d.errorText(error)) }
|
||||
)
|
||||
},
|
||||
function(error) { explorerView.setError(id, _d.errorText(error)) }
|
||||
)
|
||||
},
|
||||
function(error) { explorerView.setError(id, _d.errorText(error)) }
|
||||
)
|
||||
}
|
||||
onCopyToClipboard: (text) => root.copyText(text)
|
||||
}
|
||||
}
|
||||
|
||||
// Sidebar nav entry used by the Operations tab. `pinnable` adds a
|
||||
|
||||
@ -5,7 +5,9 @@ import Logos.Theme
|
||||
import Logos.Controls
|
||||
|
||||
// A boxed, monospace, wrapping JSON/text block. Used for prettified payloads
|
||||
// and raw fallbacks.
|
||||
// and raw fallbacks. Rendered with a read-only TextEdit so the content can be
|
||||
// selected with the mouse and copied (Cmd/Ctrl+C), in addition to the dedicated
|
||||
// copy buttons elsewhere.
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
@ -18,15 +20,23 @@ Rectangle {
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
|
||||
LogosText {
|
||||
TextEdit {
|
||||
id: jsonText
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Theme.spacing.small
|
||||
text: root.json
|
||||
readOnly: true
|
||||
selectByMouse: true
|
||||
persistentSelection: true
|
||||
// Treat the payload as literal text; never interpret it as rich text.
|
||||
textFormat: TextEdit.PlainText
|
||||
color: Theme.palette.text
|
||||
selectionColor: Theme.palette.overlayOrange
|
||||
selectedTextColor: Theme.palette.text
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.family: "monospace"
|
||||
wrapMode: Text.WrapAnywhere
|
||||
wrapMode: TextEdit.WrapAnywhere
|
||||
}
|
||||
}
|
||||
|
||||
446
src/qml/views/ExplorerView.qml
Normal file
446
src/qml/views/ExplorerView.qml
Normal file
@ -0,0 +1,446 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Logos.Theme
|
||||
import Logos.Controls
|
||||
|
||||
import "../controls"
|
||||
|
||||
// Block/transaction explorer. The user pastes a block header id or a
|
||||
// transaction hash; the lookup is auto-detected (block first, then tx —
|
||||
// both are hex hashes and can't be told apart by shape) and the matching
|
||||
// entity is rendered with every field copyable.
|
||||
//
|
||||
// Orchestration lives in BlockchainView: `searchRequested` triggers a
|
||||
// get_block; on a miss it falls back to get_transaction. Results flow back
|
||||
// through setBlockResult / setTransactionResult / setNotFound / setError.
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
signal searchRequested(string id)
|
||||
signal copyToClipboard(string text)
|
||||
|
||||
property bool nodeRunning: false
|
||||
|
||||
// ---- Result state (driven by BlockchainView) ----
|
||||
// kind: "" (none) | "block" | "transaction" | "notfound" | "error"
|
||||
property string kind: ""
|
||||
property string queriedId: ""
|
||||
property string rawJson: ""
|
||||
property string errorText: ""
|
||||
property bool busy: false
|
||||
|
||||
// Block context for a transaction resolved from a loaded block (empty when
|
||||
// the tx came from the node's mempool lookup).
|
||||
property string txSlot: ""
|
||||
property string txBlockId: ""
|
||||
|
||||
// Parsed block fields (populated when kind === "block").
|
||||
property var block: null
|
||||
|
||||
function _reset() {
|
||||
root.kind = ""
|
||||
root.rawJson = ""
|
||||
root.errorText = ""
|
||||
root.block = null
|
||||
root.txSlot = ""
|
||||
root.txBlockId = ""
|
||||
}
|
||||
|
||||
// Called by BlockchainView when a get_block succeeds. `value` is the JSON
|
||||
// string returned by the module (tolerant of a few nesting shapes, mirroring
|
||||
// BlockModel::appendRaw).
|
||||
function setBlockResult(id, value) {
|
||||
root.busy = false
|
||||
root._reset()
|
||||
root.queriedId = id
|
||||
root.kind = "block"
|
||||
root.rawJson = value
|
||||
root.block = parseBlock(value)
|
||||
}
|
||||
|
||||
// `slot` / `blockId` are optional — supplied when the tx was resolved from
|
||||
// a loaded block, omitted for a pending mempool tx.
|
||||
function setTransactionResult(id, value, slot, blockId) {
|
||||
root.busy = false
|
||||
root._reset()
|
||||
root.queriedId = id
|
||||
root.kind = "transaction"
|
||||
root.rawJson = value
|
||||
root.txSlot = slot !== undefined && slot !== null ? String(slot) : ""
|
||||
root.txBlockId = blockId !== undefined && blockId !== null ? String(blockId) : ""
|
||||
}
|
||||
|
||||
function setNotFound(id) {
|
||||
root.busy = false
|
||||
root._reset()
|
||||
root.queriedId = id
|
||||
root.kind = "notfound"
|
||||
}
|
||||
|
||||
function setError(id, message) {
|
||||
root.busy = false
|
||||
root._reset()
|
||||
root.queriedId = id
|
||||
root.kind = "error"
|
||||
root.errorText = message
|
||||
}
|
||||
|
||||
// Tolerant block parser mirroring BlockModel::appendRaw:
|
||||
// { "block": "<stringified block>" } | { "block": {...} } | { "header": ... }
|
||||
// Returns a normalised object { header, slot, version, parentBlock, blockRoot,
|
||||
// signature, proof, entropy, leaderKey, voucherCm, transactions:[jsonStr] } or null.
|
||||
function parseBlock(s) {
|
||||
var obj = null
|
||||
try { obj = JSON.parse(s) } catch (e) { return null }
|
||||
if (!obj || typeof obj !== "object") return null
|
||||
|
||||
var b = null
|
||||
if (obj.block !== undefined) {
|
||||
if (typeof obj.block === "string") {
|
||||
try { b = JSON.parse(obj.block) } catch (e) { b = null }
|
||||
} else if (typeof obj.block === "object") {
|
||||
b = obj.block
|
||||
}
|
||||
} else if (obj.header !== undefined) {
|
||||
b = obj
|
||||
}
|
||||
if (!b || typeof b !== "object") return null
|
||||
|
||||
var header = b.header || {}
|
||||
var pol = header.proof_of_leadership || {}
|
||||
var txs = []
|
||||
var arr = b.transactions || []
|
||||
for (var i = 0; i < arr.length; ++i) {
|
||||
try { txs.push(JSON.stringify(arr[i])) }
|
||||
catch (e) { txs.push(String(arr[i])) }
|
||||
}
|
||||
return {
|
||||
slot: header.slot !== undefined ? String(header.slot) : "",
|
||||
version: header.version !== undefined ? String(header.version) : "",
|
||||
parentBlock: header.parent_block || "",
|
||||
blockRoot: header.block_root || "",
|
||||
signature: b.signature || "",
|
||||
proof: pol.proof || "",
|
||||
entropy: pol.entropy_contribution || "",
|
||||
leaderKey: pol.leader_key || "",
|
||||
voucherCm: pol.voucher_cm || "",
|
||||
transactions: txs
|
||||
}
|
||||
}
|
||||
|
||||
function pretty(s) {
|
||||
try { return JSON.stringify(JSON.parse(s), null, 2) } catch (e) { return s }
|
||||
}
|
||||
|
||||
function doSearch() {
|
||||
var id = idField.text.trim()
|
||||
if (id.length === 0) return
|
||||
root.busy = true
|
||||
root.searchRequested(id)
|
||||
}
|
||||
|
||||
spacing: Theme.spacing.large
|
||||
|
||||
// ---- Search bar ----
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: searchCol.implicitHeight + 2 * Theme.spacing.large
|
||||
color: Theme.palette.backgroundTertiary
|
||||
radius: Theme.spacing.radiusLarge
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
id: searchCol
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacing.large
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
LogosText {
|
||||
text: qsTr("Explorer")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.bold: true
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
InfoButton {
|
||||
Layout.alignment: Qt.AlignVCenter
|
||||
text: qsTr("Paste a block header id or a transaction hash, then press Search. The lookup is auto-detected: it tries a block first, then a transaction.")
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
LogosTextField {
|
||||
id: idField
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 30
|
||||
placeholderText: qsTr("Block id or transaction hash (hex)")
|
||||
enabled: root.nodeRunning && !root.busy
|
||||
|
||||
// LogosTextField wraps a TextInput (no `accepted` signal of
|
||||
// its own); connect to the inner input to search on Enter.
|
||||
Connections {
|
||||
target: idField.textInput
|
||||
function onAccepted() { root.doSearch() }
|
||||
}
|
||||
}
|
||||
|
||||
LogosButton {
|
||||
Layout.preferredWidth: 90
|
||||
Layout.preferredHeight: 30
|
||||
text: root.busy ? qsTr("…") : qsTr("Search")
|
||||
enabled: root.nodeRunning && !root.busy && idField.text.trim().length > 0
|
||||
onClicked: root.doSearch()
|
||||
}
|
||||
}
|
||||
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
visible: !root.nodeRunning
|
||||
text: qsTr("Start the node to look up blocks and transactions.")
|
||||
color: Theme.palette.textSecondary
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Status line (not found / error) ----
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
visible: root.kind === "notfound" || root.kind === "error"
|
||||
text: root.kind === "notfound"
|
||||
? qsTr("Nothing found for “%1”.\nBlocks are looked up by header id. Transactions resolve from the blocks currently loaded above, or from the node's mempool while still pending — mined transactions can't be fetched by hash, so open their block instead.").arg(root.queriedId)
|
||||
: root.errorText
|
||||
color: root.kind === "notfound" ? Theme.palette.textSecondary : Theme.palette.error
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
// ---- Result area ----
|
||||
ScrollView {
|
||||
id: resultScroll
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
visible: root.kind === "block" || root.kind === "transaction"
|
||||
clip: true
|
||||
|
||||
ColumnLayout {
|
||||
width: resultScroll.availableWidth
|
||||
spacing: Theme.spacing.large
|
||||
|
||||
// ---- Block result ----
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
visible: root.kind === "block"
|
||||
Layout.preferredHeight: blockCol.implicitHeight + 2 * Theme.spacing.large
|
||||
color: Theme.palette.backgroundTertiary
|
||||
radius: Theme.spacing.radiusLarge
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
id: blockCol
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacing.large
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.small
|
||||
LogosText {
|
||||
text: qsTr("Block")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.bold: true
|
||||
}
|
||||
// slot / version badges
|
||||
LogosText {
|
||||
visible: root.block && root.block.slot.length > 0
|
||||
text: qsTr("slot %1").arg(root.block ? root.block.slot : "")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
}
|
||||
Rectangle {
|
||||
visible: root.block && root.block.version.length > 0
|
||||
radius: Theme.spacing.radiusSmall
|
||||
color: Theme.palette.backgroundSecondary
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
implicitWidth: verText.implicitWidth + 2 * Theme.spacing.small
|
||||
implicitHeight: verText.implicitHeight + Theme.spacing.tiny
|
||||
LogosText {
|
||||
id: verText
|
||||
anchors.centerIn: parent
|
||||
text: root.block ? root.block.version : ""
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
}
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
LogosCopyButton {
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Copy raw block JSON")
|
||||
onCopyText: root.copyToClipboard(root.pretty(root.rawJson))
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: Theme.palette.borderSecondary
|
||||
}
|
||||
|
||||
HashRow {
|
||||
label: qsTr("Block id"); value: root.queriedId
|
||||
onCopyRequested: (t) => root.copyToClipboard(t)
|
||||
}
|
||||
HashRow {
|
||||
label: qsTr("Parent block"); value: root.block ? root.block.parentBlock : ""
|
||||
onCopyRequested: (t) => root.copyToClipboard(t)
|
||||
}
|
||||
HashRow {
|
||||
label: qsTr("Block root"); value: root.block ? root.block.blockRoot : ""
|
||||
onCopyRequested: (t) => root.copyToClipboard(t)
|
||||
}
|
||||
HashRow {
|
||||
label: qsTr("Signature"); value: root.block ? root.block.signature : ""
|
||||
onCopyRequested: (t) => root.copyToClipboard(t)
|
||||
}
|
||||
|
||||
LogosText {
|
||||
text: qsTr("Proof of leadership")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.bold: true
|
||||
Layout.topMargin: Theme.spacing.small
|
||||
}
|
||||
HashRow {
|
||||
label: qsTr("Leader key"); value: root.block ? root.block.leaderKey : ""
|
||||
onCopyRequested: (t) => root.copyToClipboard(t)
|
||||
}
|
||||
HashRow {
|
||||
label: qsTr("Entropy"); value: root.block ? root.block.entropy : ""
|
||||
onCopyRequested: (t) => root.copyToClipboard(t)
|
||||
}
|
||||
HashRow {
|
||||
label: qsTr("Proof"); value: root.block ? root.block.proof : ""
|
||||
onCopyRequested: (t) => root.copyToClipboard(t)
|
||||
}
|
||||
HashRow {
|
||||
label: qsTr("Voucher cm"); value: root.block ? root.block.voucherCm : ""
|
||||
onCopyRequested: (t) => root.copyToClipboard(t)
|
||||
}
|
||||
|
||||
LogosText {
|
||||
text: qsTr("Transactions (%1)").arg(root.block ? root.block.transactions.length : 0)
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.bold: true
|
||||
Layout.topMargin: Theme.spacing.small
|
||||
}
|
||||
ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
Repeater {
|
||||
model: root.block ? root.block.transactions : []
|
||||
delegate: TransactionDelegate {
|
||||
required property int index
|
||||
required property string modelData
|
||||
Layout.fillWidth: true
|
||||
idx: index
|
||||
json: modelData
|
||||
onCopyToClipboard: (t) => root.copyToClipboard(t)
|
||||
}
|
||||
}
|
||||
LogosText {
|
||||
visible: root.block && root.block.transactions.length === 0
|
||||
text: qsTr("No transactions in this block.")
|
||||
color: Theme.palette.textSecondary
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Transaction result ----
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
visible: root.kind === "transaction"
|
||||
Layout.preferredHeight: txCol.implicitHeight + 2 * Theme.spacing.large
|
||||
color: Theme.palette.backgroundTertiary
|
||||
radius: Theme.spacing.radiusLarge
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
id: txCol
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacing.large
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.small
|
||||
LogosText {
|
||||
text: qsTr("Transaction")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.bold: true
|
||||
}
|
||||
LogosText {
|
||||
visible: root.txSlot.length > 0
|
||||
text: qsTr("in block · slot %1").arg(root.txSlot)
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
LogosCopyButton {
|
||||
ToolTip.visible: hovered
|
||||
ToolTip.text: qsTr("Copy raw transaction JSON")
|
||||
onCopyText: root.copyToClipboard(root.pretty(root.rawJson))
|
||||
}
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 1
|
||||
color: Theme.palette.borderSecondary
|
||||
}
|
||||
|
||||
HashRow {
|
||||
label: qsTr("Transaction id"); value: root.queriedId
|
||||
onCopyRequested: (t) => root.copyToClipboard(t)
|
||||
}
|
||||
HashRow {
|
||||
visible: root.txBlockId.length > 0
|
||||
label: qsTr("Block id"); value: root.txBlockId
|
||||
onCopyRequested: (t) => root.copyToClipboard(t)
|
||||
}
|
||||
|
||||
TransactionDelegate {
|
||||
Layout.fillWidth: true
|
||||
idx: 0
|
||||
json: root.rawJson
|
||||
open: true
|
||||
onCopyToClipboard: (t) => root.copyToClipboard(t)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillHeight: true
|
||||
visible: root.kind === "" || root.kind === "notfound" || root.kind === "error"
|
||||
}
|
||||
}
|
||||
@ -6,6 +6,7 @@ NodeInfoView 1.0 NodeInfoView.qml
|
||||
AccountsView 1.0 AccountsView.qml
|
||||
TransferView 1.0 TransferView.qml
|
||||
LeaderRewardsView 1.0 LeaderRewardsView.qml
|
||||
ExplorerView 1.0 ExplorerView.qml
|
||||
GenerateConfigView 1.0 GenerateConfigView.qml
|
||||
ConfigChoiceView 1.0 ConfigChoiceView.qml
|
||||
SetConfigPathView 1.0 SetConfigPathView.qml
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user