add uptime, transaction

This commit is contained in:
liangping 2021-09-19 16:09:20 +08:00
parent 6c053ac065
commit d7472e30eb
8 changed files with 287 additions and 56 deletions

View File

@ -23,6 +23,7 @@
"summary": "Summary", "summary": "Summary",
"blocks": "Blocks", "blocks": "Blocks",
"blockchains": "Blockchains", "blockchains": "Blockchains",
"uptime": "Uptime",
"proposal_id": "Proposal ID", "proposal_id": "Proposal ID",
"proposal_type": "Proposal Type", "proposal_type": "Proposal Type",

View File

@ -189,7 +189,10 @@ export function abbrMessage(msg) {
if (Array.isArray(msg)) { if (Array.isArray(msg)) {
return msg.map(x => abbrMessage(x)).join(', ') return msg.map(x => abbrMessage(x)).join(', ')
} }
return msg.typeUrl.substring(msg.typeUrl.lastIndexOf('.') + 1).replace('Msg', '') if (msg.typeUrl) {
return msg.typeUrl.substring(msg.typeUrl.lastIndexOf('.') + 1).replace('Msg', '')
}
return msg.type.substring(msg.type.lastIndexOf('/') + 1).replace('Msg', '')
} }
export function abbrAddress(address, length = 10) { export function abbrAddress(address, length = 10) {

View File

@ -51,6 +51,10 @@ const chainAPI = class ChainFetch {
return this.get(`/blocks/${height}`).then(data => Block.create(data)) return this.get(`/blocks/${height}`).then(data => Block.create(data))
} }
async getSlashingSigningInfo() {
return this.get('/cosmos/slashing/v1beta1/signing_infos')
}
async getTxs(hash) { async getTxs(hash) {
const ver = this.getSelectedConfig() ? this.config.sdk_version : '0.41' const ver = this.getSelectedConfig() ? this.config.sdk_version : '0.41'
// /cosmos/tx/v1beta1/txs/{hash} // /cosmos/tx/v1beta1/txs/{hash}
@ -60,6 +64,18 @@ const chainAPI = class ChainFetch {
return this.get(`/cosmos/tx/v1beta1/txs/${hash}`).then(data => WrapStdTx.create(data, ver)) return this.get(`/cosmos/tx/v1beta1/txs/${hash}`).then(data => WrapStdTx.create(data, ver))
} }
async getTxsBySender(sender, page = 1) {
return this.get(`/txs?message.sender=${sender}&page=${page}&limit=20`)
}
async getTxsByRecipient(recipient) {
return this.get(`/txs?message.recipient=${recipient}`)
}
async getTxsByHeight(height) {
return this.get(`/txs?tx.height=${height}`)
}
async getValidatorDistribution(address) { async getValidatorDistribution(address) {
return this.get(`/distribution/validators/${address}`).then(data => { return this.get(`/distribution/validators/${address}`).then(data => {
const ret = ValidatorDistribution.create(commonProcess(data)) const ret = ValidatorDistribution.create(commonProcess(data))

View File

@ -21,6 +21,11 @@ const modules = [
title: 'governance', title: 'governance',
route: 'governance', route: 'governance',
}, },
{
scope: 'normal',
title: 'uptime',
route: 'uptime',
},
] ]
function processMenu() { function processMenu() {

View File

@ -112,6 +112,20 @@ const router = new VueRouter({
], ],
}, },
}, },
{
path: '/:chain/uptime',
name: 'uptime',
component: () => import('@/views/Uptime.vue'),
meta: {
pageTitle: 'Uptime',
breadcrumb: [
{
text: 'Uptime',
active: true,
},
],
},
},
{ {
path: '/:chain/account/:address', path: '/:chain/account/:address',
name: 'chain-account', name: 'chain-account',

View File

@ -236,28 +236,34 @@
</b-row> </b-row>
<b-row> <b-row>
<b-col> <b-col>
<b-card title="Uptime"> <b-card title="Transactions">
<b-card-body class="border-top"> <b-table
<b-button :items="txs"
v-for="(b,i) in blocks" striped
:key="i" hover
v-b-tooltip.hover.v-second responsive="sm"
:variant="b[0]?'primary':'outline-danger'" stacked="sm"
:title="b" >
rounded <template #cell(height)="data">
size="sm" <router-link :to="`../blocks/${data.item.height}`">
class="btn-icon mb-25 mr-25" {{ data.item.height }}
> &nbsp; </b-button> </router-link>
</b-card-body> </template>
<b-card-footer> <template #cell(txhash)="data">
<router-link :to="`../staking`"> <router-link :to="`../tx/${data.item.txhash}`">
<b-button {{ formatHash(data.item.txhash) }}
variant="outline-primary" </router-link>
> </template>
{{ $t('btn_back_list') }} </b-table>
</b-button> <b-pagination
</router-link> v-if="Number(transactions.page_total) > 1"
</b-card-footer> :total-rows="transactions.total_count"
:per-page="transactions.limit"
:value="transactions.page_number"
align="center"
class="mt-1"
@change="pageload"
/>
</b-card> </b-card>
</b-col> </b-col>
</b-row> </b-row>
@ -268,11 +274,11 @@
<script> <script>
import { import {
BCard, BButton, BAvatar, BRow, BCol, BCardBody, BCardFooter, VBTooltip, VBModal, BBadge, BCard, BButton, BAvatar, BRow, BCol, BTable, BCardFooter, VBTooltip, VBModal, BBadge, BPagination,
} from 'bootstrap-vue' } from 'bootstrap-vue'
import { import {
percent, formatToken, StakingParameters, Validator, operatorAddressToAccount, consensusPubkeyToHexAddress, toDay, percent, formatToken, StakingParameters, Validator, operatorAddressToAccount, consensusPubkeyToHexAddress, toDay, abbrMessage, abbrAddress,
} from '@/libs/data' } from '@/libs/data'
import { keybase } from '@/libs/fetch' import { keybase } from '@/libs/fetch'
import StakingAddressComponent from './StakingAddressComponent.vue' import StakingAddressComponent from './StakingAddressComponent.vue'
@ -287,9 +293,10 @@ export default {
BRow, BRow,
BCol, BCol,
BAvatar, BAvatar,
BCardBody,
BCardFooter, BCardFooter,
BBadge, BBadge,
BPagination,
BTable,
StakingAddressComponent, StakingAddressComponent,
StakingCommissionComponent, StakingCommissionComponent,
StakingRewardComponent, StakingRewardComponent,
@ -319,8 +326,22 @@ export default {
userData: {}, userData: {},
blocks: Array.from('0'.repeat(100)).map(x => [Boolean(x), Number(x)]), blocks: Array.from('0'.repeat(100)).map(x => [Boolean(x), Number(x)]),
distribution: {}, distribution: {},
transactions: {},
} }
}, },
computed: {
txs() {
if (this.transactions.txs) {
return this.transactions.txs.map(x => ({
height: Number(x.height),
txhash: x.txhash,
msgs: abbrMessage(x.tx.value ? x.tx.value.msg : x.tx.msg),
time: toDay(x.timestamp),
}))
}
return []
},
},
created() { created() {
this.$http.getStakingPool().then(res => { this.stakingPool = res }) this.$http.getStakingPool().then(res => { this.stakingPool = res })
this.$http.getStakingParameters().then(res => { this.stakingParameter = res }) this.$http.getStakingParameters().then(res => { this.stakingParameter = res })
@ -331,6 +352,9 @@ export default {
this.validator = data this.validator = data
this.processAddress(data.operator_address, data.consensus_pubkey) this.processAddress(data.operator_address, data.consensus_pubkey)
this.$http.getTxsBySender(this.accountAddress).then(res => {
this.transactions = res
})
const { identity } = data.description const { identity } = data.description
keybase(identity).then(d => { keybase(identity).then(d => {
@ -340,12 +364,14 @@ export default {
} }
}) })
}) })
this.initBlocks()
},
beforeDestroy() {
clearInterval(this.timer)
}, },
methods: { methods: {
pageload(v) {
this.$http.getTxsBySender(this.accountAddress, v).then(res => {
this.transactions = res
})
},
formatHash: abbrAddress,
timeFormat(value) { timeFormat(value) {
return toDay(value) return toDay(value)
}, },
@ -365,30 +391,6 @@ export default {
apr(rate) { apr(rate) {
return `${percent((1 - rate) * this.mintInflation)} %` return `${percent((1 - rate) * this.mintInflation)} %`
}, },
initBlocks() {
this.$http.getLatestBlock().then(d => {
const { height } = d.block.last_commit
// update height
const blocks = []
for (let i = height - 99; i < height; i += 1) {
blocks.push([false, i])
}
const sig = d.block.last_commit.signatures.find((s => s.validator_address === this.hexAddress))
const exist = typeof sig !== 'undefined'
blocks.push([exist, height])
this.blocks = blocks
// update uptime status
const previous = []
blocks.forEach(item => {
previous.push(this.fetch_status(item))
})
Promise.allSettled(previous).then(() => {
this.timer = setInterval(this.fetch_latest, 6000)
})
})
},
fetch_status(item, lastHeight) { fetch_status(item, lastHeight) {
return this.$http.getBlockByHeight(item[1]).then(res => { return this.$http.getBlockByHeight(item[1]).then(res => {
if (item[1] !== lastHeight) { if (item[1] !== lastHeight) {

136
src/views/Uptime.vue Normal file
View File

@ -0,0 +1,136 @@
<template>
<div class="container-md px-0">
<b-card>
<b-card
no-body
class="mb-1"
>
<b-form-input
v-model="query"
placeholder="Keywords to filter validators"
/>
</b-card>
<b-row>
<b-col
v-for="x in uptime"
:key="x.moniker"
sm="12"
md="6"
>
<span class="font-weight-bold">{{ x.validator.moniker?x.validator.moniker:x.address }}</span>
<div class="d-flex justify-content-between align-self-stretch flex-wrap">
<div
v-for="(b,i) in blocks"
:key="i"
style="width:1.5%; margin:0.5px; border-radius: 3px; display: inline-block;"
><router-link :to="`./blocks/${b.height}`">
<div
v-b-tooltip.hover.v-second
:title="b.height"
:class="b.sigs && b.sigs[x.address] ? 'bg-success':'bg-danger'"
>&nbsp;</div>
</router-link>
</div>
</div></b-col>
</b-row>
</b-card>
</div>
</template>
<script>
import {
BRow, BCol, VBTooltip, BFormInput, BCard,
} from 'bootstrap-vue'
import { consensusPubkeyToHexAddress } from '@/libs/data'
export default {
components: {
BRow,
BCol,
BFormInput,
BCard,
},
directives: {
'b-tooltip': VBTooltip,
},
data() {
return {
query: '',
validators: [],
missing: {},
blocks: Array.from('0'.repeat(100)).map(x => [Boolean(x), Number(x)]),
}
},
computed: {
uptime() {
const vals = this.query ? this.validators.filter(x => String(x.description.moniker).indexOf(this.query) > -1) : this.validators
return vals.map(x => ({
validator: x.description,
address: consensusPubkeyToHexAddress(x.consensus_pubkey),
}))
},
},
created() {
this.$http.getValidatorList().then(res => {
this.validators = res
})
this.initBlocks()
},
beforeDestroy() {
clearInterval(this.timer)
},
methods: {
initBlocks() {
this.$http.getLatestBlock().then(d => {
const { height } = d.block.last_commit
// update height
const blocks = []
for (let i = height - 49; i < height; i += 1) {
blocks.push({ sigs: {}, height: i })
this.fetch_status(i, height)
}
const sigs = {}
d.block.last_commit.signatures.forEach(x => {
sigs[x.validator_address] = !!x.signature
})
blocks.push({ sigs, height })
this.blocks = blocks
this.timer = setInterval(this.fetch_latest, 6000)
})
},
fetch_status(height, lastHeight) {
return this.$http.getBlockByHeight(height).then(res => {
if (height !== lastHeight) {
const sigs = {}
res.block.last_commit.signatures.forEach(x => {
sigs[x.validator_address] = !!x.signature
})
const block = this.blocks.find(b => b.height === height)
if (typeof block !== 'undefined') {
this.$set(block, 'sigs', sigs)
}
}
})
},
fetch_latest() {
this.$http.getLatestBlock().then(res => {
const sigs = {}
res.block.last_commit.signatures.forEach(x => {
sigs[x.validator_address] = !!x.signature
})
const block = this.blocks.find(b => b[1] === res.block.last_commit.height)
if (typeof block === 'undefined') { // mei
// this.$set(block, 0, typeof sigs !== 'undefined')
if (this.blocks.length > 50) this.blocks.shift()
this.blocks.push({ sigs, height: res.block.last_commit.height })
}
})
},
},
}
</script>
<style></style>

View File

@ -189,6 +189,37 @@
</b-card-body> </b-card-body>
</b-card> </b-card>
<b-card title="Transactions">
<b-table
:items="txs"
striped
hover
responsive="sm"
stacked="sm"
>
<template #cell(height)="data">
<router-link :to="`../blocks/${data.item.height}`">
{{ data.item.height }}
</router-link>
</template>
<template #cell(txhash)="data">
<router-link :to="`../tx/${data.item.txhash}`">
{{ formatHash(data.item.txhash) }}
</router-link>
</template>
</b-table>
<b-pagination
v-if="Number(transactions.page_total) > 1"
:total-rows="transactions.total_count"
:per-page="transactions.limit"
:value="transactions.page_number"
align="center"
class="mt-1"
@change="pageload"
/>
</b-card>
<b-card <b-card
v-if="account" v-if="account"
title="Profile" title="Profile"
@ -324,7 +355,7 @@
<script> <script>
import { import {
BCard, BAvatar, BPopover, BTable, BRow, BCol, BTableSimple, BTr, BTd, BTbody, BCardHeader, BCardTitle, BButton, BCardBody, VBModal, BCard, BAvatar, BPopover, BTable, BRow, BCol, BTableSimple, BTr, BTd, BTbody, BCardHeader, BCardTitle, BButton, BCardBody, VBModal,
BButtonGroup, VBTooltip, BButtonGroup, VBTooltip, BPagination,
} from 'bootstrap-vue' } from 'bootstrap-vue'
import FeatherIcon from '@/@core/components/feather-icon/FeatherIcon.vue' import FeatherIcon from '@/@core/components/feather-icon/FeatherIcon.vue'
import ToastificationContent from '@core/components/toastification/ToastificationContent.vue' import ToastificationContent from '@core/components/toastification/ToastificationContent.vue'
@ -332,7 +363,7 @@ import Ripple from 'vue-ripple-directive'
import VueQr from 'vue-qr' import VueQr from 'vue-qr'
import chainAPI from '@/libs/fetch' import chainAPI from '@/libs/fetch'
import { import {
formatToken, formatTokenAmount, formatTokenDenom, getStakingValidatorOperator, percent, tokenFormatter, toDay, toDuration, formatToken, formatTokenAmount, formatTokenDenom, getStakingValidatorOperator, percent, tokenFormatter, toDay, toDuration, abbrMessage, abbrAddress,
} from '@/libs/data' } from '@/libs/data'
import { $themeColors } from '@themeConfig' import { $themeColors } from '@themeConfig'
import ObjectFieldComponent from './ObjectFieldComponent.vue' import ObjectFieldComponent from './ObjectFieldComponent.vue'
@ -363,6 +394,7 @@ export default {
BButtonGroup, BButtonGroup,
BTr, BTr,
BTd, BTd,
BPagination,
// eslint-disable-next-line vue/no-unused-components // eslint-disable-next-line vue/no-unused-components
ToastificationContent, ToastificationContent,
ObjectFieldComponent, ObjectFieldComponent,
@ -393,6 +425,7 @@ export default {
redelegations: [], redelegations: [],
unbonding: [], unbonding: [],
quotes: {}, quotes: {},
transactions: [],
doughnutChart: { doughnutChart: {
options: { options: {
responsive: true, responsive: true,
@ -435,6 +468,17 @@ export default {
} }
return 'Profile' return 'Profile'
}, },
txs() {
if (this.transactions.txs) {
return this.transactions.txs.map(x => ({
height: Number(x.height),
txhash: x.txhash,
msgs: abbrMessage(x.tx.value.msg),
time: toDay(x.timestamp),
}))
}
return []
},
assetTable() { assetTable() {
let total = [] let total = []
let sum = 0 let sum = 0
@ -606,14 +650,24 @@ export default {
this.$http.getStakingUnbonding(this.address).then(res => { this.$http.getStakingUnbonding(this.address).then(res => {
this.unbonding = res.unbonding_responses this.unbonding = res.unbonding_responses
}) })
this.$http.getTxsBySender(this.address).then(res => {
this.transactions = res
})
// this.$http.getStakingValidators(this.address).then(res => { // this.$http.getStakingValidators(this.address).then(res => {
// console.log(res) // console.log(res)
// }) // })
}, },
methods: { methods: {
pageload(v) {
this.$http.getTxsBySender(this.address, v).then(res => {
this.transactions = res
})
},
selectValue(v) { selectValue(v) {
this.selectedValidator = v this.selectedValidator = v
}, },
formatHash: abbrAddress,
formatDenom(v) { formatDenom(v) {
return formatTokenDenom(this.denoms[v] ? this.denoms[v] : v) return formatTokenDenom(this.denoms[v] ? this.denoms[v] : v)
}, },