add uptime overview
This commit is contained in:
parent
c35b07ee49
commit
8f1561dd74
@ -23,16 +23,6 @@ const commits = ref([] as Commit[]);
|
|||||||
const keyword = ref('');
|
const keyword = ref('');
|
||||||
const live = ref(true);
|
const live = ref(true);
|
||||||
|
|
||||||
// storage local favorite validator ids
|
|
||||||
const local = ref(
|
|
||||||
JSON.parse(localStorage.getItem('uptime-validators') || '{}') as Record<
|
|
||||||
string,
|
|
||||||
string[]
|
|
||||||
>
|
|
||||||
);
|
|
||||||
const currentPined = local.value[chainStore.chainName]
|
|
||||||
const selected = ref(currentPined || []); // favorite validators on selected blockchain
|
|
||||||
|
|
||||||
const signingInfo = ref({} as Record<string, SigningInfo>);
|
const signingInfo = ref({} as Record<string, SigningInfo>);
|
||||||
|
|
||||||
// filter validators by keywords
|
// filter validators by keywords
|
||||||
@ -90,10 +80,17 @@ onUnmounted(() => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
watchEffect(() => {
|
// watchEffect((x) => {
|
||||||
local.value[chainStore.chainName] = selected.value;
|
// const list = selected.value.map(x => {
|
||||||
localStorage.setItem('uptime-validators', JSON.stringify(local.value));
|
// const val = stakingStore.validators.find(v => v.operator_address === x)
|
||||||
});
|
// return {
|
||||||
|
// name: val?.description.moniker || "",
|
||||||
|
// address: x
|
||||||
|
// }
|
||||||
|
// })
|
||||||
|
// local.value[chainStore.chainName] = list;
|
||||||
|
// localStorage.setItem('uptime-validators', JSON.stringify(local.value));
|
||||||
|
// });
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -105,10 +102,10 @@ watchEffect(() => {
|
|||||||
placeholder="Keywords to filter validators"
|
placeholder="Keywords to filter validators"
|
||||||
class="input input-sm w-full flex-1"
|
class="input input-sm w-full flex-1"
|
||||||
/>
|
/>
|
||||||
<button class="btn btn-primary btn-sm">
|
<RouterLink class="btn btn-primary btn-sm" :to="`/${chain}/uptime/overview`">
|
||||||
<Icon icon="mdi-star" class="mr-2 text-lg" />
|
<Icon icon="mdi-star" class="mr-2 text-lg" />
|
||||||
<span class="">Favorite</span>
|
<span class="">Favorite</span>
|
||||||
</button>
|
</RouterLink>
|
||||||
</div>
|
</div>
|
||||||
<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="grid grid-cols-1 md:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-6 gap-x-4 mt-4"
|
||||||
@ -116,10 +113,6 @@ watchEffect(() => {
|
|||||||
<div v-for="(v, i) in validators" :key="i" >
|
<div v-for="(v, i) in validators" :key="i" >
|
||||||
<div class="flex items-center justify-between py-0">
|
<div class="flex items-center justify-between py-0">
|
||||||
<label class="text-truncate text-sm">
|
<label class="text-truncate text-sm">
|
||||||
<input type="checkbox"
|
|
||||||
v-model="selected"
|
|
||||||
:value="v.operator_address"
|
|
||||||
/>
|
|
||||||
<span class="ml-1 text-black dark:text-white">{{ i + 1 }}.{{ v.description.moniker }}</span>
|
<span class="ml-1 text-black dark:text-white">{{ i + 1 }}.{{ v.description.moniker }}</span>
|
||||||
</label>
|
</label>
|
||||||
<div
|
<div
|
||||||
|
188
src/modules/[chain]/uptime/overview.vue
Normal file
188
src/modules/[chain]/uptime/overview.vue
Normal file
@ -0,0 +1,188 @@
|
|||||||
|
<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,
|
||||||
|
useBaseStore,
|
||||||
|
useBlockchain,
|
||||||
|
useDashboard,
|
||||||
|
} from '@/stores';
|
||||||
|
import UptimeBar from '@/components/UptimeBar.vue';
|
||||||
|
import type { Block, Commit } from '@/types';
|
||||||
|
import { consensusPubkeyToHexAddress, valconsToBase64 } from '@/libs';
|
||||||
|
import type { SigningInfo } from '@/types/slashing';
|
||||||
|
import { CosmosRestClient } from '@/libs/client';
|
||||||
|
|
||||||
|
const props = defineProps(['chain']);
|
||||||
|
|
||||||
|
const stakingStore = useStakingStore();
|
||||||
|
const format = useFormatter();
|
||||||
|
const chainStore = useBlockchain();
|
||||||
|
const dashboard = useDashboard()
|
||||||
|
// storage local favorite validator ids
|
||||||
|
const local = ref(JSON.parse(localStorage.getItem('uptime-validators') || '{}') as Record<string, {name: string, address: string}[]>)
|
||||||
|
const signingInfo = ref({} as Record<string, SigningInfo[]>);
|
||||||
|
const selected = ref([] as string[])
|
||||||
|
const selectChain = ref(chainStore.chainName)
|
||||||
|
const validators = ref(stakingStore.validators)
|
||||||
|
const keyword = ref("")
|
||||||
|
|
||||||
|
if(local.value) Object.keys(local.value).map(chainName => {
|
||||||
|
const chain = dashboard.chains[chainName]
|
||||||
|
if(chain && chain.endpoints.rest) {
|
||||||
|
const client = CosmosRestClient.newDefault(chain.endpoints.rest[0].address)
|
||||||
|
client.getSlashingSigningInfos().then( resp => {
|
||||||
|
signingInfo.value[chainName] = resp.info
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if(chainName === selectChain.value) {
|
||||||
|
const vals = local.value[chainName]
|
||||||
|
if(vals) {
|
||||||
|
selected.value = vals.map(x => x.address)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain
|
||||||
|
})
|
||||||
|
|
||||||
|
const filterValidators = computed(() => {
|
||||||
|
if(keyword.value) {
|
||||||
|
return validators.value.filter(x => x.description.moniker.indexOf(keyword.value) > -1)
|
||||||
|
}
|
||||||
|
return validators.value
|
||||||
|
})
|
||||||
|
|
||||||
|
const list = computed(() => {
|
||||||
|
const list = [] as any[]
|
||||||
|
if(local.value) Object.keys(local.value).map( chainName => {
|
||||||
|
const vals = local.value[chainName]
|
||||||
|
const info = signingInfo.value[chainName]
|
||||||
|
if(vals && info) {
|
||||||
|
vals.forEach(v => {
|
||||||
|
const sigingInfo = info.find(x => valconsToBase64(x.address) === v.address)
|
||||||
|
list.push( {
|
||||||
|
chainName,
|
||||||
|
v,
|
||||||
|
sigingInfo,
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return list
|
||||||
|
})
|
||||||
|
|
||||||
|
function add() {
|
||||||
|
const newList = [] as { name: string; address: string; }[]
|
||||||
|
selected.value.forEach(x => {
|
||||||
|
const validator = validators.value.find(v => (consensusPubkeyToHexAddress(v.consensus_pubkey) === x))
|
||||||
|
if(validator) newList.push({
|
||||||
|
name: validator.description.moniker || x,
|
||||||
|
address: x
|
||||||
|
})
|
||||||
|
})
|
||||||
|
if(!local.value) local.value = {}
|
||||||
|
local.value[selectChain.value] = newList
|
||||||
|
localStorage.setItem("uptime-validators", JSON.stringify(local.value))
|
||||||
|
}
|
||||||
|
|
||||||
|
function changeChain() {
|
||||||
|
validators.value = []
|
||||||
|
const endpoint = dashboard.chains[selectChain.value].endpoints.rest?.at(0)?.address
|
||||||
|
if(!endpoint) return
|
||||||
|
|
||||||
|
const client = CosmosRestClient.newDefault(endpoint)
|
||||||
|
client.getStakingValidators("BOND_STATUS_BONDED").then(x => {
|
||||||
|
validators.value = x.validators
|
||||||
|
})
|
||||||
|
|
||||||
|
const vals = local.value[selectChain.value]
|
||||||
|
if(vals) {
|
||||||
|
selected.value = vals.map(x => x.address)
|
||||||
|
} else {
|
||||||
|
selected.value = []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function color(v: string) {
|
||||||
|
if(v) {
|
||||||
|
const n = Number(v)
|
||||||
|
if(n < 10) return " badge-success"
|
||||||
|
if(n > 1000) return " badge-error"
|
||||||
|
if(n > 0) return " badge-warning"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<table class="table w-full">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Blockchain</th>
|
||||||
|
<th>Validator</th>
|
||||||
|
<th>Missing Blocks</th>
|
||||||
|
<th>Signed Blocks</th>
|
||||||
|
<th>Last Jailed Time</th>
|
||||||
|
<th>Tombstoned</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<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">{{ 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">
|
||||||
|
<div class="badge">{{ format.toDay(v.sigingInfo.jailed_until, 'from') }}</div>
|
||||||
|
<div>{{v.sigingInfo?.jailed_until }}</div>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td class=" capitalize">{{ v.sigingInfo?.tombstoned }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
|
||||||
|
<label for="add-validator" class="btn btn-primary mt-5">Add Validators</label>
|
||||||
|
|
||||||
|
<!-- Put this part before </body> tag -->
|
||||||
|
<input type="checkbox" id="add-validator" class="modal-toggle" />
|
||||||
|
<div class="modal">
|
||||||
|
<div class="modal-box relative">
|
||||||
|
<label for="add-validator" class="btn btn-sm btn-circle absolute right-2 top-2">✕</label>
|
||||||
|
<h3 class="text-lg font-bold">Add Validators</h3>
|
||||||
|
<div class="py-4 max-h-60 overflow-y-auto">
|
||||||
|
<div class="form-control my-5 border-2">
|
||||||
|
<div class="input-group input-group-md">
|
||||||
|
<select v-model="selectChain" class="select select-bordered capitalize" @change="changeChain">
|
||||||
|
<option v-for="v in dashboard.chains" :value="v.chainName">
|
||||||
|
{{ v.chainName }}
|
||||||
|
</option>
|
||||||
|
</select>
|
||||||
|
<input v-model="keyword" type="text" class="input w-full" placeholder="keywords to filter validator">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<table class="table table-compact w-full hover">
|
||||||
|
<thead>
|
||||||
|
<tr><th>Validator</th><th></th></tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr v-for="v in filterValidators">
|
||||||
|
<td><label :for="v.operator_address"><div class=" w-full">{{ v.description.moniker }}</div></label></td>
|
||||||
|
<td><input :id="v.operator_address" v-model="selected" class="checkbox" type="checkbox" :value="consensusPubkeyToHexAddress(v.consensus_pubkey)"/></td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div class="modal-action">
|
||||||
|
<label for="add-validator" class="btn" @click="add">add</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="h-6"></div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
Loading…
Reference in New Issue
Block a user