Merge remote-tracking branch 'upstream/master' into v3-single

This commit is contained in:
Alisa | Side.one
2023-05-20 13:10:34 +08:00
69 changed files with 1179 additions and 244 deletions
+1 -1
View File
@@ -32,7 +32,7 @@ const addFavor = (e: Event) => {
<div class="w-8 h-8 rounded-full overflow-hidden">
<img :src="conf.logo" />
</div>
<div class="font-semibold ml-4 text-base flex-1">
<div class="font-semibold ml-4 text-base flex-1 truncate">
{{ conf?.prettyName || props.name }}
</div>
<div
@@ -22,7 +22,7 @@ const header = computed(() => {
<VTable
v-if="header.length > 0"
density="compact"
height="300px"
:height="value.length > 5? '300px': ''"
fixed-header
hover
>
+2 -2
View File
@@ -5,10 +5,10 @@ const props = defineProps(['value']);
</script>
<template>
<div class="overflow-x-auto">
<table class="table w-full text-sm">
<table class="table table-compact w-full text-sm">
<tbody>
<tr v-for="(v, k) of value">
<td class="text-capitalize" style="max-width: 100px">{{ String(k).replaceAll("_", " ") }}</td>
<td class="text-capitalize whitespace-break-spaces w-1/5" style="min-width: 180px;">{{ String(k).replaceAll("_", " ") }}</td>
<td>
<div
class="overflow-hidden w-auto whitespace-normal"
+44 -3
View File
@@ -13,6 +13,7 @@ import { useDashboard } from '@/stores/useDashboard';
import NavBarI18n from './NavBarI18n.vue';
import NavBarWallet from './NavBarWallet.vue';
import { useBlockchain } from '@/stores';
import { computed } from 'vue';
const { appRouteTransition } = useThemeConfig();
@@ -34,6 +35,7 @@ const changeOpen = (index: Number) => {
sidebarOpen.value = !sidebarOpen.value;
}
};
const showDiscord = window.location.host.search("ping.pub") > -1
</script>
<template>
@@ -92,7 +94,7 @@ const changeOpen = (index: Number) => {
</div>
<div
v-if="item?.badgeContent"
class="mr-6 badge badge-sm badge-primary"
class="mr-6 badge badge-sm rounded-none" :class="item?.badgeClass"
>
{{ item?.badgeContent }}
</div>
@@ -137,7 +139,7 @@ const changeOpen = (index: Number) => {
<RouterLink
:to="item?.to"
v-if="item?.title && !item?.children?.length"
v-if="item?.title && !item?.children?.length && item?.to"
@click="sidebarShow = false"
class="collapse-title px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]"
>
@@ -162,7 +164,7 @@ const changeOpen = (index: Number) => {
</div>
<div
v-if="item?.badgeContent"
class="mr-6 badge badge-sm badge-primary"
class="mr-6 badge badge-sm" :class="item?.badgeClass"
>
{{ item?.badgeContent }}
</div>
@@ -174,6 +176,45 @@ const changeOpen = (index: Number) => {
{{ item?.heading }}
</div>
</div>
<div>
<div class="px-4 text-sm pt-4 text-gray-400 pb-2 uppercase">
Sponsors
</div>
<a href="https://osmosis.zone" class="collapse-title px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]">
<img src="https://ping.pub/logos/osmosis.jpg" class="w-6 h-6 rounded-full mr-3"/>
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200">
Osmosis
</div>
</a>
<a href="https://becole.com" class="collapse-title px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]">
<img src="https://becole.com/static/logo/logo_becole.png" class="w-6 h-6 rounded-full mr-3"/>
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200">
Becole
</div>
</a>
<div class="px-4 text-sm pt-4 text-gray-400 pb-2 uppercase">
Links
</div>
<a href="https://twitter.com/ping_pub" class="collapse-title px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]">
<Icon icon="mdi:twitter" class="text-xl mr-2"/>
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200">
Twitter
</div>
</a>
<a v-if="showDiscord" href="https://discord.com/invite/CmjYVSr6GW" class="collapse-title px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]">
<Icon icon="mdi:discord" class="text-xl mr-2"/>
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200">
Discord
</div>
</a>
<a href="https://github.com/ping-pub/explorer/discussions" class="collapse-title px-4 flex items-center py-2 hover:bg-gray-100 dark:hover:bg-[#373f59]">
<Icon icon="mdi:frequently-asked-questions" class="text-xl mr-2"/>
<div class="text-base capitalize flex-1 text-gray-700 dark:text-gray-200">
FAQ
</div>
</a>
</div>
</div>
<div class="xl:ml-64 px-5">
<!-- header -->
+5 -4
View File
@@ -1,10 +1,11 @@
<script setup lang="ts">
import { useWalletStore } from '@/stores';
import type { Icon } from '@iconify/vue';
import { ref, computed } from 'vue';
const walletStore = useWalletStore();
walletStore.$subscribe((m, s) => {
console.log(m, s);
});
// walletStore.$subscribe((m, s) => {
// console.log(m, s);
// });
function walletStateChange(res: any) {
walletStore.setConnectedWallet(res.detail?.value);
}
@@ -105,7 +106,7 @@ const addressChange = computed(() => {
<ping-connect-wallet
:chain-id="'juno-1'"
:hd-path="`m/44'/118/0'/0/0`"
@change="walletStateChange"
@connect="walletStateChange"
/>
</Teleport>
</div>
+2 -2
View File
@@ -30,7 +30,7 @@ import type {
PaginatedProposals,
Tally,
} from '@/types/gov';
import type { PaginatedSigningInfo } from '@/types/slashing';
import type { PaginatedSigningInfo, SlashingParam } from '@/types/slashing';
import type {
Delegation,
PaginatedDelegations,
@@ -90,7 +90,7 @@ export interface RequestRegistry extends AbstractRegistry {
}>;
mint_annual_provisions: Request<{ annual_provisions: string }>;
slashing_params: Request<any>;
slashing_params: Request<{params: SlashingParam}>;
slashing_signing_info: Request<PaginatedSigningInfo>;
gov_params_voting: Request<GovParams>;
+8 -2
View File
@@ -9,7 +9,7 @@ import { loadFonts } from '@/plugins/vuetify/webfontloader';
import '@/plugins/vuetify/@core/scss/template/index.scss';
import '@/plugins/vuetify/styles/styles.scss';
import '@/style.css';
import { createApp } from 'vue';
import { createApp, ref } from 'vue';
import { createPinia } from 'pinia';
import LazyLoad from 'lazy-load-vue3';
// import router from "@/plugins/vuetify/router";
@@ -32,4 +32,10 @@ app.mount('#app');
// fetch latest block every 6s
const blockStore = useBaseStore()
setInterval(() => {blockStore.fetchLatest()}, 6000)
const requestCounter = ref(0)
setInterval(() => {
requestCounter.value += 1
if(requestCounter.value < 5) { // max allowed request
blockStore.fetchLatest().finally(() => requestCounter.value -= 1)
}
}, 6000)
+3 -1
View File
@@ -458,8 +458,10 @@ loadAccount(props.address);
v.height
}}</RouterLink>
</td>
<td class="text-truncate py-3" style="max-width: 200px">
<td class="text-truncate text-primary py-3" style="max-width: 200px">
<RouterLink :to="`/${chain}/tx/${v.txhash}`">
{{ v.txhash }}
</RouterLink>
</td>
<td class="flex items-center py-3">
<div class="mr-2">
+35 -31
View File
@@ -8,6 +8,12 @@ const tab = ref('blocks');
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
})
</script>
<template>
<div>
@@ -26,41 +32,35 @@ const format = useFormatter();
>
</div>
<div v-show="tab === 'blocks'" class="bg-base-100 rounded overflow-x-auto">
<table class="table w-full">
<thead>
<tr>
<th style="position: relative; z-index: 2;">Height</th>
<th>Hash</th>
<th>Proposer</th>
<th>Txs</th>
<th>Time</th>
</tr>
</thead>
<tbody v-if="base.recents && base.recents.length > 0" >
<tr v-for="(item, index) in base.recents" :key="index">
<td class="text-sm text-primary">
<RouterLink
:to="`/${props.chain}/block/${item.block?.header?.height}`"
>{{ item.block?.header?.height }}</RouterLink
>
</td>
<td>{{ item.block_id?.hash }}</td>
<td>
{{ format.validator(item.block?.header?.proposer_address) }}
</td>
<td>{{ item.block?.data?.txs.length }}</td>
<td>{{ format.toDay(item.block?.header?.time, 'from') }}</td>
</tr>
</tbody>
</table>
<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 === 'transactions'"
class="bg-base-100 rounded overflow-x-auto"
>
<table class="table w-full">
<table class="table w-full table-compact">
<thead>
<tr>
<th style="position: relative; z-index: 2;">Height</th>
@@ -71,8 +71,12 @@ const format = useFormatter();
</thead>
<tbody >
<tr v-for="(item,index) in base.txsInRecents" :index="index">
<td>{{ item.height }}</td>
<td class="text-xs truncate" width="50%">
<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>
+1 -1
View File
@@ -71,7 +71,7 @@ function color(v: string) {
{
meta: {
i18n: 'ibc',
order: 8
order: 9
}
}
</route>
+40 -20
View File
@@ -9,6 +9,7 @@ import {
useTxDialog,
useWalletStore,
useStakingStore,
useParamStore,
} from '@/stores';
import { onMounted, ref } from 'vue';
import { useIndexModule } from './indexStore';
@@ -16,6 +17,9 @@ import { computed } from '@vue/reactivity';
import CardStatisticsVertical from '@/components/CardStatisticsVertical.vue';
import ProposalListItem from '@/components/ProposalListItem.vue';
import ArrayObjectElement from '@/components/dynamic/ArrayObjectElement.vue'
const props = defineProps(['chain']);
const blockchain = useBlockchain();
const store = useIndexModule();
@@ -23,6 +27,7 @@ const walletStore = useWalletStore();
const format = useFormatter();
const dialog = useTxDialog();
const stakingStore = useStakingStore();
const paramStore = useParamStore()
const coinInfo = computed(() => {
return store.coinInfo;
});
@@ -30,6 +35,9 @@ const coinInfo = computed(() => {
onMounted(() => {
store.loadDashboard();
walletStore.loadMyAsset();
paramStore.handleAbciInfo()
// if(!(coinInfo.value && coinInfo.value.name)) {
// }
});
const ticker = computed(() => store.coinInfo.tickers[store.tickerIndex]);
@@ -40,6 +48,7 @@ blockchain.$subscribe((m, s) => {
) {
store.loadDashboard();
walletStore.loadMyAsset();
paramStore.handleAbciInfo()
}
});
function shortName(name: string, id: string) {
@@ -100,8 +109,7 @@ const color = computed(() => {
<div class="text-xl font-semibold text-main">
{{ coinInfo.name }} (<span class="uppercase">{{
coinInfo.symbol
}}</span
>)
}}</span>)
</div>
<div class="text-xs mt-2">
Rank:
@@ -229,7 +237,7 @@ const color = computed(() => {
</div>
</div>
<div class="bg-base-100 rounded mt-4 shadow">
<div v-if="blockchain.supportModule('governance')" class="bg-base-100 rounded mt-4 shadow">
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
Active Proposals
</div>
@@ -246,8 +254,8 @@ const color = computed(() => {
{{ walletStore.currentAddress || 'Not Connected' }}
<RouterLink
v-if="walletStore.currentAddress"
class="float-right font-light text-sm cursor-pointert link link-primary no-underline font-medium"
to="/wallet/portfolio"
class="float-right text-sm cursor-pointert link link-primary no-underline font-medium"
:to="`/${chain}/account/${walletStore.currentAddress}`"
>More</RouterLink
>
</div>
@@ -294,7 +302,7 @@ const color = computed(() => {
</div>
</div>
<div class="px-4 pb-4">
<div v-if="walletStore.delegations.length > 0" class="px-4 pb-4">
<table class="table table-compact w-full table-zebra">
<thead>
<tr>
@@ -316,27 +324,28 @@ const color = computed(() => {
<td>{{ format.formatToken(item?.balance) }}</td>
<td>
{{
format.formatToken(
format.formatTokens(
walletStore?.rewards?.rewards?.find(
(el) =>
el?.validator_address ===
item?.delegation?.validator_address
)?.reward?.[0]
)
)?.reward)
}}
</td>
<td>
<div>
<button
<label for="delegate"
class="btn btn-xs btn-primary btn-ghost text-primary rounded-sm mr-2"
@click="dialog.open('delegate', { validator_address: item.delegation.validator_address})"
>
Delegate
</button>
<button
</label>
<label for="withdraw"
class="btn btn-xs btn-primary btn-ghost text-primary rounded-sm"
@click="dialog.open('withdraw', { validator_address: item.delegation.validator_address})"
>
Withdraw Rewards
</button>
</label>
</div>
</td>
</tr>
@@ -345,13 +354,9 @@ const color = computed(() => {
</div>
<div class="grid grid-cols-3 gap-4 px-4 pb-6 mt-4">
<label for="send" class="btn btn-success text-white">Send</label>
<RouterLink to="/wallet/receive" class="btn btn-info text-white"
>Receive</RouterLink
>
<label for="PingTokenConvert" class="btn btn-primary text-white"
>Convert</label
>
<label for="PingTokenConvert" class="btn btn-primary text-white">Convert</label>
<label for="send" class="btn btn-success text-white" @click="dialog.open('send', {})">Send</label>
<RouterLink to="/wallet/receive" class="btn btn-info text-white">Receive</RouterLink>
</div>
<Teleport to="body">
<ping-token-convert
@@ -361,6 +366,21 @@ const color = computed(() => {
></ping-token-convert>
</Teleport>
</div>
<div class="bg-base-100 rounded mt-4 shadow">
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
Application
</div>
<!-- Application Version -->
<ArrayObjectElement :value="paramStore.appVersion?.items" :thead="false" />
</div>
<div v-if="!store.coingeckoId" class="bg-base-100 rounded mt-4 shadow">
<div class="px-4 pt-4 pb-2 text-lg font-semibold text-main">
Node Information
</div>
<ArrayObjectElement :value="paramStore.nodeVersion?.items" :thead="false" />
</div>
</div>
</template>
+20 -7
View File
@@ -87,29 +87,35 @@ export const useIndexModule = defineStore('module-index', {
return useBankStore();
},
twitter(): string {
return `https://twitter.com/${this.coinInfo.links.twitter_screen_name}`;
if(!this.coinInfo?.links?.twitter_screen_name) return ""
return `https://twitter.com/${this.coinInfo?.links.twitter_screen_name}`;
},
homepage(): string {
const [page1, page2, page3] = this.coinInfo.links?.homepage;
if(!this.coinInfo?.links?.homepage) return ""
const [page1, page2, page3] = this.coinInfo?.links?.homepage;
return page1 || page2 || page3;
},
github(): string {
const [page1, page2, page3] = this.coinInfo.links?.repos_url?.github;
if(!this.coinInfo?.links?.repos_url) return ""
const [page1, page2, page3] = this.coinInfo?.links?.repos_url?.github;
return page1 || page2 || page3;
},
telegram(): string {
return `https://t.me/${this.coinInfo.links.telegram_channel_identifier}`;
if(!this.coinInfo?.links?.homepage) return ""
return `https://t.me/${this.coinInfo?.links.telegram_channel_identifier}`;
},
priceChange(): string {
if(!this.coinInfo?.market_data?.price_change_percentage_24h) return ""
const change =
this.coinInfo.market_data?.price_change_percentage_24h || 0;
this.coinInfo?.market_data?.price_change_percentage_24h || 0;
return numeral(change).format('+0.[00]');
},
priceColor(): string {
if(!this.coinInfo?.market_data?.price_change_percentage_24h) return ""
const change =
this.coinInfo.market_data?.price_change_percentage_24h || 0;
this.coinInfo?.market_data?.price_change_percentage_24h || 0;
switch (true) {
case change > 0:
return 'text-success';
@@ -120,7 +126,8 @@ export const useIndexModule = defineStore('module-index', {
}
},
trustColor(): string {
const change = this.coinInfo.tickers[this.tickerIndex]?.trust_score;
if(!this.coinInfo?.tickers) return ""
const change = this.coinInfo?.tickers[this.tickerIndex]?.trust_score;
return colorMap(change);
},
@@ -193,6 +200,12 @@ export const useIndexModule = defineStore('module-index', {
},
];
},
coingeckoId() {
this.tickerIndex = 0;
const [firstAsset] = this.blockchain?.assets || [];
return firstAsset.coingecko_id
}
},
actions: {
async loadDashboard() {
+1 -1
View File
@@ -56,7 +56,7 @@ onMounted(() => {
{
meta: {
i18n: 'parameters',
order: 10
order: 50
}
}
</route>
+11 -7
View File
@@ -55,7 +55,6 @@ staking
const txs = ref({} as PaginatedTxs);
blockchain.rpc.getTxsBySender(addresses.value.account).then((x) => {
console.log('txs', x);
txs.value = x;
});
@@ -77,14 +76,19 @@ const selfRate = computed(() => {
}
return '-';
});
const logo = (identity?: string) => {
if (!identity) return '';
const url = avatars.value[identity] || '';
return url.startsWith('http')
? url
: `https://s3.amazonaws.com/keybase_processed_uploads/${url}`;
};
onMounted(() => {
if (validator) {
staking.fetchValidator(validator).then((res) => {
v.value = res.validator;
identity.value = res.validator?.description?.identity || '';
if (identity.value && !avatars.value[identity.value]) {
console.log(identity.value, avatars);
staking.keybase(identity.value).then((d) => {
if (Array.isArray(d.them) && d.them.length > 0) {
const uri = String(d.them[0]?.pictures?.primary?.url).replace(
@@ -143,9 +147,7 @@ onMounted(() => {
<div class="w-24 rounded-lg">
<img
v-if="avatars[identity] !== 'undefined'"
v-lazy="
`https://s3.amazonaws.com/keybase_processed_uploads/${avatars[identity]}`
"
v-lazy="logo(identity)"
class="object-contain"
/>
<Icon
@@ -394,8 +396,10 @@ onMounted(() => {
item.height
}}</RouterLink>
</td>
<td class="text-truncate" style="max-width: 200px">
<td class="text-truncate text-primary" style="max-width: 200px">
<RouterLink :to="`/${props.chain}/tx/${item.txhash}`">
{{ item.txhash }}
</RouterLink>
</td>
<td>
<div class="flex items-center">
+4 -17
View File
@@ -58,8 +58,6 @@ async function fetchChange() {
}
}
fetchChange();
const changes = computed(() => {
const changes = {} as Record<string, number>;
Object.keys(latest.value).forEach((k) => {
@@ -92,12 +90,6 @@ const change24Color = (key?: Key) => {
if (v < 0) return 'text-error';
};
const update = (m: DebuggerEvent) => {
if (m.key === 'validators') {
loadAvatars();
}
};
const list = computed(() => {
return tab.value === 'active' ? staking.validators : unbondList.value;
// return staking.validators
@@ -136,15 +128,6 @@ const loadAvatars = () => {
});
};
staking.$subscribe((m, s) => {
if (Array.isArray(m.events)) {
m.events.forEach((x) => {
update(x);
});
} else {
update(m.events);
}
});
const logo = (identity?: string) => {
if (!identity) return '';
const url = avatars.value[identity] || '';
@@ -168,6 +151,10 @@ const rank = function (position: number) {
return 'primary';
}
};
fetchChange();
loadAvatars();
</script>
<template>
<div>
+11 -5
View File
@@ -10,6 +10,15 @@ const blockchain = useBlockchain();
const base = useBaseStore();
const nodeInfo = ref({} as NodeInfo);
const height = ref(Number(base.latest.block?.header?.height|| 2001))
const hash = ref("")
if(height.value > 2000) {
height.value = Math.round((height.value - 2000) / 1000) * 1000
base.fetchBlock(height.value).then(res => {
hash.value = toHex(fromBase64(res.block_id.hash)).toUpperCase()
})
}
const state = computed(() => {
const rpcs = blockchain.current?.endpoints?.rpc
?.map((x) => x.address)
@@ -17,10 +26,8 @@ const state = computed(() => {
return `[statesync]
enable = true
rpc_servers = "${rpcs}"
trust_height = ${base.latest.block?.header?.height || 'loading'}
trust_hash = "${
base.latest.block_id ? toHex(fromBase64(base.latest.block_id?.hash)) : ''
}"
trust_height = ${height.value || 'loading'}
trust_hash = "${hash.value}"
trust_period = "168h" # 2/3 of unbonding time"
`;
});
@@ -31,7 +38,6 @@ const appName = computed(() => {
onMounted(() => {
blockchain.rpc.getBaseNodeInfo().then((x) => {
console.log('node info', x);
nodeInfo.value = x;
});
});
+2 -2
View File
@@ -39,7 +39,7 @@ const messages = computed(() => {
</tr>
<tr>
<td>Height</td>
<td>
<td class="text-primary">
<RouterLink
:to="`/${props.chain}/block/${tx.tx_response.height}`"
>{{ tx.tx_response.height }}</RouterLink
@@ -55,7 +55,7 @@ const messages = computed(() => {
tx.tx_response.code === 0 ? 'success' : 'error'
}`"
>
<span
<span
class="inset-x-0 inset-y-0 opacity-10 absolute"
:class="`bg-${
tx.tx_response.code === 0 ? 'success' : 'error'
@@ -155,7 +155,7 @@ function color(v: string) {
<tr v-for="v in list">
<td class=" capitalize">{{ v.chainName }}</td>
<td>{{ v.v.name }}</td>
<td><span class="badge " :class="color( v.sigingInfo?.missed_blocks_counter)">{{ v.sigingInfo?.missed_blocks_counter }}</span></td>
<td><span v-if="v.sigingInfo" class="badge " :class="color( v.sigingInfo?.missed_blocks_counter)">{{ v.sigingInfo?.missed_blocks_counter }}</span></td>
<td><span v-if="v.sigingInfo">{{ Number(v.sigingInfo.index_offset) - Number(v.sigingInfo.start_height) }}</span></td>
<td>
<div v-if="v.sigingInfo && !v.sigingInfo?.jailed_until.startsWith('1970')" class="text-xs flex flex-col">
+66 -50
View File
@@ -1,7 +1,6 @@
<script lang="ts" setup>
import { ref, onMounted, computed, watchEffect } from 'vue';
import { fromHex, toBase64 } from '@cosmjs/encoding';
import { Icon } from '@iconify/vue';
import {
useFormatter,
useStakingStore,
@@ -9,9 +8,8 @@ import {
useBlockchain,
} from '@/stores';
import UptimeBar from '@/components/UptimeBar.vue';
import type { Block, Commit } from '@/types';
import type { Commit, SlashingParam, SigningInfo } from '@/types';
import { consensusPubkeyToHexAddress, valconsToBase64 } from '@/libs';
import type { SigningInfo } from '@/types/slashing';
const props = defineProps(['chain']);
@@ -23,9 +21,11 @@ const latest = ref(0);
const commits = ref([] as Commit[]);
const keyword = ref('');
const live = ref(true);
const slashingParam = ref({} as SlashingParam)
const signingInfo = ref({} as Record<string, SigningInfo>);
const filterOptout =ref(false)
// filter validators by keywords
const validators = computed(() => {
if (keyword)
@@ -36,37 +36,45 @@ const validators = computed(() => {
});
const list = computed(() => {
return validators.value.map(v => ({
v,
signing: signingInfo.value[consensusPubkeyToHexAddress(v.consensus_pubkey)],
hex: toBase64(fromHex(consensusPubkeyToHexAddress(v.consensus_pubkey)))
}))
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(b => {
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 === 0) {
if (height > 50) {
// constructs sequence for loading blocks
let promise = Promise.resolve();
for (let i = height - 1; i > height - 50; i -= 1) {
if (i > height - 48) {
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();
});
}
})
);
}
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();
});
}
})
);
}
}
});
@@ -76,6 +84,10 @@ onMounted(() => {
signingInfo.value[valconsToBase64(i.address)] = i;
});
});
chainStore.rpc.getSlashingParams().then(x => {
slashingParam.value = x.params
})
});
const commits2 = computed(() => {
@@ -91,7 +103,6 @@ const tab = ref("3")
function changeTab(v: string) {
tab.value = v
}
</script>
<template>
@@ -109,7 +120,7 @@ function changeTab(v: string) {
@click="changeTab('2')"
>Blocks</a
>
<RouterLink :to="`/${chain}/uptime/overview`">
<RouterLink :to="`/${chain}/uptime/customize`">
<a
class="tab text-gray-400 capitalize"
>Customize</a
@@ -117,12 +128,12 @@ function changeTab(v: string) {
</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" />
<RouterLink class="btn btn-primary btn-sm" :to="`/${chain}/uptime/overview`">
<Icon icon="mdi-star" class="mr-2 text-lg" />
<span class="">Favorite</span>
</RouterLink>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-6 gap-x-4 mt-4" :class="tab === '2'?'':'hidden'">
<div v-for="({v, signing, hex}, i) in list" :key="i">
@@ -130,10 +141,10 @@ function changeTab(v: string) {
<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-error badge-sm text-white">
<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="mt-1 badge badge-sm text-white bg-yes border-0">
<div v-else class="badge badge-sm bg-transparent text-green-600 border-0">
{{ signing?.missed_blocks_counter }}
</div>
</div>
@@ -143,35 +154,40 @@ function changeTab(v: string) {
<div :class="tab === '3'?'':'hidden'">
<table class="table table-compact w-full mt-5">
<thead>
<thead class=" capitalize">
<tr>
<td rowspan="2">Validator</td>
<td rowspan="2">Start Height</td>
<td rowspan="2">Signed Blocks</td>
<td colspan="2">Missing blocks</td>
<td rowspan="2">Last Jailed Time</td>
<td rowspan="2">Tombstoned</td>
</tr>
<tr>
<td>In Window</td>
<td>Over All</td>
<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}, i) in list">
<td>{{ i+1 }}. {{ v.description.moniker }}</td>
<td>{{ signing?.start_height }}</td>
<td>{{ signing?.index_offset }}</td>
<td>
<span class="badge badge-sm text-white" :class="Number(signing?.missed_blocks_counter) < 10?'badge-success':'badge-error'">{{ signing?.missed_blocks_counter }}</span>
<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')">{{ format.percent(Number(signing.index_offset)/(latest-Number(signing.start_height))) }}</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-right text-xs">
{{ signing?.index_offset }}<br>
<span v-if="signing && signing.jailed_until.startsWith('1970')" class="text-xs">{{ format.percent(Number(signing.index_offset)/(latest-Number(signing.start_height))) }}</span>
</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>
+64
View File
@@ -0,0 +1,64 @@
<script lang="ts" setup>
import { useBaseStore, useBlockchain, useFormatter } from '@/stores';
import type { Connection } from '@/types';
import { computed } from 'vue';
import { onMounted } from 'vue';
import { ref } from 'vue';
const props = defineProps(['chain']);
const chainStore = useBlockchain();
const baseStore = useBaseStore()
const endpoint = ref(chainStore.current?.endpoints?.rest?.at(0)?.address)
const chainId = computed(() => baseStore.latest?.block?.header?.chain_id || "")
const chainName = computed(() => chainStore?.chainName || "")
const hdPath = computed(() => {
return `m/44'/${ chainStore.current?.coinType }/0'/0/0`
})
</script>
<template>
<div>
<div class="bg-base-100 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">Initial Setting</h2>
<div class="my-4 grid grid-flow-col auto-cols-max ">
<div class="form-control">
<div class="input-group">
<span>Endpoint</span>
<select v-model="endpoint" class="select select-bordered w-fit">
<option disabled selected>Select Endpoint</option>
<option v-for="v in chainStore.current?.endpoints.rest" :value="v.address">{{ v.address }}</option>
</select>
</div>
</div>
</div>
<span class="text-base">Import global script </span>
<div class="mockup-code my-2">
<pre data-prefix="1"><code class="text-gray-400">&lt;script type="module" src="https://unpkg.com/ping-widget@latest/dist/ping-widget.js"&gt;</code></pre>
</div>
</div>
<div class="bg-base-100 my-5 px-4 pt-3 pb-4 rounded shadow">
<h2 class="card-title">Widgets</h2>
<div class="mt-4">
<span class="text-base"> 1. Connect Wallet </span>
<div class="mockup-code my-2">
<pre data-prefix=">"><code class="text-gray-400">&lt;ping-connect-wallet chain-id="{{ chainId }}" hd-path="{{ hdPath }}"/&gt;</code></pre>
</div>
<span class="text-base"> 2. Osmosis Convert </span>
<div class="mockup-code my-2">
<pre data-prefix=">"><code class="text-gray-400">&lt;ping-token-convert chain-name="{{ chainName }}" endpoint="{{endpoint}}" hd-path="{{hdPath}}"/&gt;</code></pre>
<pre data-prefix=">"><code class="text-gray-400">&lt;label for="PingTokenConvert" class="btn btn-sm"&gt;Buy {{chainName.toUpperCase()}}&lt;/label&gt;</code></pre>
</div>
</div>
</div>
</div>
</template>
<route>
{
meta: {
i18n: 'widget',
order: 300
}
}
</route>
+2 -1
View File
@@ -1,13 +1,14 @@
{
"module": {
"dashboard": "Dashboard",
"blocks": "Blocks&Transaction",
"blocks": "Blocks",
"staking": "Staking",
"governance": "Governance",
"parameters": "Parameters",
"uptime": "Uptime",
"state-sync": "State Sync",
"cosmwasm": "Cosmwasm",
"widget": "Widgets",
"ibc": "IBC"
},
"index": {
@@ -12,6 +12,7 @@ import { filterInputProps, makeVInputProps } from 'vuetify/lib/components/VInput
import { filterInputAttrs } from 'vuetify/lib/util/helpers'
import { useThemeConfig } from '@core/composable/useThemeConfig'
import { ref } from 'vue'
const props = defineProps({
...makeVInputProps({
+1
View File
@@ -1,6 +1,7 @@
import type { ThemeConfig, UserThemeConfig } from './types'
import { RouteTransitions, Skins } from '@core/enums'
import type { UserConfig as LayoutConfig } from '@layouts/types'
import { ref } from 'vue';
export const defineThemeConfig = (
userConfig: UserThemeConfig,
@@ -9,6 +9,7 @@ import type { NavGroup, NavLink, NavSectionTitle, VerticalNavItems } from '@layo
import { themeConfig as config } from '@themeConfig'
console.log("v nav")
interface Props {
tag?: string | Component
navItems: VerticalNavItems
@@ -4,6 +4,7 @@ import { TransitionExpand, VerticalNavLink } from '@layouts/components'
import { themeConfig as config } from '@themeConfig'
import type { NavGroup } from '@layouts/types'
import { isNavGroupActive, openGroups } from '@layouts/utils'
import { ref } from 'vue'
const props = defineProps<{
item: NavGroup
@@ -1,8 +1,9 @@
import type { MaybeRef } from '@vueuse/shared'
import type { Ref } from 'vue'
import { computed, unref, type Ref, ref, watch } from 'vue'
import { AppContentLayoutNav, NavbarType } from '../enums'
import { themeConfig as config } from '@themeConfig'
import { injectionKeyIsVerticalNavHovered } from '@layouts'
import { useRoute } from 'vue-router'
export const useLayouts = () => {
const navbarType = computed({
+2
View File
@@ -1,5 +1,6 @@
import type { Router } from 'vue-router'
import type { NavGroup, NavLink, NavLinkProps } from '@layouts/types'
import { computed, ref } from 'vue'
export const openGroups = ref<string[]>([])
@@ -8,6 +9,7 @@ export const openGroups = ref<string[]>([])
* @param {Object, String} item navigation routeName or route Object provided in navigation data
*/
export const getComputedNavLinkToProp = computed(() => (link: NavLink) => {
console.log(link)
const props: NavLinkProps = {
target: link.target,
rel: link.rel,
+1 -1
View File
@@ -1,6 +1,6 @@
import { defineStore } from 'pinia';
export const useStoreName = defineStore('bankstore', {
export const useStoreName = defineStore('template', {
state: () => {
return {};
},
+1 -1
View File
@@ -78,7 +78,7 @@ export const useBaseStore = defineStore('baseStore', {
) === -1
) {
if (this.recents.length >= 50) {
this.recents.pop();
this.recents.shift();
}
this.recents.push(this.latest);
}
+18 -1
View File
@@ -1,4 +1,5 @@
import { defineStore } from 'pinia';
import { useTheme } from 'vuetify';
import {
useDashboard,
type ChainConfig,
@@ -14,6 +15,7 @@ import {
useGovStore,
useMintStore,
useStakingStore,
useWalletStore,
} from '.';
import { useBlockModule } from '@/modules/[chain]/block/block';
import { DEFAULT } from '@/libs';
@@ -50,6 +52,10 @@ export const useBlockchain = defineStore('blockchain', {
dashboard() {
return useDashboard();
},
isConsumerChain() {
// @ts-ignore
return this.current && this.current.providerChain
},
computedChainMenu() {
let currNavItem: VerticalNavItems = [];
@@ -61,8 +67,11 @@ export const useBlockchain = defineStore('blockchain', {
title: this.current?.prettyName || this.chainName || '',
icon: { image: this.current.logo, size: '22' },
i18n: false,
badgeContent: this.isConsumerChain? 'Consumer': undefined,
badgeClass: 'bg-error',
children: routes
.filter((x) => x.meta.i18n)
.filter((x) => x.meta.i18n) // defined menu name
.filter((x) => !this.current?.features || this.current.features.includes(String(x.meta.i18n))) // filter none-custom module
.map((x) => ({
title: `module.${x.meta.i18n}`,
to: { path: x.path.replace(':chain', this.chainName) },
@@ -112,6 +121,11 @@ export const useBlockchain = defineStore('blockchain', {
},
actions: {
async initial() {
// this.current?.themeColor {
// const { global } = useTheme();
// global.current
// }
useWalletStore().$reset()
await this.randomSetupEndpoint();
await useStakingStore().init();
useBankStore().initial();
@@ -146,5 +160,8 @@ export const useBlockchain = defineStore('blockchain', {
this.chainName = name;
}
},
supportModule(mod: string) {
return !this.current?.features || this.current.features.includes(mod)
}
},
});
+15
View File
@@ -61,6 +61,7 @@ export interface ChainConfig {
coinType: string;
assets: Asset[];
themeColor?: string;
features?: string[]
endpoints: {
rest?: Endpoint[];
rpc?: Endpoint[];
@@ -74,12 +75,18 @@ export interface ChainConfig {
};
exponent: string;
excludes?: string;
providerChain: {
api: Endpoint[]
};
}
export interface LocalConfig {
addr_prefix: string;
alias: string;
api: string[] | Endpoint[];
provider_chain: {
api: string[] | Endpoint[]
}
assets: {
base: string;
coingecko_id: string;
@@ -90,10 +97,12 @@ export interface LocalConfig {
chain_name: string;
coin_type: string;
logo: string;
theme_color?: string;
min_tx_fee: string;
rpc: string[] | Endpoint[];
sdk_version: string;
registry_name?: string;
features?: string[];
}
function apiConverter(api: any[]) {
@@ -135,6 +144,12 @@ export function fromLocal(lc: LocalConfig): ChainConfig {
rest: apiConverter(lc.api),
rpc: apiConverter(lc.rpc),
};
if(lc.provider_chain) {
conf.providerChain = {
api: apiConverter(lc.provider_chain.api)
}
}
conf.features = lc.features
conf.logo = lc.logo;
return conf;
}
-2
View File
@@ -156,7 +156,6 @@ export const useParamStore = defineStore('paramstore', {
// this.chain.items[chainIndex].value = `${percent(res)}%`
// })
const res = await this.getMintParam();
console.log(res, 'mint');
},
async handleSlashingParams() {
const res = await this.getSlashingParams();
@@ -200,7 +199,6 @@ export const useParamStore = defineStore('paramstore', {
this.nodeVersion.items = Object.entries(res.default_node_info).map(
([key, value]) => ({ subtitle: key, value: value })
);
console.log('handleAbciInfo', this.nodeVersion.items);
},
async getBaseTendermintBlockLatest() {
return await this.blockchain.rpc?.getBaseBlockLatest();
+19
View File
@@ -3,6 +3,9 @@ import { useBlockchain } from './useBlockchain';
import { get } from '@/libs/http';
import type { StakingParam, StakingPool, Validator } from '@/types';
import { CosmosRestClient } from '@/libs/client';
import { consensusPubkeyToHexAddress, valconsToBase64 } from '@/libs';
import { toHex, fromBase64 } from '@cosmjs/encoding';
export const useStakingStore = defineStore('stakingStore', {
state: () => {
@@ -77,6 +80,20 @@ export const useStakingStore = defineStore('stakingStore', {
);
},
async fetchValidators(status: string) {
if(this.blockchain.isConsumerChain) {
if(this.blockchain.current?.providerChain.api && this.blockchain.current.providerChain.api.length > 0) {
const client = CosmosRestClient.newDefault(this.blockchain.current.providerChain.api[0].address)
// const vals = await client.getBaseValidatorsetLatest(0)
const res = await client.getStakingValidators(status)
const proVals = res.validators.sort(
(a, b) => Number(b.delegator_shares) - Number(a.delegator_shares)
)
if (status === 'BOND_STATUS_BONDED') {
this.validators = proVals;
}
return proVals
}
}
return this.blockchain.rpc?.getStakingValidators(status).then((res) => {
const vals = res.validators.sort(
(a, b) => Number(b.delegator_shares) - Number(a.delegator_shares)
@@ -89,3 +106,5 @@ export const useStakingStore = defineStore('stakingStore', {
},
},
});
+2 -1
View File
@@ -7,4 +7,5 @@ export * from './distribution'
export * from './gov'
export * from './staking'
export * from './tx'
export * from './ibc'
export * from './ibc'
export * from './slashing'
+14 -6
View File
@@ -1,14 +1,22 @@
import type { PaginatedResponse } from "./common";
export interface SigningInfo {
"address": string,
"start_height": string,
"index_offset": string,
"jailed_until": string,
"tombstoned": boolean,
"missed_blocks_counter": string
address: string,
start_height: string,
index_offset: string,
jailed_until: string,
tombstoned: boolean,
missed_blocks_counter: string
}
export interface SlashingParam{
signed_blocks_window: string,
min_signed_per_window: string,
downtime_jail_duration: string,
slash_fraction_double_sign: string,
slash_fraction_downtime: string
}
export interface PaginatedSigningInfo extends PaginatedResponse {
info: SigningInfo[]
}