forked from cerc-io/cosmos-explorer
feat: style uptime
This commit is contained in:
parent
26e2563c99
commit
393b2da55b
2
components.d.ts
vendored
2
components.d.ts
vendored
@ -7,7 +7,6 @@ export {}
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
export interface GlobalComponents {
|
||||
AddAuthenticatorAppDialog: typeof import('./src/plugins/vuetify/@core/components/AddAuthenticatorAppDialog.vue')['default']
|
||||
AddEditAddressDialog: typeof import('./src/plugins/vuetify/@core/components/AddEditAddressDialog.vue')['default']
|
||||
AppBarSearch: typeof import('./src/plugins/vuetify/@core/components/AppBarSearch.vue')['default']
|
||||
AppCardActions: typeof import('./src/plugins/vuetify/@core/components/AppCardActions.vue')['default']
|
||||
@ -43,7 +42,6 @@ declare module '@vue/runtime-core' {
|
||||
Shortcuts: typeof import('./src/plugins/vuetify/@core/components/Shortcuts.vue')['default']
|
||||
TheCustomizer: typeof import('./src/plugins/vuetify/@core/components/TheCustomizer.vue')['default']
|
||||
ThemeSwitcher: typeof import('./src/plugins/vuetify/@core/components/ThemeSwitcher.vue')['default']
|
||||
TwoFactorAuthDialog: typeof import('./src/plugins/vuetify/@core/components/TwoFactorAuthDialog.vue')['default']
|
||||
UserInfoEditDialog: typeof import('./src/plugins/vuetify/@core/components/UserInfoEditDialog.vue')['default']
|
||||
UserUpgradePlanDialog: typeof import('./src/plugins/vuetify/@core/components/UserUpgradePlanDialog.vue')['default']
|
||||
}
|
||||
|
@ -1,7 +1,4 @@
|
||||
<script lang="ts" setup>
|
||||
import TwoFactorAuthDialog from '@/plugins/vuetify/@core/components/TwoFactorAuthDialog.vue';
|
||||
import { PageRequest } from '@/types';
|
||||
import type { PropType } from 'vue';
|
||||
import { computed, ref } from 'vue';
|
||||
|
||||
const props = defineProps({
|
||||
@ -12,19 +9,19 @@ const props = defineProps({
|
||||
const current = ref(1)
|
||||
const showSize = 3
|
||||
const pages = computed(() => {
|
||||
const pages: {color: string, page: number}[] = []
|
||||
if(props.total && props.limit && props.total > props.limit) {
|
||||
const pages: { color: string, page: number }[] = []
|
||||
if (props.total && props.limit && props.total > props.limit) {
|
||||
let page = 0
|
||||
while(true) {
|
||||
if( page * props.limit > props.total ) break
|
||||
while (true) {
|
||||
if (page * props.limit > props.total) break
|
||||
page += 1
|
||||
if( props.total / props.limit > 10 && page > showSize && page < (props.total / props.limit - showSize + 1)) {
|
||||
if(!(page >= current.value - 1 && page <= current.value + 1)){
|
||||
if (props.total / props.limit > 10 && page > showSize && page < (props.total / props.limit - showSize + 1)) {
|
||||
if (!(page >= current.value - 1 && page <= current.value + 1)) {
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
pages.push({
|
||||
color: page === current.value? 'btn-primary': '',
|
||||
color: page === current.value ? 'btn-primary' : '',
|
||||
page: page,
|
||||
})
|
||||
}
|
||||
@ -41,7 +38,8 @@ function gotoPage(pageNum: number) {
|
||||
<template>
|
||||
<div class="my-5 text-center">
|
||||
<div v-if="total && limit" class="btn-group">
|
||||
<button v-for="{page, color} in pages" class="btn btn-md" :class="color" @click="gotoPage(page)">{{ page }}</button>
|
||||
<button v-for="{ page, color } in pages" class="btn btn-md" :class="color" @click="gotoPage(page)">{{ page
|
||||
}}</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,10 +1,9 @@
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
useBlockchain,
|
||||
useFormatter,
|
||||
useStakingStore,
|
||||
useWalletStore,
|
||||
useTxDialog
|
||||
useBlockchain,
|
||||
useFormatter,
|
||||
useStakingStore,
|
||||
useTxDialog
|
||||
} from '@/stores';
|
||||
import { select } from '@/components/dynamic/index';
|
||||
import type { PaginatedProposals } from '@/types';
|
||||
@ -13,229 +12,166 @@ import type { PropType } from 'vue';
|
||||
import { ref } from 'vue';
|
||||
const dialog = useTxDialog();
|
||||
defineProps({
|
||||
proposals: { type: Object as PropType<PaginatedProposals> },
|
||||
proposals: { type: Object as PropType<PaginatedProposals> },
|
||||
});
|
||||
|
||||
const format = useFormatter();
|
||||
const staking = useStakingStore();
|
||||
const chain = useBlockchain();
|
||||
function showType(v: string) {
|
||||
if (v) {
|
||||
return v.substring(v.lastIndexOf('.') + 1);
|
||||
}
|
||||
return v;
|
||||
if (v) {
|
||||
return v.substring(v.lastIndexOf('.') + 1);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
const statusMap: Record<string, string> = {
|
||||
PROPOSAL_STATUS_VOTING_PERIOD: 'VOTING',
|
||||
PROPOSAL_STATUS_PASSED: 'PASSED',
|
||||
PROPOSAL_STATUS_REJECTED: 'REJECTED',
|
||||
PROPOSAL_STATUS_VOTING_PERIOD: 'VOTING',
|
||||
PROPOSAL_STATUS_PASSED: 'PASSED',
|
||||
PROPOSAL_STATUS_REJECTED: 'REJECTED',
|
||||
};
|
||||
const voterStatusMap: Record<string, string> = {
|
||||
No_With_Veto: '',
|
||||
VOTE_OPTION_YES: 'success',
|
||||
VOTE_OPTION_NO: 'error',
|
||||
VOTE_OPTION_ABSTAIN: 'warning',
|
||||
No_With_Veto: '',
|
||||
VOTE_OPTION_YES: 'success',
|
||||
VOTE_OPTION_NO: 'error',
|
||||
VOTE_OPTION_ABSTAIN: 'warning',
|
||||
};
|
||||
|
||||
const proposalInfo = ref();
|
||||
</script>
|
||||
<template>
|
||||
<div class="bg-white dark:bg-[#28334e] rounded text-sm">
|
||||
<table class="table-compact w-full table-fixed hidden lg:!table">
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in proposals?.proposals" :key="index">
|
||||
<td class="px-4 w-20">
|
||||
<label
|
||||
for="proposal-detail-modal"
|
||||
class="text-main text-base hover:text-indigo-400 cursor-pointer"
|
||||
@click="proposalInfo = item"
|
||||
>
|
||||
#{{ item?.proposal_id }}</label
|
||||
>
|
||||
</td>
|
||||
<td class="w-full">
|
||||
<div>
|
||||
<RouterLink
|
||||
:to="`/${chain.chainName}/gov/${item?.proposal_id}`"
|
||||
class="text-main text-base mb-1 block hover:text-indigo-400 truncate"
|
||||
>
|
||||
{{ item?.content?.title }}
|
||||
</RouterLink>
|
||||
<div
|
||||
class="bg-[#f6f2ff] text-[#9c6cff] dark:bg-gray-600 dark:text-gray-300 inline-block rounded-full px-2 py-[1px] text-xs mb-1"
|
||||
>
|
||||
{{ showType(item.content['@type']) }}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="w-60">
|
||||
<ProposalProcess
|
||||
:pool="staking.pool"
|
||||
:tally="item.final_tally_result"
|
||||
></ProposalProcess>
|
||||
</td>
|
||||
<td class="w-36">
|
||||
<div class="pl-4">
|
||||
<div
|
||||
class="flex items-center"
|
||||
:class="
|
||||
statusMap?.[item?.status] === 'PASSED'
|
||||
? 'text-yes'
|
||||
: statusMap?.[item?.status] === 'REJECTED'
|
||||
? 'text-no'
|
||||
: 'text-info'
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="w-1 h-1 rounded-full mr-2"
|
||||
:class="
|
||||
statusMap?.[item?.status] === 'PASSED'
|
||||
? 'bg-yes'
|
||||
: statusMap?.[item?.status] === 'REJECTED'
|
||||
? 'bg-no'
|
||||
: 'bg-info'
|
||||
"
|
||||
></div>
|
||||
<div class="text-xs">
|
||||
{{ statusMap?.[item?.status] || item?.status }}
|
||||
<div class="bg-white dark:bg-[#28334e] rounded text-sm">
|
||||
<table class="table-compact w-full table-fixed hidden lg:!table">
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in proposals?.proposals" :key="index">
|
||||
<td class="px-4 w-20">
|
||||
<label for="proposal-detail-modal" class="text-main text-base hover:text-indigo-400 cursor-pointer"
|
||||
@click="proposalInfo = item">
|
||||
#{{ item?.proposal_id }}</label>
|
||||
</td>
|
||||
<td class="w-full">
|
||||
<div>
|
||||
<RouterLink :to="`/${chain.chainName}/gov/${item?.proposal_id}`"
|
||||
class="text-main text-base mb-1 block hover:text-indigo-400 truncate">
|
||||
{{ item?.content?.title }}
|
||||
</RouterLink>
|
||||
<div
|
||||
class="bg-[#f6f2ff] text-[#9c6cff] dark:bg-gray-600 dark:text-gray-300 inline-block rounded-full px-2 py-[1px] text-xs mb-1">
|
||||
{{ showType(item.content['@type']) }}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
<td class="w-60">
|
||||
<ProposalProcess :pool="staking.pool" :tally="item.final_tally_result"></ProposalProcess>
|
||||
</td>
|
||||
<td class="w-36">
|
||||
<div class="pl-4">
|
||||
<div class="flex items-center" :class="statusMap?.[item?.status] === 'PASSED'
|
||||
? 'text-yes'
|
||||
: statusMap?.[item?.status] === 'REJECTED'
|
||||
? 'text-no'
|
||||
: 'text-info'
|
||||
">
|
||||
<div class="w-1 h-1 rounded-full mr-2" :class="statusMap?.[item?.status] === 'PASSED'
|
||||
? 'bg-yes'
|
||||
: statusMap?.[item?.status] === 'REJECTED'
|
||||
? 'bg-no'
|
||||
: 'bg-info'
|
||||
"></div>
|
||||
<div class="text-xs">
|
||||
{{ statusMap?.[item?.status] || item?.status }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="truncate col-span-2 md:!col-span-1 text-xs text-gray-500 dark:text-gray-400 text-right md:!flex md:!justify-start">
|
||||
{{ format.toDay(item.voting_end_time, 'from') }}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td v-if="statusMap?.[item?.status] === 'VOTING'" class="w-40">
|
||||
<div class="">
|
||||
<label for="vote" class="btn btn-xs btn-primary rounded"
|
||||
@click="dialog.open('vote', { proposal_id: item?.proposal_id })">
|
||||
<span v-if="item?.voterStatus">{{ item?.voterStatus.replace("VOTE_OPTION_", "") }}</span>
|
||||
<span v-else>Vote</span>
|
||||
</label>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="lg:!hidden">
|
||||
<div v-for="(item, index) in proposals?.proposals" :key="index" class="px-4 py-4">
|
||||
<div class="text-main text-base mb-1 flex justify-between hover:text-indigo-400">
|
||||
<RouterLink :to="`/${chain.chainName}/gov/${item?.proposal_id}`" class="flex-1 w-0 truncate mr-4">{{
|
||||
item?.content?.title }}</RouterLink>
|
||||
<label for="proposal-detail-modal" class="text-main text-base hover:text-indigo-400 cursor-pointer"
|
||||
@click="proposalInfo = item">
|
||||
#{{ item?.proposal_id }}</label>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="truncate col-span-2 md:!col-span-1 text-xs text-gray-500 dark:text-gray-400 text-right md:!flex md:!justify-start"
|
||||
>
|
||||
{{ format.toDay(item.voting_end_time, 'from') }}
|
||||
</div>
|
||||
</div>
|
||||
</td>
|
||||
|
||||
<td v-if="statusMap?.[item?.status] === 'VOTING'" class="w-40">
|
||||
<div class="">
|
||||
<label
|
||||
for="vote"
|
||||
class="btn btn-xs btn-primary rounded-sm"
|
||||
@click="dialog.open('vote', { proposal_id: item?.proposal_id })"
|
||||
>
|
||||
<span v-if="item?.voterStatus">{{ item?.voterStatus.replace("VOTE_OPTION_", "")}}</span>
|
||||
<span v-else>Vote</span>
|
||||
</label
|
||||
>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="grid grid-cols-4 mt-2 mb-2">
|
||||
<div class="col-span-2">
|
||||
<div
|
||||
class="bg-[#f6f2ff] text-[#9c6cff] dark:bg-gray-600 dark:text-gray-300 inline-block rounded-full px-2 py-[1px] text-xs mb-1">
|
||||
{{ showType(item.content['@type']) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex items-center" :class="statusMap?.[item?.status] === 'PASSED'
|
||||
? 'text-yes'
|
||||
: statusMap?.[item?.status] === 'REJECTED'
|
||||
? 'text-no'
|
||||
: 'text-info'
|
||||
">
|
||||
<div class="w-1 h-1 rounded-full mr-2" :class="statusMap?.[item?.status] === 'PASSED'
|
||||
? 'bg-yes'
|
||||
: statusMap?.[item?.status] === 'REJECTED'
|
||||
? 'bg-no'
|
||||
: 'bg-info'
|
||||
"></div>
|
||||
<div class="text-xs flex items-center">
|
||||
{{ statusMap?.[item?.status] || item?.status }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="truncate text-xs text-gray-500 dark:text-gray-400 flex items-center justify-end">
|
||||
{{ format.toDay(item.voting_end_time, 'from') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="lg:!hidden">
|
||||
<div
|
||||
v-for="(item, index) in proposals?.proposals"
|
||||
:key="index"
|
||||
class="px-4 py-4"
|
||||
>
|
||||
<div
|
||||
class="text-main text-base mb-1 flex justify-between hover:text-indigo-400"
|
||||
>
|
||||
<RouterLink
|
||||
:to="`/${chain.chainName}/gov/${item?.proposal_id}`"
|
||||
class="flex-1 w-0 truncate mr-4"
|
||||
>{{ item?.content?.title }}</RouterLink
|
||||
>
|
||||
<label
|
||||
for="proposal-detail-modal"
|
||||
class="text-main text-base hover:text-indigo-400 cursor-pointer"
|
||||
@click="proposalInfo = item"
|
||||
>
|
||||
#{{ item?.proposal_id }}</label
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<ProposalProcess :pool="staking.pool" :tally="item.final_tally_result"></ProposalProcess>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-4 mt-2 mb-2">
|
||||
<div class="col-span-2">
|
||||
<div
|
||||
class="bg-[#f6f2ff] text-[#9c6cff] dark:bg-gray-600 dark:text-gray-300 inline-block rounded-full px-2 py-[1px] text-xs mb-1"
|
||||
>
|
||||
{{ showType(item.content['@type']) }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="flex items-center"
|
||||
:class="
|
||||
statusMap?.[item?.status] === 'PASSED'
|
||||
? 'text-yes'
|
||||
: statusMap?.[item?.status] === 'REJECTED'
|
||||
? 'text-no'
|
||||
: 'text-info'
|
||||
"
|
||||
>
|
||||
<div
|
||||
class="w-1 h-1 rounded-full mr-2"
|
||||
:class="
|
||||
statusMap?.[item?.status] === 'PASSED'
|
||||
? 'bg-yes'
|
||||
: statusMap?.[item?.status] === 'REJECTED'
|
||||
? 'bg-no'
|
||||
: 'bg-info'
|
||||
"
|
||||
></div>
|
||||
<div class="text-xs flex items-center">
|
||||
{{ statusMap?.[item?.status] || item?.status }}
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="truncate text-xs text-gray-500 dark:text-gray-400 flex items-center justify-end"
|
||||
>
|
||||
{{ format.toDay(item.voting_end_time, 'from') }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<ProposalProcess
|
||||
:pool="staking.pool"
|
||||
:tally="item.final_tally_result"
|
||||
></ProposalProcess>
|
||||
</div>
|
||||
|
||||
<div class="mt-4" v-if="statusMap?.[item?.status] === 'VOTING'">
|
||||
<div class="" v-show="item?.voterStatus === 'No With Veto'">
|
||||
<label
|
||||
for="vote"
|
||||
class="btn btn-xs btn-primary rounded-sm"
|
||||
@click="dialog.open('vote', { proposal_id: item?.proposal_id })"
|
||||
>Vote</label
|
||||
>
|
||||
<div
|
||||
class="text-xs truncate relative py-1 px-3 rounded-full w-fit"
|
||||
:class="`text-${voterStatusMap?.[item?.voterStatus]}`"
|
||||
v-show="item?.voterStatus !== 'No With Veto'"
|
||||
>
|
||||
<span
|
||||
class="inset-x-0 inset-y-0 opacity-10 absolute"
|
||||
:class="`bg-${voterStatusMap?.[item?.voterStatus]}`"
|
||||
></span>
|
||||
{{ item?.voterStatus }}
|
||||
</div>
|
||||
<div class="mt-4" v-if="statusMap?.[item?.status] === 'VOTING'">
|
||||
<div class="" v-show="item?.voterStatus === 'No With Veto'">
|
||||
<label for="vote" class="btn btn-xs btn-primary rounded"
|
||||
@click="dialog.open('vote', { proposal_id: item?.proposal_id })">Vote</label>
|
||||
<div class="text-xs truncate relative py-1 px-3 rounded-full w-fit"
|
||||
:class="`text-${voterStatusMap?.[item?.voterStatus]}`"
|
||||
v-show="item?.voterStatus !== 'No With Veto'">
|
||||
<span class="inset-x-0 inset-y-0 opacity-10 absolute"
|
||||
:class="`bg-${voterStatusMap?.[item?.voterStatus]}`"></span>
|
||||
{{ item?.voterStatus }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<input type="checkbox" id="proposal-detail-modal" class="modal-toggle" />
|
||||
<label for="proposal-detail-modal" class="modal">
|
||||
<label class="modal-box w-11/12 max-w-5xl" for="">
|
||||
<label for="proposal-detail-modal" class="btn btn-sm btn-circle absolute right-2 top-2">✕</label>
|
||||
<h3 class="font-bold text-lg">Description</h3>
|
||||
<p class="py-4">
|
||||
<Component v-if="proposalInfo?.content?.description"
|
||||
:is="select(proposalInfo?.content?.description, 'horizontal')"
|
||||
:value="proposalInfo?.content?.description">
|
||||
</Component>
|
||||
</p>
|
||||
</label>
|
||||
</label>
|
||||
</div>
|
||||
|
||||
<input type="checkbox" id="proposal-detail-modal" class="modal-toggle" />
|
||||
<label for="proposal-detail-modal" class="modal">
|
||||
<label class="modal-box w-11/12 max-w-5xl" for="">
|
||||
<label
|
||||
for="proposal-detail-modal"
|
||||
class="btn btn-sm btn-circle absolute right-2 top-2"
|
||||
>✕</label
|
||||
>
|
||||
<h3 class="font-bold text-lg">Description</h3>
|
||||
<p class="py-4">
|
||||
<Component
|
||||
v-if="proposalInfo?.content?.description"
|
||||
:is="select(proposalInfo?.content?.description, 'horizontal')"
|
||||
:value="proposalInfo?.content?.description"
|
||||
></Component>
|
||||
</p>
|
||||
</label>
|
||||
</label>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -1,162 +1,143 @@
|
||||
<script setup lang="ts">
|
||||
import ApexCharts from 'vue3-apexcharts';
|
||||
import { useTheme } from 'vuetify';
|
||||
import { hexToRgb } from '@/plugins/vuetify/@layouts/utils';
|
||||
import { computed, type PropType } from 'vue';
|
||||
import { useFormatter } from '@/stores';
|
||||
import type { CommissionRate } from '@/types';
|
||||
|
||||
const props = defineProps({
|
||||
commission: { type: Object as PropType<CommissionRate> },
|
||||
commission: { type: Object as PropType<CommissionRate> },
|
||||
});
|
||||
|
||||
let rate = computed(
|
||||
() => Number(props.commission?.commission_rates.rate || 0) * 100
|
||||
() => Number(props.commission?.commission_rates.rate || 0) * 100
|
||||
);
|
||||
let change = computed(
|
||||
() => Number(props.commission?.commission_rates.max_change_rate || 0) * 100
|
||||
() => Number(props.commission?.commission_rates.max_change_rate || 0) * 100
|
||||
);
|
||||
let max = computed(
|
||||
() => Number(props.commission?.commission_rates.max_rate || 1) * 100
|
||||
() => Number(props.commission?.commission_rates.max_rate || 1) * 100
|
||||
);
|
||||
|
||||
// const rate = 15 // props.commision?.commissionRates.rate
|
||||
// const change = 15
|
||||
// const max = 20
|
||||
|
||||
const left = rate;
|
||||
const right = computed(() => max.value - rate.value);
|
||||
|
||||
const s1 = computed(() =>
|
||||
left.value > change.value ? left.value - change.value : 0
|
||||
left.value > change.value ? left.value - change.value : 0
|
||||
);
|
||||
const s2 = computed(() =>
|
||||
left.value > change.value ? change.value : left.value
|
||||
left.value > change.value ? change.value : left.value
|
||||
);
|
||||
const s3 = 2;
|
||||
const s4 = computed(() =>
|
||||
right.value > change.value ? change.value : right.value
|
||||
right.value > change.value ? change.value : right.value
|
||||
);
|
||||
const s5 = computed(() =>
|
||||
right.value > change.value ? right.value - change.value : 0
|
||||
right.value > change.value ? right.value - change.value : 0
|
||||
);
|
||||
|
||||
const series = computed(() => [s1.value, s2.value, s3, s4.value, s5.value]);
|
||||
|
||||
const vuetifyTheme = useTheme();
|
||||
const format = useFormatter();
|
||||
|
||||
const chartConfig = computed(() => {
|
||||
const themeColors = vuetifyTheme.current.value.colors;
|
||||
const variableTheme = vuetifyTheme.current.value.variables;
|
||||
|
||||
const secondaryText = `rgba(${hexToRgb(
|
||||
String(themeColors['on-background'])
|
||||
)},${variableTheme['medium-emphasis-opacity']})`;
|
||||
const primaryText = `rgba(${hexToRgb(String(themeColors['on-background']))},${
|
||||
variableTheme['high-emphasis-opacity']
|
||||
})`;
|
||||
const secondaryText = `hsl(var(--bc))`;
|
||||
const primaryText = `hsl(var(--bc))`;
|
||||
|
||||
return {
|
||||
chart: {
|
||||
sparkline: { enabled: false },
|
||||
},
|
||||
colors: [
|
||||
`rgba(${hexToRgb(String(themeColors.secondary))},0.2)`,
|
||||
`rgba(${hexToRgb(String(themeColors.success))},0.2)`,
|
||||
`rgba(${hexToRgb(String(themeColors.success))},1)`,
|
||||
`rgba(${hexToRgb(String(themeColors.success))},0.2)`,
|
||||
`rgba(${hexToRgb(String(themeColors.secondary))},0.2)`,
|
||||
],
|
||||
legend: { show: false },
|
||||
tooltip: { enabled: false },
|
||||
dataLabels: { enabled: false },
|
||||
stroke: {
|
||||
width: 3,
|
||||
lineCap: 'round',
|
||||
colors: ['rgba(var(--v-theme-surface), 1)'],
|
||||
},
|
||||
labels: [
|
||||
'Available',
|
||||
'Daily Change',
|
||||
'Commission Rate',
|
||||
'Daily Change',
|
||||
'Available',
|
||||
],
|
||||
states: {
|
||||
hover: {
|
||||
filter: { type: 'none' },
|
||||
},
|
||||
active: {
|
||||
filter: { type: 'none' },
|
||||
},
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
endAngle: 130,
|
||||
startAngle: -130,
|
||||
customScale: 0.9,
|
||||
donut: {
|
||||
size: '83%',
|
||||
labels: {
|
||||
show: true,
|
||||
name: {
|
||||
offsetY: 25,
|
||||
fontSize: '1rem',
|
||||
color: secondaryText,
|
||||
},
|
||||
value: {
|
||||
offsetY: -15,
|
||||
fontWeight: 500,
|
||||
fontSize: '2.125rem',
|
||||
formatter: (value: unknown) => `${rate.value}%`,
|
||||
color: primaryText,
|
||||
},
|
||||
total: {
|
||||
show: true,
|
||||
label: 'Commission Rate',
|
||||
fontSize: '1rem',
|
||||
color: secondaryText,
|
||||
formatter: () => `${rate.value}%`,
|
||||
},
|
||||
},
|
||||
return {
|
||||
chart: {
|
||||
sparkline: { enabled: false },
|
||||
},
|
||||
},
|
||||
},
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 1709,
|
||||
options: {
|
||||
chart: { height: 237 },
|
||||
colors: ['rgba(109,120,141,0.2)', 'rgba(114,225,40,0.2)', 'rgba(114,225,40,1)', 'rgba(114,225,40,0.2)', 'rgba(109,120,141,0.2)'],
|
||||
legend: { show: false },
|
||||
tooltip: { enabled: false },
|
||||
dataLabels: { enabled: false },
|
||||
stroke: {
|
||||
width: 3,
|
||||
lineCap: 'round',
|
||||
colors: ['hsl(var(--b1))'],
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
labels: [
|
||||
'Available',
|
||||
'Daily Change',
|
||||
'Commission Rate',
|
||||
'Daily Change',
|
||||
'Available',
|
||||
],
|
||||
states: {
|
||||
hover: {
|
||||
filter: { type: 'none' },
|
||||
},
|
||||
active: {
|
||||
filter: { type: 'none' },
|
||||
},
|
||||
},
|
||||
plotOptions: {
|
||||
pie: {
|
||||
endAngle: 130,
|
||||
startAngle: -130,
|
||||
customScale: 0.9,
|
||||
donut: {
|
||||
size: '83%',
|
||||
labels: {
|
||||
show: true,
|
||||
name: {
|
||||
offsetY: 25,
|
||||
fontSize: '1rem',
|
||||
color: secondaryText,
|
||||
},
|
||||
value: {
|
||||
offsetY: -15,
|
||||
fontWeight: 500,
|
||||
fontSize: '2.125rem',
|
||||
formatter: (value: unknown) => `${rate.value}%`,
|
||||
color: primaryText,
|
||||
},
|
||||
total: {
|
||||
show: true,
|
||||
label: 'Commission Rate',
|
||||
fontSize: '1rem',
|
||||
color: secondaryText,
|
||||
formatter: () => `${rate.value}%`,
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
responsive: [
|
||||
{
|
||||
breakpoint: 1709,
|
||||
options: {
|
||||
chart: { height: 237 },
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="bg-base-100 rounded shadow p-4">
|
||||
<div class="text-lg text-main font-semibold mb-1">Commission Rate</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ `Updated at ${format.toDay(props.commission?.update_time, 'short')}` }}
|
||||
<div class="bg-base-100 rounded shadow p-4">
|
||||
<div class="text-lg text-main font-semibold mb-1">Commission Rate</div>
|
||||
<div class="text-sm text-gray-500 dark:text-gray-400">
|
||||
{{ `Updated at ${format.toDay(props.commission?.update_time, 'short')}` }}
|
||||
</div>
|
||||
<ApexCharts type="donut" :options="chartConfig" :series="series" />
|
||||
<div>
|
||||
<div class="flex items-center justify-center flex-wrap gap-x-3">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="bg-success w-[6px] h-[6px] rounded-full"></div>
|
||||
<span class="text-caption">Rate:{{ rate }}%</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="bg-success w-[6px] h-[6px] rounded-full opacity-60"></div>
|
||||
<span class="text-caption">24h: ±{{ change }}%</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="bg-secondary w-[6px] h-[6px] rounded-full"></div>
|
||||
<span class="text-caption">Max:{{ max }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<ApexCharts type="donut" :options="chartConfig" :series="series" />
|
||||
<div>
|
||||
<div class="flex items-center justify-center flex-wrap gap-x-3">
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="bg-success w-[6px] h-[6px] rounded-full"></div>
|
||||
<span class="text-caption">Rate:{{ rate }}%</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="bg-success w-[6px] h-[6px] rounded-full opacity-60"></div>
|
||||
<span class="text-caption">24h: ±{{ change }}%</span>
|
||||
</div>
|
||||
<div class="flex items-center gap-x-2">
|
||||
<div class="bg-secondary w-[6px] h-[6px] rounded-full"></div>
|
||||
<span class="text-caption">Max:{{ max }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -10,93 +10,82 @@ const base = useBaseStore()
|
||||
const format = useFormatter();
|
||||
|
||||
const list = computed(() => {
|
||||
// const recents = base.recents
|
||||
// return recents.sort((a, b) => (Number(b.block.header.height) - Number(a.block.header.height)))
|
||||
return base.recents
|
||||
// const recents = base.recents
|
||||
// return recents.sort((a, b) => (Number(b.block.header.height) - Number(a.block.header.height)))
|
||||
return base.recents
|
||||
})
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="tabs tabs-boxed bg-transparent mb-4">
|
||||
<a
|
||||
class="tab text-gray-400 uppercase"
|
||||
:class="{ 'tab-active': tab === 'blocks' }"
|
||||
@click="tab = 'blocks'"
|
||||
>Blocks</a
|
||||
>
|
||||
<a
|
||||
class="tab text-gray-400 uppercase"
|
||||
:class="{ 'tab-active': tab === 'transactions' }"
|
||||
@click="tab = 'transactions'"
|
||||
>Transactions</a
|
||||
>
|
||||
</div>
|
||||
<div>
|
||||
<div class="tabs tabs-boxed bg-transparent mb-4">
|
||||
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'blocks' }"
|
||||
@click="tab = 'blocks'">Blocks</a>
|
||||
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === 'transactions' }"
|
||||
@click="tab = 'transactions'">Transactions</a>
|
||||
</div>
|
||||
|
||||
<div v-show="tab === 'blocks'" class="grid xl:!grid-cols-6 md:!grid-cols-4 grid-cols-1">
|
||||
|
||||
<RouterLink
|
||||
v-for="item in list"
|
||||
class="flex flex-col justify-between rounded border border-gray-100 m-2 p-4 shadow-xl bg-base-100"
|
||||
:to="`/${chain}/block/${item.block.header.height}`"
|
||||
>
|
||||
<div class="flex justify-between">
|
||||
<h3 class="text-md font-bold sm:!text-lg">
|
||||
{{ item.block.header.height }}
|
||||
</h3>
|
||||
<span class="rounded text-xs whitespace-nowrap font-medium text-green-600">
|
||||
{{ format.toDay(item.block?.header?.time, 'from') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between tooltip" data-tip="Block Proposor">
|
||||
<div class="mt-2 hidden text-sm sm:!block truncate">
|
||||
<span>{{ format.validator(item.block?.header?.proposer_address) }}</span>
|
||||
</div>
|
||||
<span class="text-right mt-1 whitespace-nowrap"> {{ item.block?.data?.txs.length }} txs </span>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div v-show="tab === 'blocks'" class="grid xl:!grid-cols-6 md:!grid-cols-4 grid-cols-1 gap-3">
|
||||
|
||||
<div
|
||||
v-show="tab === 'transactions'"
|
||||
class="bg-base-100 rounded overflow-x-auto"
|
||||
>
|
||||
<table class="table w-full table-compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="position: relative; z-index: 2;">Height</th>
|
||||
<th style="position: relative; z-index: 2;">Hash</th>
|
||||
<th>Messages</th>
|
||||
<th>Fees</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody >
|
||||
<tr v-for="(item,index) in base.txsInRecents" :index="index">
|
||||
<td class="text-sm text-primary">
|
||||
<RouterLink
|
||||
:to="`/${props.chain}/block/${item.height}`"
|
||||
>{{ item.height }}</RouterLink
|
||||
></td>
|
||||
<td class="truncate text-primary" width="50%">
|
||||
<RouterLink :to="`/${props.chain}/tx/${item.hash}`">{{
|
||||
item.hash
|
||||
}}</RouterLink>
|
||||
</td>
|
||||
<td>{{ format.messages(item.tx.body.messages) }}</td>
|
||||
<td>{{ format.formatTokens(item.tx.authInfo.fee?.amount) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="p-4">
|
||||
<div class="alert relative bg-transparent">
|
||||
<div class="alert absolute inset-x-0 inset-y-0 w-full h-full bg-info opacity-10"></div>
|
||||
<div class="text-info">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" class="stroke-current flex-shrink-0 w-6 h-6"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path></svg>
|
||||
<span>Only show txs in recent blocks</span>
|
||||
</div>
|
||||
<RouterLink v-for="item in list"
|
||||
class="flex flex-col justify-between rounded p-4 shadow bg-base-100"
|
||||
:to="`/${chain}/block/${item.block.header.height}`">
|
||||
<div class="flex justify-between">
|
||||
<h3 class="text-md font-bold sm:!text-lg">
|
||||
{{ item.block.header.height }}
|
||||
</h3>
|
||||
<span class="rounded text-xs whitespace-nowrap font-medium text-green-600">
|
||||
{{ format.toDay(item.block?.header?.time, 'from') }}
|
||||
</span>
|
||||
</div>
|
||||
<div class="flex justify-between tooltip" data-tip="Block Proposor">
|
||||
<div class="mt-2 hidden text-sm sm:!block truncate">
|
||||
<span>{{ format.validator(item.block?.header?.proposer_address) }}</span>
|
||||
</div>
|
||||
<span class="text-right mt-1 whitespace-nowrap"> {{ item.block?.data?.txs.length }} txs </span>
|
||||
</div>
|
||||
</RouterLink>
|
||||
</div>
|
||||
|
||||
<div v-show="tab === 'transactions'" class="bg-base-100 rounded overflow-x-auto">
|
||||
<table class="table w-full table-compact">
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="position: relative; z-index: 2;">Height</th>
|
||||
<th style="position: relative; z-index: 2;">Hash</th>
|
||||
<th>Messages</th>
|
||||
<th>Fees</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr v-for="(item, index) in base.txsInRecents" :index="index">
|
||||
<td class="text-sm text-primary">
|
||||
<RouterLink :to="`/${props.chain}/block/${item.height}`">{{ item.height }}</RouterLink>
|
||||
</td>
|
||||
<td class="truncate text-primary" width="50%">
|
||||
<RouterLink :to="`/${props.chain}/tx/${item.hash}`">{{
|
||||
item.hash
|
||||
}}</RouterLink>
|
||||
</td>
|
||||
<td>{{ format.messages(item.tx.body.messages) }}</td>
|
||||
<td>{{ format.formatTokens(item.tx.authInfo.fee?.amount) }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="p-4">
|
||||
<div class="alert relative bg-transparent">
|
||||
<div class="alert absolute inset-x-0 inset-y-0 w-full h-full bg-info opacity-10"></div>
|
||||
<div class="text-info">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24"
|
||||
class="stroke-current flex-shrink-0 w-6 h-6">
|
||||
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
|
||||
d="M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"></path>
|
||||
</svg>
|
||||
<span>Only show txs in recent blocks</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<route>
|
||||
|
@ -10,51 +10,37 @@ const store = useGovStore();
|
||||
const pageRequest = ref(new PageRequest())
|
||||
|
||||
onMounted(() => {
|
||||
store.fetchProposals('2').then((x) => {
|
||||
if (x?.proposals?.length === 0) {
|
||||
tab.value = '3';
|
||||
store.fetchProposals('3');
|
||||
}
|
||||
store.fetchProposals('3');
|
||||
store.fetchProposals('4');
|
||||
});
|
||||
store.fetchProposals('2').then((x) => {
|
||||
if (x?.proposals?.length === 0) {
|
||||
tab.value = '3';
|
||||
store.fetchProposals('3');
|
||||
}
|
||||
store.fetchProposals('3');
|
||||
store.fetchProposals('4');
|
||||
});
|
||||
});
|
||||
|
||||
const changeTab = (val: '2' | '3' | '4') => {
|
||||
tab.value = val;
|
||||
tab.value = val;
|
||||
};
|
||||
|
||||
function page(p: number) {
|
||||
pageRequest.value.setPage(p)
|
||||
store.fetchProposals(tab.value, pageRequest.value)
|
||||
pageRequest.value.setPage(p)
|
||||
store.fetchProposals(tab.value, pageRequest.value)
|
||||
}
|
||||
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div class="tabs tabs-boxed bg-transparent mb-4 text-center">
|
||||
<a
|
||||
class="tab text-gray-400 uppercase"
|
||||
:class="{ 'tab-active': tab === '2' }"
|
||||
@click="changeTab('2')"
|
||||
>Voting</a
|
||||
>
|
||||
<a
|
||||
class="tab text-gray-400 uppercase"
|
||||
:class="{ 'tab-active': tab === '3' }"
|
||||
@click="changeTab('3')"
|
||||
>Passed</a
|
||||
>
|
||||
<a
|
||||
class="tab text-gray-400 uppercase"
|
||||
:class="{ 'tab-active': tab === '4' }"
|
||||
@click="changeTab('4')"
|
||||
>Rejected</a
|
||||
>
|
||||
<div>
|
||||
<div class="tabs tabs-boxed bg-transparent mb-4 text-center">
|
||||
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === '2' }" @click="changeTab('2')">Voting</a>
|
||||
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === '3' }" @click="changeTab('3')">Passed</a>
|
||||
<a class="tab text-gray-400 uppercase" :class="{ 'tab-active': tab === '4' }"
|
||||
@click="changeTab('4')">Rejected</a>
|
||||
</div>
|
||||
<ProposalListItem :proposals="store?.proposals[tab]" />
|
||||
<PaginationBar :total="store?.proposals[tab]?.pagination?.total" :limit="pageRequest.limit" :callback="page" />
|
||||
</div>
|
||||
<ProposalListItem :proposals="store?.proposals[tab]"/>
|
||||
<PaginationBar :total="store?.proposals[tab]?.pagination?.total" :limit="pageRequest.limit" :callback="page"/>
|
||||
</div>
|
||||
</template>
|
||||
<route>
|
||||
{
|
||||
|
@ -9,7 +9,7 @@ import {
|
||||
useTxDialog,
|
||||
useWalletStore,
|
||||
useStakingStore,
|
||||
useParamStore,
|
||||
useParamStore,
|
||||
} from '@/stores';
|
||||
import { onMounted, ref } from 'vue';
|
||||
import { useIndexModule } from './indexStore';
|
||||
@ -99,10 +99,7 @@ const color = computed(() => {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-if="coinInfo && coinInfo.name"
|
||||
class="bg-base-100 rounded shadow mb-4"
|
||||
>
|
||||
<div v-if="coinInfo && coinInfo.name" class="bg-base-100 rounded shadow mb-4">
|
||||
<div class="grid grid-cols-2 md:grid-cols-3 p-4">
|
||||
<div class="col-span-2 md:col-span-1">
|
||||
<div class="text-xl font-semibold text-main">
|
||||
@ -112,37 +109,26 @@ const color = computed(() => {
|
||||
</div>
|
||||
<div class="text-xs mt-2">
|
||||
Rank:
|
||||
<div
|
||||
class="badge text-xs badge-error bg-[#fcebea] dark:bg-[#41384d] text-red-400"
|
||||
>
|
||||
<div class="badge text-xs badge-error bg-[#fcebea] dark:bg-[#41384d] text-red-400">
|
||||
#{{ coinInfo.market_cap_rank }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="my-4 flex flex-wrap items-center">
|
||||
<a
|
||||
v-for="(item, index) of comLinks"
|
||||
:key="index"
|
||||
:href="item.href"
|
||||
class="link link-primary px-2 py-1 rounded-sm no-underline hover:text-primary hover:bg-gray-100 dark:hover:bg-slate-800 flex items-center"
|
||||
>
|
||||
<a v-for="(item, index) of comLinks" :key="index" :href="item.href"
|
||||
class="link link-primary px-2 py-1 rounded-sm no-underline hover:text-primary hover:bg-gray-100 dark:hover:bg-slate-800 flex items-center">
|
||||
<Icon :icon="item?.icon" />
|
||||
<span class="ml-1 text-sm uppercase">{{ item?.name }}</span>
|
||||
</a>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<div
|
||||
class="dropdown dropdown-hover w-full"
|
||||
>
|
||||
<div class="dropdown dropdown-hover w-full">
|
||||
<label>
|
||||
<div
|
||||
class="bg-gray-100 dark:bg-[#384059] flex items-center justify-between px-4 py-2 cursor-pointer rounded"
|
||||
>
|
||||
class="bg-gray-100 dark:bg-[#384059] flex items-center justify-between px-4 py-2 cursor-pointer rounded">
|
||||
<div>
|
||||
<div
|
||||
class="font-semibold text-xl text-[#666] dark:text-white"
|
||||
>
|
||||
<div class="font-semibold text-xl text-[#666] dark:text-white">
|
||||
{{ ticker?.market?.name || '' }}
|
||||
</div>
|
||||
<div class="text-info text-sm">
|
||||
@ -153,9 +139,7 @@ const color = computed(() => {
|
||||
</div>
|
||||
|
||||
<div class="text-right">
|
||||
<div
|
||||
class="text-xl font-semibold text-[#666] dark:text-white"
|
||||
>
|
||||
<div class="text-xl font-semibold text-[#666] dark:text-white">
|
||||
${{ ticker.converted_last.usd }}
|
||||
</div>
|
||||
<div class="text-sm" :class="store.priceColor">
|
||||
@ -165,18 +149,10 @@ const color = computed(() => {
|
||||
</div>
|
||||
</label>
|
||||
<div class="dropdown-content pt-1">
|
||||
<div
|
||||
class="h-64 overflow-auto w-full shadow rounded"
|
||||
>
|
||||
<div class="h-64 overflow-auto w-full shadow rounded">
|
||||
<ul class="menu w-full bg-gray-100 rounded dark:bg-[#384059]">
|
||||
<li
|
||||
v-for="(item, index) in store.coinInfo.tickers"
|
||||
:key="index"
|
||||
@click="store.selectTicker(index)"
|
||||
>
|
||||
<div
|
||||
class="flex items-center justify-between hover:bg-base-100"
|
||||
>
|
||||
<li v-for="(item, index) in store.coinInfo.tickers" :key="index" @click="store.selectTicker(index)">
|
||||
<div class="flex items-center justify-between hover:bg-base-100">
|
||||
<div class="flex-1">
|
||||
<div class="text-main text-sm">
|
||||
{{ item?.market?.name }}
|
||||
@ -198,12 +174,8 @@ const color = computed(() => {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a
|
||||
:color="store.trustColor"
|
||||
class="my-5 !text-white btn !bg-yes !border-yes w-full"
|
||||
:href="ticker.trade_url"
|
||||
target="_blank"
|
||||
>
|
||||
<a :color="store.trustColor" class="my-5 !text-white btn !bg-yes !border-yes w-full" :href="ticker.trade_url"
|
||||
target="_blank">
|
||||
Buy {{ coinInfo.symbol || '' }}
|
||||
</a>
|
||||
</div>
|
||||
@ -215,23 +187,18 @@ const color = computed(() => {
|
||||
</div>
|
||||
<div class="h-[1px] w-full bg-gray-100 dark:bg-[#384059]"></div>
|
||||
<div class="max-h-[250px] overflow-auto p-4 text-sm">
|
||||
<MdEditor
|
||||
:model-value="coinInfo.description?.en"
|
||||
previewOnly
|
||||
></MdEditor>
|
||||
<MdEditor :model-value="coinInfo.description?.en" previewOnly></MdEditor>
|
||||
</div>
|
||||
<div class="mx-4 flex flex-wrap items-center">
|
||||
<div
|
||||
v-for="tag in coinInfo.categories"
|
||||
class="mr-2 mb-4 text-xs bg-gray-100 dark:bg-[#384059] px-3 rounded-full py-1"
|
||||
>
|
||||
<div v-for="tag in coinInfo.categories"
|
||||
class="mr-2 mb-4 text-xs bg-gray-100 dark:bg-[#384059] px-3 rounded-full py-1">
|
||||
{{ tag }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="grid grid-cols-2 gap-4 md:!grid-cols-3 lg:!grid-cols-6">
|
||||
<div v-for="item in store.stats">
|
||||
<div v-for="(item, key) in store.stats" :key="key">
|
||||
<CardStatisticsVertical v-bind="item" />
|
||||
</div>
|
||||
</div>
|
||||
@ -251,16 +218,11 @@ const color = computed(() => {
|
||||
<div class="bg-base-100 rounded mt-4 shadow">
|
||||
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
|
||||
{{ walletStore.currentAddress || 'Not Connected' }}
|
||||
<RouterLink
|
||||
v-if="walletStore.currentAddress"
|
||||
<RouterLink v-if="walletStore.currentAddress"
|
||||
class="float-right text-sm cursor-pointert link link-primary no-underline font-medium"
|
||||
:to="`/${chain}/account/${walletStore.currentAddress}`"
|
||||
>More</RouterLink
|
||||
>
|
||||
:to="`/${chain}/account/${walletStore.currentAddress}`">More</RouterLink>
|
||||
</div>
|
||||
<div
|
||||
class="grid grid-cols-1 md:!grid-cols-4 auto-cols-auto gap-4 px-4 pb-6"
|
||||
>
|
||||
<div class="grid grid-cols-1 md:!grid-cols-4 auto-cols-auto gap-4 px-4 pb-6">
|
||||
<div class="bg-gray-100 dark:bg-[#373f59] rounded-sm px-4 py-3">
|
||||
<div class="text-sm mb-1">Balance</div>
|
||||
<div class="text-lg font-semibold text-main">
|
||||
@ -331,16 +293,12 @@ const color = computed(() => {
|
||||
</td>
|
||||
<td>
|
||||
<div>
|
||||
<label for="delegate"
|
||||
class="btn !btn-xs !btn-primary btn-ghost rounded-sm mr-2"
|
||||
@click="dialog.open('delegate', { validator_address: item.delegation.validator_address})"
|
||||
>
|
||||
<label for="delegate" class="btn !btn-xs !btn-primary btn-ghost rounded-sm mr-2"
|
||||
@click="dialog.open('delegate', { validator_address: item.delegation.validator_address })">
|
||||
Delegate
|
||||
</label>
|
||||
<label for="withdraw"
|
||||
class="btn !btn-xs !btn-primary btn-ghost rounded-sm"
|
||||
@click="dialog.open('withdraw', { validator_address: item.delegation.validator_address})"
|
||||
>
|
||||
<label for="withdraw" class="btn !btn-xs !btn-primary btn-ghost rounded-sm"
|
||||
@click="dialog.open('withdraw', { validator_address: item.delegation.validator_address })">
|
||||
Withdraw Rewards
|
||||
</label>
|
||||
</div>
|
||||
@ -353,15 +311,13 @@ const color = computed(() => {
|
||||
<div class="grid grid-cols-3 gap-4 px-4 pb-6 mt-4">
|
||||
<label for="PingTokenConvert" class="btn btn-primary text-white">Swap</label>
|
||||
<label for="send" class="btn !bg-yes !border-yes text-white" @click="dialog.open('send', {})">Send</label>
|
||||
<label for="delegate" class="btn !bg-info !border-info text-white" @click="dialog.open('delegate', {})">Delegate</label>
|
||||
<label for="delegate" class="btn !bg-info !border-info text-white"
|
||||
@click="dialog.open('delegate', {})">Delegate</label>
|
||||
<RouterLink to="/wallet/receive" class="btn !bg-info !border-info text-white hidden">Receive</RouterLink>
|
||||
</div>
|
||||
<Teleport to="body">
|
||||
<ping-token-convert
|
||||
:chain-name="blockchain?.current?.prettyName"
|
||||
:endpoint="blockchain?.endpoint?.address"
|
||||
:hd-path="walletStore?.connectedWallet?.hdPath"
|
||||
></ping-token-convert>
|
||||
<ping-token-convert :chain-name="blockchain?.current?.prettyName" :endpoint="blockchain?.endpoint?.address"
|
||||
:hd-path="walletStore?.connectedWallet?.hdPath"></ping-token-convert>
|
||||
</Teleport>
|
||||
</div>
|
||||
|
||||
|
@ -145,16 +145,8 @@ onMounted(() => {
|
||||
<div class="avatar mr-4 relative w-24 rounded-lg overflow-hidden">
|
||||
<div class="w-24 rounded-lg absolute opacity-10"></div>
|
||||
<div class="w-24 rounded-lg">
|
||||
<img
|
||||
v-if="avatars[identity] !== 'undefined'"
|
||||
v-lazy="logo(identity)"
|
||||
class="object-contain"
|
||||
/>
|
||||
<Icon
|
||||
v-else
|
||||
class="text-4xl"
|
||||
:icon="`mdi-help-circle-outline`"
|
||||
/>
|
||||
<img v-if="avatars[identity] !== 'undefined'" v-lazy="logo(identity)" class="object-contain" />
|
||||
<Icon v-else class="text-4xl" :icon="`mdi-help-circle-outline`" />
|
||||
</div>
|
||||
</div>
|
||||
<div class="mx-2">
|
||||
@ -162,16 +154,11 @@ onMounted(() => {
|
||||
<div class="text-sm mb-4">
|
||||
{{ v.description?.identity || '-' }}
|
||||
</div>
|
||||
<label
|
||||
for="delegate"
|
||||
class="btn btn-primary btn-sm w-full"
|
||||
@click="
|
||||
dialog.open('delegate', {
|
||||
validator_address: v.operator_address,
|
||||
})
|
||||
"
|
||||
>Delegate</label
|
||||
>
|
||||
<label for="delegate" class="btn btn-primary btn-sm w-full" @click="
|
||||
dialog.open('delegate', {
|
||||
validator_address: v.operator_address,
|
||||
})
|
||||
">Delegate</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="m-4 text-sm">
|
||||
@ -192,8 +179,7 @@ onMounted(() => {
|
||||
<div class="card-list">
|
||||
<div class="flex items-center mb-2">
|
||||
<Icon icon="mdi-shield-account-outline" class="text-xl mr-1" />
|
||||
<span>Status: </span
|
||||
><span>
|
||||
<span>Status: </span><span>
|
||||
{{ String(v.status).replace('BOND_STATUS_', '') }}
|
||||
</span>
|
||||
</div>
|
||||
@ -208,10 +194,7 @@ onMounted(() => {
|
||||
<div class="flex-1">
|
||||
<div class="flex flex-col justify-between">
|
||||
<div class="flex mb-2">
|
||||
<div
|
||||
class="flex items-center justify-center rounded w-10 h-10"
|
||||
style="border: 1px solid #666"
|
||||
>
|
||||
<div class="flex items-center justify-center rounded w-10 h-10" style="border: 1px solid #666">
|
||||
<Icon icon="mdi-coin" class="text-3xl" />
|
||||
</div>
|
||||
<div class="ml-3 flex flex-col justify-center">
|
||||
@ -227,10 +210,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-2">
|
||||
<div
|
||||
class="flex items-center justify-center rounded w-10 h-10"
|
||||
style="border: 1px solid #666"
|
||||
>
|
||||
<div class="flex items-center justify-center rounded w-10 h-10" style="border: 1px solid #666">
|
||||
<Icon icon="mdi-percent" class="text-3xl" />
|
||||
</div>
|
||||
<div class="ml-3 flex flex-col justify-center">
|
||||
@ -242,10 +222,7 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="flex mb-2">
|
||||
<div
|
||||
class="flex items-center justify-center rounded w-10 h-10"
|
||||
style="border: 1px solid #666"
|
||||
>
|
||||
<div class="flex items-center justify-center rounded w-10 h-10" style="border: 1px solid #666">
|
||||
<Icon icon="mdi-account-tie" class="text-3xl" />
|
||||
</div>
|
||||
|
||||
@ -257,10 +234,7 @@ onMounted(() => {
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex mb-2">
|
||||
<div
|
||||
class="flex items-center justify-center rounded w-10 h-10"
|
||||
style="border: 1px solid #666"
|
||||
>
|
||||
<div class="flex items-center justify-center rounded w-10 h-10" style="border: 1px solid #666">
|
||||
<Icon icon="mdi-finance" class="text-3xl" />
|
||||
</div>
|
||||
<div class="ml-3 flex flex-col justify-center">
|
||||
@ -270,10 +244,7 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="flex mb-2">
|
||||
<div
|
||||
class="flex items-center justify-center rounded w-10 h-10"
|
||||
style="border: 1px solid #666"
|
||||
>
|
||||
<div class="flex items-center justify-center rounded w-10 h-10" style="border: 1px solid #666">
|
||||
<Icon icon="mdi-stairs-up" class="text-3xl" />
|
||||
</div>
|
||||
<div class="ml-3 flex flex-col justify-center">
|
||||
@ -283,10 +254,7 @@ onMounted(() => {
|
||||
</div>
|
||||
|
||||
<div class="flex mb-2">
|
||||
<div
|
||||
class="flex items-center justify-center rounded w-10 h-10"
|
||||
style="border: 1px solid #666"
|
||||
>
|
||||
<div class="flex items-center justify-center rounded w-10 h-10" style="border: 1px solid #666">
|
||||
<Icon icon="mdi-clock" class="text-3xl" />
|
||||
</div>
|
||||
<div class="ml-3 flex flex-col justify-center">
|
||||
@ -304,76 +272,56 @@ onMounted(() => {
|
||||
<div class="h-100">
|
||||
<CommissionRate :commission="v.commission"></CommissionRate>
|
||||
</div>
|
||||
<div>
|
||||
<div class="h-100 bg-base-100 rounded shadow">
|
||||
<div class="text-lg font-semibold text-main px-4 pt-4">
|
||||
Commissions & Rewards
|
||||
</div>
|
||||
<div class="px-4 mt-1">
|
||||
<div class="overflow-auto" style="max-height: 280px">
|
||||
<div class="text-sm mb-2">Commissions</div>
|
||||
<div
|
||||
v-for="(i, k) in commission"
|
||||
:key="`reward-${k}`"
|
||||
color="info"
|
||||
label
|
||||
variant="outlined"
|
||||
class="mr-1 mb-1 badge text-xs"
|
||||
>
|
||||
{{ format.formatToken2(i) }}
|
||||
</div>
|
||||
<div class="text-sm mb-2 mt-2">Outstanding Rewards</div>
|
||||
<div
|
||||
v-for="(i, k) in rewards"
|
||||
:key="`reward-${k}`"
|
||||
class="mr-1 mb-1 badge text-xs"
|
||||
>
|
||||
{{ format.formatToken2(i) }}
|
||||
</div>
|
||||
<div class="h-100 bg-base-100 rounded shadow relative">
|
||||
<div class="text-lg font-semibold text-main px-4 pt-4">
|
||||
Commissions & Rewards
|
||||
</div>
|
||||
<div class="px-4 mt-1">
|
||||
<div class="overflow-auto" style="max-height: 280px">
|
||||
<div class="text-sm mb-2">Commissions</div>
|
||||
<div v-for="(i, k) in commission" :key="`reward-${k}`" color="info" label variant="outlined"
|
||||
class="mr-1 mb-1 badge text-xs">
|
||||
{{ format.formatToken2(i) }}
|
||||
</div>
|
||||
<label
|
||||
for="withdraw_commission"
|
||||
class="btn btn-primary mt-4 w-full btn-sm"
|
||||
@click="
|
||||
dialog.open('withdraw_commission', {
|
||||
validator_address: v.operator_address,
|
||||
})
|
||||
"
|
||||
>Withdraw</label
|
||||
>
|
||||
<div class="text-sm mb-2 mt-2">Outstanding Rewards</div>
|
||||
<div v-for="(i, k) in rewards" :key="`reward-${k}`" class="mr-1 mb-1 badge text-xs">
|
||||
{{ format.formatToken2(i) }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="absolute bottom-6 left-0 right-0 px-4">
|
||||
<label for="withdraw_commission" class="btn btn-primary w-full" @click="
|
||||
dialog.open('withdraw_commission', {
|
||||
validator_address: v.operator_address,
|
||||
})
|
||||
">Withdraw</label>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="h-100 bg-base-100 rounded shadow">
|
||||
<div class="px-4 pt-4 mb-2 text-main font-lg font-semibold">
|
||||
Addresses
|
||||
<div class="h-100 bg-base-100 rounded shadow">
|
||||
<div class="px-4 pt-4 mb-2 text-main font-lg font-semibold">
|
||||
Addresses
|
||||
</div>
|
||||
<div class="px-4">
|
||||
<div class="mb-3">
|
||||
<div class="text-sm">Account</div>
|
||||
<RouterLink class="text-xs text-primary" :to="`/${chain}/account/${addresses.account}`">
|
||||
{{ addresses.account }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div class="px-4">
|
||||
<div class="mb-3">
|
||||
<div class="text-sm">Account</div>
|
||||
<RouterLink
|
||||
class="text-xs text-primary"
|
||||
:to="`/${chain}/account/${addresses.account}`"
|
||||
>
|
||||
{{ addresses.account }}
|
||||
</RouterLink>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="text-sm">Operator Address</div>
|
||||
<div class="text-xs">
|
||||
{{ v.operator_address }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="text-sm">Hex Address</div>
|
||||
<div class="text-xs">{{ addresses.hex }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm">Signer Address</div>
|
||||
<div class="text-xs">{{ addresses.valCons }}</div>
|
||||
<div class="mb-3">
|
||||
<div class="text-sm">Operator Address</div>
|
||||
<div class="text-xs">
|
||||
{{ v.operator_address }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="mb-3">
|
||||
<div class="text-sm">Hex Address</div>
|
||||
<div class="text-xs">{{ addresses.hex }}</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="text-sm">Signer Address</div>
|
||||
<div class="text-xs">{{ addresses.valCons }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -398,19 +346,15 @@ onMounted(() => {
|
||||
</td>
|
||||
<td class="text-truncate text-primary" style="max-width: 200px">
|
||||
<RouterLink :to="`/${props.chain}/tx/${item.txhash}`">
|
||||
{{ item.txhash }}
|
||||
</RouterLink>
|
||||
{{ item.txhash }}
|
||||
</RouterLink>
|
||||
</td>
|
||||
<td>
|
||||
<div class="flex items-center">
|
||||
<span class="mr-2">{{
|
||||
format.messages(item.tx.body.messages)
|
||||
}}</span>
|
||||
<Icon
|
||||
v-if="item.code === 0"
|
||||
icon="mdi-check"
|
||||
class="text-yes"
|
||||
/>
|
||||
<Icon v-if="item.code === 0" icon="mdi-check" class="text-yes" />
|
||||
<Icon v-else icon="mdi-multiply" class="text-no" />
|
||||
</div>
|
||||
</td>
|
||||
|
@ -302,7 +302,7 @@ loadAvatars();
|
||||
<label
|
||||
v-else
|
||||
for="delegate"
|
||||
class="btn btn-xs bg-primary capitalize"
|
||||
class="btn btn-xs rounded bg-primary capitalize border-none"
|
||||
@click="
|
||||
dialog.open('delegate', {
|
||||
validator_address: v.operator_address,
|
||||
|
@ -11,115 +11,99 @@ const props = defineProps(['hash', 'chain']);
|
||||
const blockchain = useBlockchain();
|
||||
const format = useFormatter();
|
||||
const tx = ref(
|
||||
{} as {
|
||||
tx: Tx;
|
||||
tx_response: TxResponse;
|
||||
}
|
||||
{} as {
|
||||
tx: Tx;
|
||||
tx_response: TxResponse;
|
||||
}
|
||||
);
|
||||
if (props.hash) {
|
||||
blockchain.rpc.getTx(props.hash).then((x) => (tx.value = x));
|
||||
blockchain.rpc.getTx(props.hash).then((x) => (tx.value = x));
|
||||
}
|
||||
const messages = computed(() => {
|
||||
return tx.value.tx?.body?.messages || [];
|
||||
return tx.value.tx?.body?.messages || [];
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div>
|
||||
<div
|
||||
v-if="tx.tx_response"
|
||||
class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4"
|
||||
>
|
||||
<h2 class="card-title truncate mb-2">Summary</h2>
|
||||
<div class="overflow-auto-x">
|
||||
<table class="table text-sm">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Tx Hash</td>
|
||||
<td>{{ tx.tx_response.txhash }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Height</td>
|
||||
<td class="text-primary">
|
||||
<RouterLink
|
||||
:to="`/${props.chain}/block/${tx.tx_response.height}`"
|
||||
>{{ tx.tx_response.height }}</RouterLink
|
||||
>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td>
|
||||
<div
|
||||
class="text-xs truncate relative py-2 px-4 w-fit mr-2"
|
||||
:class="`text-${
|
||||
tx.tx_response.code === 0 ? 'success' : 'error'
|
||||
}`"
|
||||
>
|
||||
<span
|
||||
class="inset-x-0 inset-y-0 opacity-10 absolute"
|
||||
:class="`bg-${
|
||||
tx.tx_response.code === 0 ? 'success' : 'error'
|
||||
}`"
|
||||
></span>
|
||||
{{ tx.tx_response.code === 0 ? 'Success' : 'Failded' }}
|
||||
<div>
|
||||
<div v-if="tx.tx_response" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4">
|
||||
<h2 class="card-title truncate mb-2">Summary</h2>
|
||||
<div class="overflow-auto-x">
|
||||
<table class="table text-sm">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>Tx Hash</td>
|
||||
<td>{{ tx.tx_response.txhash }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Height</td>
|
||||
<td class="text-primary">
|
||||
<RouterLink :to="`/${props.chain}/block/${tx.tx_response.height}`">{{ tx.tx_response.height
|
||||
}}
|
||||
</RouterLink>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Status</td>
|
||||
<td>
|
||||
<div class="text-xs truncate relative py-2 px-4 w-fit mr-2 rounded" :class="`text-${tx.tx_response.code === 0 ? 'success' : 'error'
|
||||
}`">
|
||||
<span class="inset-x-0 inset-y-0 opacity-10 absolute" :class="`bg-${tx.tx_response.code === 0 ? 'success' : 'error'
|
||||
}`"></span>
|
||||
{{ tx.tx_response.code === 0 ? 'Success' : 'Failded' }}
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Time</td>
|
||||
<td>
|
||||
{{ tx.tx_response.timestamp }} ({{
|
||||
format.toDay(tx.tx_response.timestamp, 'from')
|
||||
}})
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Gas</td>
|
||||
<td>
|
||||
{{ tx.tx_response.gas_used }} / {{ tx.tx_response.gas_wanted }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fee</td>
|
||||
<td>
|
||||
{{
|
||||
format.formatTokens(
|
||||
tx.tx?.auth_info?.fee?.amount,
|
||||
true,
|
||||
'0,0.[00]'
|
||||
)
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Memo</td>
|
||||
<td>{{ tx.tx.body.memo }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="tx.tx_response" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4">
|
||||
<h2 class="card-title truncate mb-2">
|
||||
Messages: ({{ messages.length }})
|
||||
</h2>
|
||||
<div class="divider"></div>
|
||||
<div v-for="(msg, i) in messages">
|
||||
<div>
|
||||
<DynamicComponent :value="msg" />
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Time</td>
|
||||
<td>
|
||||
{{ tx.tx_response.timestamp }} ({{
|
||||
format.toDay(tx.tx_response.timestamp, 'from')
|
||||
}})
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Gas</td>
|
||||
<td>
|
||||
{{ tx.tx_response.gas_used }} / {{ tx.tx_response.gas_wanted }}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fee</td>
|
||||
<td>
|
||||
{{
|
||||
format.formatTokens(
|
||||
tx.tx?.auth_info?.fee?.amount,
|
||||
true,
|
||||
'0,0.[00]'
|
||||
)
|
||||
}}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Memo</td>
|
||||
<td>{{ tx.tx.body.memo }}</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="messages.length === 0">No messages</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="tx.tx_response"
|
||||
class="bg-base-100 px-4 pt-3 pb-4 rounded shadow mb-4"
|
||||
>
|
||||
<h2 class="card-title truncate mb-2">
|
||||
Messages: ({{ messages.length }})
|
||||
</h2>
|
||||
<div class="divider"></div>
|
||||
<div v-for="(msg, i) in messages">
|
||||
<div><DynamicComponent :value="msg" /></div>
|
||||
</div>
|
||||
<div v-if="messages.length === 0">No messages</div>
|
||||
<div v-if="tx.tx_response" class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
|
||||
<h2 class="card-title truncate mb-2">JSON</h2>
|
||||
<JsonPretty :data="tx" :deep="3" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div
|
||||
v-if="tx.tx_response"
|
||||
class="bg-base-100 px-4 pt-3 pb-4 rounded shadow"
|
||||
>
|
||||
<h2 class="card-title truncate mb-2">JSON</h2>
|
||||
<JsonPretty :data="tx" :deep="3" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
@ -124,7 +124,7 @@ function color(v: string) {
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="overflow-x-auto w-full card ">
|
||||
<div class="overflow-x-auto w-full">
|
||||
<div class="lg:!flex lg:!items-center lg:!justify-between bg-base-100 p-5">
|
||||
<div class="min-w-0 flex-1">
|
||||
<h2 class="text-2xl font-bold leading-7 sm:!truncate sm:!text-3xl sm:!tracking-tight">My Validators</h2>
|
||||
|
@ -2,10 +2,10 @@
|
||||
import { ref, onMounted, computed, onUnmounted } from 'vue';
|
||||
import { fromHex, toBase64 } from '@cosmjs/encoding';
|
||||
import {
|
||||
useFormatter,
|
||||
useStakingStore,
|
||||
useBaseStore,
|
||||
useBlockchain,
|
||||
useFormatter,
|
||||
useStakingStore,
|
||||
useBaseStore,
|
||||
useBlockchain,
|
||||
} from '@/stores';
|
||||
import UptimeBar from '@/components/UptimeBar.vue';
|
||||
import type { Commit, SlashingParam, SigningInfo } from '@/types';
|
||||
@ -25,177 +25,181 @@ const slashingParam = ref({} as SlashingParam)
|
||||
|
||||
const signingInfo = ref({} as Record<string, SigningInfo>);
|
||||
|
||||
const filterOptout =ref(false)
|
||||
const filterOptout = ref(false)
|
||||
// filter validators by keywords
|
||||
const validators = computed(() => {
|
||||
if (keyword)
|
||||
return stakingStore.validators.filter(
|
||||
(x) => x.description.moniker.indexOf(keyword.value) > -1
|
||||
);
|
||||
return stakingStore.validators;
|
||||
if (keyword)
|
||||
return stakingStore.validators.filter(
|
||||
(x) => x.description.moniker.indexOf(keyword.value) > -1
|
||||
);
|
||||
return stakingStore.validators;
|
||||
});
|
||||
|
||||
const list = computed(() => {
|
||||
const window = Number(slashingParam.value.signed_blocks_window|| 0)
|
||||
const vset = validators.value.map(v => {
|
||||
const signing = signingInfo.value[consensusPubkeyToHexAddress(v.consensus_pubkey)]
|
||||
return {
|
||||
v,
|
||||
signing,
|
||||
hex: toBase64(fromHex(consensusPubkeyToHexAddress(v.consensus_pubkey))),
|
||||
uptime: signing && window > 0 ? (window - Number(signing.missed_blocks_counter)) / window: undefined
|
||||
}})
|
||||
return filterOptout.value? vset.filter(x => x.signing) : vset
|
||||
const window = Number(slashingParam.value.signed_blocks_window || 0)
|
||||
const vset = validators.value.map(v => {
|
||||
const signing = signingInfo.value[consensusPubkeyToHexAddress(v.consensus_pubkey)]
|
||||
return {
|
||||
v,
|
||||
signing,
|
||||
hex: toBase64(fromHex(consensusPubkeyToHexAddress(v.consensus_pubkey))),
|
||||
uptime: signing && window > 0 ? (window - Number(signing.missed_blocks_counter)) / window : undefined
|
||||
}
|
||||
})
|
||||
return filterOptout.value ? vset.filter(x => x.signing) : vset
|
||||
})
|
||||
|
||||
onMounted(() => {
|
||||
live.value = true;
|
||||
|
||||
baseStore.fetchLatest().then(l => {
|
||||
let b = l
|
||||
if(baseStore.recents?.findIndex(x => x.block_id.hash === l.block_id.hash) > -1 ) {
|
||||
b = baseStore.recents?.at(0) || l
|
||||
}
|
||||
latest.value = Number(b.block.header.height);
|
||||
commits.value.unshift(b.block.last_commit);
|
||||
const height = Number(b.block.header?.height || 0);
|
||||
if (height > 50) {
|
||||
// constructs sequence for loading blocks
|
||||
let promise = Promise.resolve();
|
||||
for (let i = height - 1; i > height - 50; i -= 1) {
|
||||
promise = promise.then(
|
||||
() =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (live.value && commits2.value.length < 50) {
|
||||
// continue only if the page is living
|
||||
baseStore.fetchBlock(i).then((x) => {
|
||||
commits.value.unshift(x.block.last_commit);
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
live.value = true;
|
||||
|
||||
chainStore.rpc.getSlashingSigningInfos().then((x) => {
|
||||
x.info?.forEach((i) => {
|
||||
signingInfo.value[valconsToBase64(i.address)] = i;
|
||||
baseStore.fetchLatest().then(l => {
|
||||
let b = l
|
||||
if (baseStore.recents?.findIndex(x => x.block_id.hash === l.block_id.hash) > -1) {
|
||||
b = baseStore.recents?.at(0) || l
|
||||
}
|
||||
latest.value = Number(b.block.header.height);
|
||||
commits.value.unshift(b.block.last_commit);
|
||||
const height = Number(b.block.header?.height || 0);
|
||||
if (height > 50) {
|
||||
// constructs sequence for loading blocks
|
||||
let promise = Promise.resolve();
|
||||
for (let i = height - 1; i > height - 50; i -= 1) {
|
||||
promise = promise.then(
|
||||
() =>
|
||||
new Promise((resolve, reject) => {
|
||||
if (live.value && commits2.value.length < 50) {
|
||||
// continue only if the page is living
|
||||
baseStore.fetchBlock(i).then((x) => {
|
||||
commits.value.unshift(x.block.last_commit);
|
||||
resolve();
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
chainStore.rpc.getSlashingParams().then(x => {
|
||||
slashingParam.value = x.params
|
||||
})
|
||||
chainStore.rpc.getSlashingSigningInfos().then((x) => {
|
||||
x.info?.forEach((i) => {
|
||||
signingInfo.value[valconsToBase64(i.address)] = i;
|
||||
});
|
||||
});
|
||||
|
||||
chainStore.rpc.getSlashingParams().then(x => {
|
||||
slashingParam.value = x.params
|
||||
})
|
||||
});
|
||||
|
||||
const commits2 = computed(() => {
|
||||
const la = baseStore.recents.map(b => b.block.last_commit)
|
||||
const all = [...commits.value, ...la]
|
||||
return all.length > 50 ? all.slice(all.length - 50) : all
|
||||
const la = baseStore.recents.map(b => b.block.last_commit)
|
||||
const all = [...commits.value, ...la]
|
||||
return all.length > 50 ? all.slice(all.length - 50) : all
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
live.value = false;
|
||||
live.value = false;
|
||||
});
|
||||
|
||||
//const tab = ref(window.location.hash.search("block")>-1?"2":"3")
|
||||
const tab = ref("2")
|
||||
function changeTab(v: string) {
|
||||
tab.value = v
|
||||
tab.value = v
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div>
|
||||
<div class="tabs tabs-boxed bg-transparent mb-4">
|
||||
<a
|
||||
class="tab text-gray-400 capitalize"
|
||||
:class="{ 'tab-active': tab === '3' }"
|
||||
@click="changeTab('3')"
|
||||
>Overall</a
|
||||
>
|
||||
<a
|
||||
class="tab text-gray-400 capitalize"
|
||||
:class="{ 'tab-active': tab === '2' }"
|
||||
@click="changeTab('2')"
|
||||
>Blocks</a
|
||||
>
|
||||
<RouterLink :to="`/${chain}/uptime/customize`">
|
||||
<a
|
||||
class="tab text-gray-400 capitalize"
|
||||
>Customize</a
|
||||
></RouterLink>
|
||||
</div>
|
||||
<div class="bg-base-100 px-5 pt-5">
|
||||
<div class="flex items-center gap-x-4">
|
||||
<label v-if="chainStore.isConsumerChain" class="text-center">
|
||||
<input type="checkbox" v-model="filterOptout" class="checkbox"/>
|
||||
Only Consumer Set
|
||||
</label>
|
||||
<input type="text" v-model="keyword" placeholder="Keywords to filter validators"
|
||||
class="input input-sm w-full flex-1" />
|
||||
</div>
|
||||
<div class="flex flex-row flex-wrap gap-x-4 mt-4" :class="tab === '2'?'':'hidden'">
|
||||
<div v-for="({v, signing, hex}, i) in list" :key="i">
|
||||
<div class="flex items-center justify-between py-0 w-72">
|
||||
<label class="text-truncate text-sm">
|
||||
<span class="ml-1 text-black dark:text-white">{{ i + 1 }}.{{ v.description.moniker }}</span>
|
||||
</label>
|
||||
<div v-if="Number(signing?.missed_blocks_counter || 0) > 10" class="badge badge-sm bg-transparent border-0 text-red-500">
|
||||
{{ signing?.missed_blocks_counter }}
|
||||
</div>
|
||||
<div v-else class="badge badge-sm bg-transparent text-green-600 border-0">
|
||||
{{ signing?.missed_blocks_counter }}
|
||||
</div>
|
||||
</div>
|
||||
<UptimeBar :blocks="commits2" :validator="hex" />
|
||||
<div>
|
||||
<div class="tabs tabs-boxed bg-transparent mb-4">
|
||||
<a class="tab text-gray-400 capitalize" :class="{ 'tab-active': tab === '3' }"
|
||||
@click="changeTab('3')">Overall</a>
|
||||
<a class="tab text-gray-400 capitalize" :class="{ 'tab-active': tab === '2' }"
|
||||
@click="changeTab('2')">Blocks</a>
|
||||
<RouterLink :to="`/${chain}/uptime/customize`">
|
||||
<a class="tab text-gray-400 capitalize">Customize</a>
|
||||
</RouterLink>
|
||||
</div>
|
||||
</div>
|
||||
<div class="bg-base-100 px-5 pt-5">
|
||||
<div class="flex items-center gap-x-4">
|
||||
<label v-if="chainStore.isConsumerChain" class="text-center">
|
||||
<input type="checkbox" v-model="filterOptout" class="checkbox" />
|
||||
Only Consumer Set
|
||||
</label>
|
||||
<input type="text" v-model="keyword" placeholder="Keywords to filter validators"
|
||||
class="input input-sm w-full flex-1 border border-gray-200 dark:border-gray-600" />
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-x-4 mt-4" :class="tab === '2' ? '' : 'hidden'">
|
||||
<div v-for="({ v, signing, hex }, i) in list" :key="i">
|
||||
<div class="flex items-center justify-between py-0">
|
||||
<label class="text-truncate text-sm">
|
||||
<span class="ml-1 text-black dark:text-white">{{ i + 1 }}.{{ v.description.moniker }}</span>
|
||||
</label>
|
||||
<div v-if="Number(signing?.missed_blocks_counter || 0) > 10"
|
||||
class="badge badge-sm bg-transparent border-0 text-red-500">
|
||||
{{ signing?.missed_blocks_counter }}
|
||||
</div>
|
||||
<div v-else class="badge badge-sm bg-transparent text-green-600 border-0">
|
||||
{{ signing?.missed_blocks_counter }}
|
||||
</div>
|
||||
</div>
|
||||
<UptimeBar :blocks="commits2" :validator="hex" />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div :class="tab === '3'?'':'hidden'" class="overflow-x-auto">
|
||||
<table class="table table-compact w-full mt-5">
|
||||
<thead class=" capitalize">
|
||||
<tr>
|
||||
<td>Validator</td>
|
||||
<td class="text-right">Uptime</td>
|
||||
<td>Last Jailed Time</td>
|
||||
<td class="text-right">Signed Precommits</td>
|
||||
<td class="text-right">Start Height</td>
|
||||
<td>Tombstoned</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="({v, signing, uptime}, i) in list" class="hover">
|
||||
<td><div class="truncate max-w-sm">{{ i+1 }}. {{ v.description.moniker }}</div></td>
|
||||
<td class="text-right">
|
||||
<span v-if="signing" class="" :class="uptime && uptime > 0.95 ? 'text-green-500':'text-red-500'"><div class="tooltip" :data-tip="`${signing.missed_blocks_counter} missing blocks`"> {{ format.percent(uptime) }} </div> </span>
|
||||
</td>
|
||||
<td><span v-if="signing && !signing.jailed_until.startsWith('1970')">
|
||||
<div class="tooltip" :data-tip="format.toDay(signing?.jailed_until, 'long')">
|
||||
<span>{{ format.toDay(signing?.jailed_until, "from") }}</span>
|
||||
</div>
|
||||
</span></td>
|
||||
<td class="text-xs text-right">
|
||||
|
||||
<span v-if="signing && signing.jailed_until.startsWith('1970')" class="text-right">{{ format.percent(Number(signing.index_offset)/(latest-Number(signing.start_height))) }}</span>
|
||||
{{ signing?.index_offset }}
|
||||
</td>
|
||||
<td class="text-right">{{ signing?.start_height }}</td>
|
||||
<td class=" capitalize">{{ signing?.tombstoned }}</td>
|
||||
</tr>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="2" class="text-right"> Minimum uptime per window: <span class="lowercase tooltip" :data-tip="`Window size: ${ slashingParam.signed_blocks_window }`"><span class="ml-2 btn btn-error btn-xs">{{ format.percent(slashingParam.min_signed_per_window) }}</span> </span></td>
|
||||
<td colspan="8"></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
<div :class="tab === '3' ? '' : 'hidden'" class="overflow-x-auto">
|
||||
<table class="table table-compact w-full mt-5">
|
||||
<thead class=" capitalize">
|
||||
<tr>
|
||||
<td>Validator</td>
|
||||
<td class="text-right">Uptime</td>
|
||||
<td>Last Jailed Time</td>
|
||||
<td class="text-right">Signed Precommits</td>
|
||||
<td class="text-right">Start Height</td>
|
||||
<td>Tombstoned</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tr v-for="({ v, signing, uptime }, i) in list" class="hover">
|
||||
<td>
|
||||
<div class="truncate max-w-sm">{{ i + 1 }}. {{ v.description.moniker }}</div>
|
||||
</td>
|
||||
<td class="text-right">
|
||||
<span v-if="signing" class=""
|
||||
:class="uptime && uptime > 0.95 ? 'text-green-500' : 'text-red-500'">
|
||||
<div class="tooltip" :data-tip="`${signing.missed_blocks_counter} missing blocks`"> {{
|
||||
format.percent(uptime) }} </div>
|
||||
</span>
|
||||
</td>
|
||||
<td><span v-if="signing && !signing.jailed_until.startsWith('1970')">
|
||||
<div class="tooltip" :data-tip="format.toDay(signing?.jailed_until, 'long')">
|
||||
<span>{{ format.toDay(signing?.jailed_until, "from") }}</span>
|
||||
</div>
|
||||
</span></td>
|
||||
<td class="text-xs text-right">
|
||||
|
||||
<div class="h-6"></div>
|
||||
<span v-if="signing && signing.jailed_until.startsWith('1970')" class="text-right">{{
|
||||
format.percent(Number(signing.index_offset) / (latest - Number(signing.start_height)))
|
||||
}}</span>
|
||||
{{ signing?.index_offset }}
|
||||
</td>
|
||||
<td class="text-right">{{ signing?.start_height }}</td>
|
||||
<td class=" capitalize">{{ signing?.tombstoned }}</td>
|
||||
</tr>
|
||||
<tfoot>
|
||||
<tr>
|
||||
<td colspan="2" class="text-right"> Minimum uptime per window: <span class="lowercase tooltip"
|
||||
:data-tip="`Window size: ${slashingParam.signed_blocks_window}`"><span
|
||||
class="ml-2 btn btn-error btn-xs">{{
|
||||
format.percent(slashingParam.min_signed_per_window) }}</span>
|
||||
</span></td>
|
||||
<td colspan="8"></td>
|
||||
</tr>
|
||||
</tfoot>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="h-6"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<route>
|
||||
{
|
||||
@ -206,8 +210,6 @@ function changeTab(v: string) {
|
||||
}
|
||||
</route>
|
||||
|
||||
<style lang="scss">
|
||||
.v-field--variant-outlined .v-field__outline__notch {
|
||||
border-width: 0 !important;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">.v-field--variant-outlined .v-field__outline__notch {
|
||||
border-width: 0 !important;
|
||||
}</style>
|
||||
|
@ -20,3 +20,7 @@
|
||||
position: relative;
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.btn {
|
||||
@apply rounded;
|
||||
}
|
Loading…
Reference in New Issue
Block a user