Files
lnbits/lnbits/templates/pages/blockexplorer.vue
T
2026-07-13 09:09:42 +02:00

359 lines
13 KiB
Vue

<template id="page-blockexplorer">
<div class="row q-col-gutter-md">
<!-- Left column: blocks + search + results -->
<div class="col-12 col-md-7 q-gutter-y-md">
<!-- Recent blocks (mempool-style squares) -->
<q-card v-if="blocks.length">
<q-card-section>
<div
class="text-subtitle1 q-mb-md"
v-text="$t('recent_blocks')"
></div>
<div class="row q-gutter-sm">
<q-card
v-for="b in formattedBlocks"
:key="b.height"
flat
class="bg-primary text-white cursor-pointer"
v-ripple
@click="openBlock(b)"
>
<q-card-section class="q-pa-sm">
<div
class="text-subtitle1 text-weight-bold"
v-text="'#' + b.height.toLocaleString()"
></div>
<div class="text-caption q-mt-xs" v-text="b.timeAgo"></div>
<div class="text-caption q-mt-sm">
<code v-text="b.shortHash"></code>
</div>
<div class="text-caption" v-text="'diff ' + b.difficulty"></div>
</q-card-section>
</q-card>
</div>
</q-card-section>
</q-card>
<!-- Block detail dialog -->
<q-dialog v-model="blockDialog">
<q-card v-if="selectedBlock">
<q-card-section class="bg-primary text-white q-pb-sm">
<div
class="text-h6"
v-text="'Block #' + selectedBlock.height.toLocaleString()"
></div>
<div class="text-caption" v-text="selectedBlock.utcTime"></div>
</q-card-section>
<q-card-section>
<div class="q-mb-md">
<div class="text-caption text-grey q-mb-xs">Hash</div>
<code
class="text-caption be-wrap"
v-text="selectedBlock.hash"
></code>
</div>
<div class="q-mb-md">
<div class="text-caption text-grey q-mb-xs">Previous block</div>
<code
class="text-caption be-wrap"
v-text="selectedBlock.prev_hash"
></code>
</div>
<div class="q-mb-lg">
<div class="text-caption text-grey q-mb-xs">Merkle root</div>
<code
class="text-caption be-wrap"
v-text="selectedBlock.merkle_root"
></code>
</div>
<div class="row q-col-gutter-md">
<div class="col-6 col-sm-3">
<div class="text-caption text-grey">Version</div>
<div v-text="'0x' + selectedBlock.version.toString(16)"></div>
</div>
<div class="col-6 col-sm-3">
<div class="text-caption text-grey">Bits</div>
<div v-text="selectedBlock.bits"></div>
</div>
<div class="col-6 col-sm-3">
<div class="text-caption text-grey">Difficulty</div>
<div v-text="selectedBlock.difficulty"></div>
</div>
<div class="col-6 col-sm-3">
<div class="text-caption text-grey">Nonce</div>
<div v-text="selectedBlock.nonce.toLocaleString()"></div>
</div>
</div>
</q-card-section>
<q-card-actions align="right">
<q-btn flat v-close-popup v-text="$t('close')"></q-btn>
</q-card-actions>
</q-card>
</q-dialog>
<q-card>
<q-card-section>
<div class="text-h6" v-text="$t('block_explorer')"></div>
</q-card-section>
<q-card-section class="q-pt-none">
<q-input
filled
v-model="query"
:label="$t('blockexplorer_search_label')"
:hint="$t('blockexplorer_search_hint')"
@keyup.enter="search"
>
<template v-slot:append>
<q-btn
flat
round
dense
icon="search"
:loading="loading"
@click="search"
/>
</template>
</q-input>
</q-card-section>
</q-card>
<!-- Transaction result -->
<q-card v-if="txResult">
<q-card-section>
<div class="row items-center justify-between q-mb-sm">
<div class="row items-center q-gutter-sm">
<div class="text-subtitle1" v-text="$t('transaction')"></div>
<q-badge
v-if="txStatus"
:color="txStatus.confirmed ? 'positive' : 'orange'"
:label="txStatus.confirmed ? $t('confirmed') : $t('unconfirmed')"
></q-badge>
<q-spinner v-if="!txStatus" size="1em" color="grey" />
</div>
<q-btn flat round dense icon="close" @click="clearResult" />
</div>
<div class="q-mb-sm">
<span class="text-caption text-grey">txid: </span>
<code class="text-caption be-wrap" v-text="txResult.txid"></code>
</div>
<div class="row q-col-gutter-md q-mb-sm">
<div class="col-auto" v-if="txStatus && txStatus.height">
<div
class="text-caption text-grey"
v-text="$t('block_height')"
></div>
<div v-text="txStatus.height.toLocaleString()"></div>
</div>
<div class="col-auto" v-if="txStatus && txStatus.fee !== null">
<div class="text-caption text-grey">fee</div>
<div v-text="txStatus.fee + ' sat'"></div>
</div>
<div class="col-auto" v-if="txResult.vsize || txResult.size">
<div class="text-caption text-grey">vsize</div>
<div v-text="(txResult.vsize || txResult.size) + ' vB'"></div>
</div>
<div class="col-auto" v-if="txResult.weight">
<div class="text-caption text-grey">weight</div>
<div v-text="txResult.weight + ' WU'"></div>
</div>
</div>
<q-expansion-item
icon="login"
:label="$t('inputs') + ' (' + txResult.vin.length + ')'"
dense
class="q-mb-xs"
>
<q-list dense separator>
<q-item v-for="(vin, i) in txResult.vin" :key="i">
<q-item-section>
<q-item-label
v-if="vin.coinbase"
class="text-grey"
v-text="$t('coinbase')"
>
</q-item-label>
<q-item-label v-else class="be-wrap">
<a
href="#"
@click.prevent="loadTx(vin.txid)"
class="text-primary"
v-text="vin.txid + ':' + vin.vout"
></a>
</q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-expansion-item>
<q-expansion-item
icon="logout"
:label="$t('outputs') + ' (' + txResult.vout.length + ')'"
dense
>
<q-list dense separator>
<q-item v-for="(vout, i) in txResult.vout" :key="i">
<q-item-section>
<q-item-label>
<template
v-if="vout.scriptPubKey && vout.scriptPubKey.address"
>
<a
href="#"
@click.prevent="loadAddress(vout.scriptPubKey.address)"
class="text-primary"
v-text="vout.scriptPubKey.address"
></a>
</template>
<template
v-else-if="
vout.scriptPubKey &&
vout.scriptPubKey.type === 'nulldata'
"
>
<span class="text-grey">OP_RETURN</span>
</template>
<template v-else-if="vout.scriptPubKey">
<span v-text="vout.scriptPubKey.type"></span>
</template>
</q-item-label>
<q-item-label
caption
v-text="vout.value + ' BTC'"
></q-item-label>
</q-item-section>
</q-item>
</q-list>
</q-expansion-item>
</q-card-section>
</q-card>
<!-- Address result -->
<q-card v-if="addressResult">
<q-card-section>
<div class="row items-center justify-between q-mb-xs">
<div class="text-subtitle1" v-text="$t('address')"></div>
<q-btn flat round dense icon="close" @click="clearResult" />
</div>
<div class="text-caption q-mb-sm">
<code class="be-wrap" v-text="currentAddress"></code>
</div>
<div class="row q-col-gutter-md q-mb-md">
<div class="col-auto">
<div
class="text-caption text-grey"
v-text="$t('confirmed_balance')"
></div>
<div
v-text="
addressResult.balance.confirmed.toLocaleString() + ' sat'
"
></div>
</div>
<div
class="col-auto"
v-if="addressResult.balance.unconfirmed !== 0"
>
<div
class="text-caption text-grey"
v-text="$t('unconfirmed_balance')"
></div>
<div
v-text="
addressResult.balance.unconfirmed.toLocaleString() + ' sat'
"
></div>
</div>
</div>
<div
class="text-subtitle2 q-mb-xs"
v-text="
$t('transaction_history') +
' (' +
addressResult.history.length +
')'
"
></div>
<q-list dense separator>
<q-item
v-for="h in addressResult.history"
:key="h.tx_hash"
clickable
v-ripple
@click="loadTx(h.tx_hash)"
>
<q-item-section>
<q-item-label
class="text-primary be-wrap"
v-text="h.tx_hash"
></q-item-label>
<q-item-label
caption
v-text="
h.height > 0
? $t('block_height') + ': ' + h.height.toLocaleString()
: $t('unconfirmed')
"
></q-item-label>
</q-item-section>
<q-item-section side>
<q-icon name="chevron_right" color="grey-5"></q-icon>
</q-item-section>
</q-item>
</q-list>
<div
v-if="addressResult.history_error"
class="text-warning q-mt-sm text-caption"
v-text="$t('history_unavailable')"
></div>
<div
v-else-if="addressResult.history.length === 0"
class="text-grey q-mt-sm"
v-text="$t('no_transactions')"
></div>
</q-card-section>
</q-card>
</div>
<!-- Right column: chain tip + fees -->
<div class="col-12 col-md-5 q-gutter-y-md">
<q-card v-if="tip">
<q-card-section>
<div class="text-subtitle1 q-mb-sm" v-text="$t('chain_tip')"></div>
<div class="text-caption text-grey" v-text="$t('block_height')"></div>
<div
class="text-h6 q-mb-md"
v-text="tip.height.toLocaleString()"
></div>
<template v-if="feeList.length">
<div
class="text-subtitle2 q-mb-sm"
v-text="$t('fee_estimates')"
></div>
<q-list dense>
<q-item v-for="f in feeList" :key="f.label" class="q-px-none">
<q-item-section>
<q-item-label
class="text-caption text-grey"
v-text="f.label"
></q-item-label>
</q-item-section>
<q-item-section side>
<q-item-label
class="text-body2"
v-text="f.rate"
></q-item-label>
</q-item-section>
</q-item>
</q-list>
</template>
</q-card-section>
</q-card>
</div>
</div>
</template>
<style>
.be-wrap {
word-break: break-all;
}
</style>