Finish validator detail

This commit is contained in:
liangping
2021-08-03 00:13:53 +08:00
parent 18e0d933b0
commit e4fca8f41b
22 changed files with 1326 additions and 7 deletions
+5 -1
View File
@@ -56,7 +56,11 @@
</b-avatar>
</template>
<span class="font-weight-bolder d-block text-nowrap">
{{ data.item.description.moniker }}
<router-link
:to="`./staking/${data.item.operator_address}`"
>
{{ data.item.description.moniker }}
</router-link>
</span>
<small class="text-muted">{{ data.item.description.website || data.item.description.identity }}</small>
</b-media>
+144
View File
@@ -0,0 +1,144 @@
<template>
<b-card
title="Address"
class="align-items-stretch "
>
<!-- address media -->
<b-media
class="mb-1"
no-body
>
<b-media-aside class="mr-1">
<b-avatar
rounded
variant="light-primary"
size="34"
>
<feather-icon
icon="UserIcon"
size="18"
/>
</b-avatar>
</b-media-aside>
<b-media-body>
<h6 class="mb-0">
Account Address
</h6>
<small>{{ accountAddress }}</small>
</b-media-body>
</b-media>
<b-media
class="mb-1"
no-body
>
<b-media-aside class="mr-1">
<b-avatar
rounded
variant="light-primary"
size="34"
>
<feather-icon
icon="Link2Icon"
size="18"
/>
</b-avatar>
</b-media-aside>
<b-media-body>
<h6 class="mb-0">
Validator Address
</h6>
<small>{{ operatorAddress }}</small>
</b-media-body>
</b-media>
<b-media
class="mb-1"
no-body
>
<b-media-aside class="mr-1">
<b-avatar
rounded
variant="light-primary"
size="34"
>
<feather-icon
icon="KeyIcon"
size="18"
/>
</b-avatar>
</b-media-aside>
<b-media-body>
<h6 class="mb-0">
Consensus Public Address
</h6>
<small>{{ consensusPubkey }}</small>
</b-media-body>
</b-media>
<b-media
class="mb-1"
no-body
>
<b-media-aside class="mr-1">
<b-avatar
rounded
variant="light-primary"
size="34"
>
<feather-icon
icon="HashIcon"
size="18"
/>
</b-avatar>
</b-media-aside>
<b-media-body>
<h6 class="mb-0">
Hex Address
</h6>
<small>{{ hexAddress }}</small>
</b-media-body>
</b-media>
</b-card>
</template>
<script>
import {
BCard, BAvatar, BMedia, BMediaAside, BMediaBody, VBTooltip,
} from 'bootstrap-vue'
export default {
components: {
BCard,
BAvatar,
BMedia,
BMediaAside,
BMediaBody,
},
directives: {
'b-tooltip': VBTooltip,
},
props: {
operatorAddress: {
type: String,
default: '-',
},
accountAddress: {
type: String,
default: '-',
},
consensusPubkey: {
type: [Object, String],
required: true,
},
hexAddress: {
type: String,
default: '-',
},
},
}
</script>
<style scoped>
.media small {
white-space:nowrap;
overflow: hidden;
}
</style>
+151
View File
@@ -0,0 +1,151 @@
<template>
<b-card
no-body
>
<b-card-header>
<h4 class="mb-0">
Commission
</h4>
<b-card-text class="font-small-5 mb-0">
Updated on {{ dateFormat(data.update_time) }}
</b-card-text>
</b-card-header>
<!-- apex chart -->
<vue-apex-charts
type="radialBar"
height="145"
class="my-2"
:options="goalOverviewRadialBar"
:series="[percentFormat(data.rate)]"
/>
<b-row class="text-center mx-0">
<b-col
cols="6"
class="border-top border-right d-flex align-items-between flex-column py-1"
>
<b-card-text class="text-muted mb-0">
Max Rate
</b-card-text>
<h3 class="font-weight-bolder mb-0">
{{ percentFormat(data.max_rate) }}%
</h3>
</b-col>
<b-col
cols="6"
class="border-top d-flex align-items-between flex-column py-1"
>
<b-card-text class="text-muted mb-0">
Max Change Rate
</b-card-text>
<h3 class="font-weight-bolder mb-0">
{{ percentFormat(data.max_change_rate) }}%
</h3>
</b-col>
</b-row>
</b-card>
</template>
<script>
import {
BCard, BCardHeader, BRow, BCol, BCardText,
} from 'bootstrap-vue'
import VueApexCharts from 'vue-apexcharts'
import { $themeColors } from '@themeConfig'
import { percent, toDay } from '@/libs/data'
const $strokeColor = '#ebe9f1'
const $textHeadingColor = '#5e5873'
const $goalStrokeColor2 = '#51e5a8'
export default {
components: {
VueApexCharts,
BCard,
BCardHeader,
BRow,
BCardText,
BCol,
},
props: {
data: {
type: Object,
default: () => {},
},
},
data() {
return {
goalOverviewRadialBar: {
chart: {
height: 105,
type: 'radialBar',
sparkline: {
enabled: true,
},
dropShadow: {
enabled: true,
blur: 3,
left: 1,
top: 1,
opacity: 0.1,
},
},
colors: [$goalStrokeColor2],
plotOptions: {
radialBar: {
offsetY: -10,
startAngle: -150,
endAngle: 150,
hollow: {
size: '60%',
},
track: {
background: $strokeColor,
strokeWidth: '80%',
},
dataLabels: {
name: {
show: false,
},
value: {
color: $textHeadingColor,
fontSize: '2.86rem',
fontWeight: '600',
},
},
},
},
fill: {
type: 'gradient',
gradient: {
shade: 'dark',
type: 'horizontal',
shadeIntensity: 0.5,
gradientToColors: [$themeColors.success],
inverseColors: true,
opacityFrom: 1,
opacityTo: 1,
stops: [0, 100],
},
},
stroke: {
lineCap: 'round',
},
grid: {
padding: {
bottom: 10,
},
},
},
}
},
methods: {
dateFormat(value) {
return toDay(value)
},
percentFormat(value) {
return percent(value)
},
},
}
</script>
+146
View File
@@ -0,0 +1,146 @@
<template>
<b-card
class="card-transaction"
no-body
>
<b-card-header>
<b-card-title>Outstanding Rewards</b-card-title>
<feather-icon
icon="MoreVerticalIcon"
size="18"
class="cursor-pointer"
/>
</b-card-header>
<b-card-body>
<div
v-for="d in data.self_bond_rewards"
:key="d.amount"
class="transaction-item"
>
<b-media no-body>
<b-media-aside>
<b-avatar
rounded
size="42"
variant="light-success"
/>
</b-media-aside>
<b-media-body>
<h6 class="transaction-title">
{{ d.amount }}
</h6>
<small>{{ d.denom }} </small>
</b-media-body>
</b-media>
<div
class="font-weight-bolder text-success d-none d-md-block hidden-md-down "
>
Reward
</div>
</div>
<div
v-for="d in data.val_commission"
:key="d.amount"
class="transaction-item"
>
<b-media no-body>
<b-media-aside>
<b-avatar
rounded
size="42"
variant="light-primary"
>
<feather-icon
size="18"
icon="ServerIcon"
/>
</b-avatar>
</b-media-aside>
<b-media-body>
<h6 class="transaction-title">
{{ d.amount }}
</h6>
<small>{{ d.denom }}</small>
</b-media-body>
</b-media>
<div
class="font-weight-bolder text-primary hidden-sm hidden-md"
>
Commission
</div>
</div>
</b-card-body>
</b-card>
</template>
<script>
import {
BCard, BCardHeader, BCardTitle, BCardBody, BMediaBody, BMedia, BMediaAside, BAvatar,
} from 'bootstrap-vue'
export default {
components: {
BCard,
BCardHeader,
BCardTitle,
BCardBody,
BMediaBody,
BMedia,
BMediaAside,
BAvatar,
},
props: {
data: {
type: Object,
required: true,
},
},
data() {
return {
transactionData: [
{
mode: 'Wallet',
types: 'Starbucks',
avatar: 'PocketIcon',
avatarVariant: 'light-primary',
payment: '-$74',
deduction: true,
},
{
mode: 'Bank Transfer',
types: 'Add Money',
avatar: 'CheckIcon',
avatarVariant: 'light-success',
payment: '+$480',
deduction: false,
},
{
mode: 'Paypal',
types: 'Add Money',
avatar: 'DollarSignIcon',
avatarVariant: 'light-danger',
payment: '+$480',
deduction: false,
},
{
mode: 'Mastercard',
types: 'Ordered Food',
avatar: 'CreditCardIcon',
avatarVariant: 'light-warning',
payment: '-$23',
deduction: true,
},
{
mode: 'Transfer',
types: 'Refund',
avatar: 'TrendingUpIcon',
avatarVariant: 'light-info',
payment: '+$98',
deduction: false,
},
],
}
},
}
</script>
+401
View File
@@ -0,0 +1,401 @@
<template>
<div>
<b-card class="border-primary">
<b-row>
<!-- User Info: Left col -->
<b-col
cols="21"
xl="6"
class="d-flex justify-content-between flex-column"
>
<!-- User Avatar & Action Buttons -->
<div class="d-flex justify-content-start">
<b-avatar
:src="validator.avatar"
:variant="`light-primary`"
size="104px"
rounded
/>
<div class="d-flex flex-column ml-1">
<div class="mb-1">
<h4 class="mb-0">
{{ validator.description.moniker }}
</h4>
<span class="card-text">{{ validator.description.website }}</span>
</div>
<div class="d-flex flex-wrap">
<b-button
variant="primary"
>
Delegate
</b-button>
<b-button
variant="outline-danger"
class="ml-1"
>
Redelegate
</b-button>
</div>
</div>
</div>
<!-- User Stats -->
<div class="d-flex align-items-center mt-2">
<div class="d-flex align-items-center mr-2">
<b-avatar
variant="light-primary"
rounded
>
<feather-icon
icon="DollarSignIcon"
size="18"
/>
</b-avatar>
<div class="ml-1">
<h5 class="mb-0">
{{ tokenFormatter(validator.tokens) }}
</h5>
<small>Bonded Tokens</small>
</div>
</div>
<div class="d-flex align-items-center mr-2">
<b-avatar
variant="light-success"
rounded
>
<feather-icon
icon="TrendingUpIcon"
size="18"
/>
</b-avatar>
<div class="ml-1">
<h5 class="mb-0">
{{ apr(validator.commission.rate) }}
</h5>
<small>Annual Profit</small>
</div>
</div>
<div class="d-flex align-items-center">
<b-avatar
variant="light-warning"
rounded
>
<feather-icon
icon="SmileIcon"
size="18"
/>
</b-avatar>
<div class="ml-1">
<h5 class="mb-0">
{{ percentFormat(selfDelegation.balance.amount/validator.tokens) }}%
</h5>
<small>Self Delegation</small>
</div>
</div>
</div>
</b-col>
<!-- Right Col: Table -->
<b-col
cols="12"
xl="6"
>
<table class="mt-2 mt-xl-0 w-100">
<tr>
<th class="pb-50">
<feather-icon
icon="UserIcon"
class="mr-75"
/>
<span class="font-weight-bold">Identity</span>
</th>
<td class="pb-50">
{{ validator.description.identity }}
</td>
</tr>
<tr>
<th class="pb-50">
<feather-icon
icon="CheckIcon"
class="mr-75"
/>
<span class="font-weight-bold">Status</span>
</th>
<td class="pb-50 text-capitalize">
{{ validator.status }}
</td>
</tr>
<tr>
<th class="pb-50">
<feather-icon
icon="StarIcon"
class="mr-75"
/>
<span class="font-weight-bold">Unbond Height</span>
</th>
<td class="pb-50 text-capitalize">
{{ validator.unbonding_height }}
</td>
</tr>
<tr>
<th class="pb-50">
<feather-icon
icon="StarIcon"
class="mr-75"
/>
<span class="font-weight-bold">Unbond Time</span>
</th>
<td class="pb-50 text-capitalize">
{{ timeFormat(validator.unbonding_time) }}
</td>
</tr>
<tr>
<th class="pb-50">
<feather-icon
icon="FlagIcon"
class="mr-75"
/>
<span class="font-weight-bold">Min Self Delegation</span>
</th>
<td class="pb-50">
{{ validator.min_self_delegation }}
</td>
</tr>
<tr>
<th class="pb-50">
<feather-icon
icon="AlertCircleIcon"
class="mr-75"
/>
<span class="font-weight-bold">Jailed</span>
</th>
<td class="pb-50">
{{ validator.jailed }}
</td>
</tr>
<tr>
<th>
<feather-icon
icon="PhoneIcon"
class="mr-75"
/>
<span class="font-weight-bold">Contact</span>
</th>
<td>
{{ validator.security_contact }}
</td>
</tr>
</table>
</b-col>
</b-row>
<b-card-footer
v-if="validator.description.details"
class="mt-1"
>
{{ validator.description.details || '' }}
</b-card-footer>
</b-card>
<!-- First Row -->
<template>
<b-row class="match-height">
<b-col
xl="4"
lg="4"
md="12"
>
<staking-commission-component :data="validator.commission" />
</b-col>
<b-col
xl="4"
lg="4"
md="12"
>
<staking-reward-component :data="distribution" />
</b-col>
<b-col
xl="4"
lg="4"
md="12"
>
<staking-address-component
:hex-address="hexAddress"
:operator-address="validator.operator_address"
:consensus-pubkey="validator.consensus_pubkey"
:account-address="accountAddress"
/>
</b-col>
</b-row>
<b-row>
<b-col>
<b-card title="Uptime">
<b-card-body>
<b-button
v-for="(b,i) in blocks"
:key="i"
v-b-tooltip.hover.v-second
:variant="b[0]?'primary':'outline-danger'"
:title="b"
rounded
size="sm"
class="btn-icon mb-25 mr-25"
> &nbsp; </b-button>
</b-card-body>
</b-card>
</b-col>
</b-row>
</template>
</div>
</template>
<script>
import {
BCard, BButton, BAvatar, BRow, BCol, BCardBody, BCardFooter, VBTooltip,
} from 'bootstrap-vue'
import {
percent, formatToken, StakingParameters, Validator, operatorAddressToAccount, consensusPubkeyToHexAddress, toDay,
} from '@/libs/data'
import { keybase } from '@/libs/fetch'
import StakingAddressComponent from './StakingAddressComponent.vue'
import StakingCommissionComponent from './StakingCommissionComponent.vue'
import StakingRewardComponent from './StakingRewardComponent.vue'
export default {
components: {
BCard,
BButton,
BRow,
BCol,
BAvatar,
BCardBody,
BCardFooter,
StakingAddressComponent,
StakingCommissionComponent,
StakingRewardComponent,
},
directives: {
'b-tooltip': VBTooltip,
},
data() {
return {
commission: {
series: [90],
completed: 89,
inProgress: 64,
},
selfDelegation: {
balance: {},
},
latestHeight: 0,
accountAddress: '-',
hexAddress: '-',
stakingPool: {},
mintInflation: 0,
stakingParameter: new StakingParameters(),
validator: new Validator(),
userData: {},
blocks: Array.from('0'.repeat(100)).map(x => [Boolean(x), Number(x)]),
distribution: {},
}
},
created() {
this.$http.getStakingPool().then(res => { this.stakingPool = res })
this.$http.getStakingParameters().then(res => { this.stakingParameter = res })
this.$http.getMintingInflation().then(res => { this.mintInflation = res })
const { address } = this.$route.params
this.$http.getValidatorDistribution(address).then(res => { this.distribution = res })
this.$http.getStakingValidator(address).then(data => {
this.validator = data
this.processAddress(data.operator_address, data.consensus_pubkey)
const { identity } = data.description
keybase(identity).then(d => {
if (Array.isArray(d.them) && d.them.length > 0) {
this.$set(this.validator, 'avatar', d.them[0].pictures.primary.url)
this.$store.commit('cacheAvatar', { identity, url: d.them[0].pictures.primary.url })
}
})
})
this.initBlocks()
},
beforeDestroy() {
console.log('destroying')
clearInterval(this.timer)
},
methods: {
timeFormat(value) {
return toDay(value)
},
percentFormat(value) {
return percent(value)
},
processAddress(operAddress, consensusPubkey) {
this.accountAddress = operatorAddressToAccount(operAddress)
this.hexAddress = consensusPubkeyToHexAddress(consensusPubkey)
this.$http.getStakingDelegatorDelegation(this.accountAddress, operAddress).then(d => {
console.log(d)
this.selfDelegation = d
})
},
tokenFormatter(token) {
return formatToken({ amount: token, denom: this.stakingParameter.bond_denom })
},
apr(rate) {
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) {
return this.$http.getBlockByHeight(item[1]).then(res => {
if (item[1] !== lastHeight) {
const sigs = res.block.last_commit.signatures.find(s => s.validator_address === this.hexAddress)
const block = this.blocks.find(b => b[1] === item[1])
if (typeof block !== 'undefined') {
this.$set(block, 0, typeof sigs !== 'undefined')
}
}
})
},
fetch_latest() {
this.$http.getLatestBlock().then(res => {
console.log('fetched: ', res.block.last_commit.height)
const sigs = res.block.last_commit.signatures.find(s => s.validator_address === this.hexAddress)
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 > 999) this.blocks.shift()
this.blocks.push([typeof sigs !== 'undefined', res.block.last_commit.height])
}
})
},
},
}
</script>
<style></style>