Add slots inside of storage request details

This commit is contained in:
Eric 2024-06-06 15:28:29 +10:00
parent 552cc2705b
commit 83ab353038
No known key found for this signature in database
8 changed files with 268 additions and 207 deletions

View File

@ -1,124 +1,52 @@
<script setup>
import { onMounted } from 'vue'
import { initModals } from 'flowbite'
import { useRequestsStore } from '@/stores/requests'
import { storeToRefs } from 'pinia'
const requestsStore = useRequestsStore()
// const ethProvider = inject('ethProvider')
// const marketplace = inject('marketplace')
// const codexApi = inject('codexApi')
// const token = inject('token')
// const blockNumber = ref(0)
// const requests = ref([])
// const onStorageRequested = async ({requestId, ask, expiry, blockNumber}) => {
// let request = await marketplace.getRequest(requestId)
// let stateIdx = await marketplace.requestState(requestId)
// let cid = request[2][0]
// let state = requestState[stateIdx]
// var imgSrc = ""
// var error = ""
// if (state == "New" || state == "Fulfilled"){
// try {
// let res = await codexApi.download(cid)
// try{
// const blob = await res.blob()
// imgSrc = URL.createObjectURL(blob)
// } catch (e) {
// error = `not an image (error: ${error.message})`
// }
// } catch(error) {
// error = `failed to download cid data: ${error}`
// }
// }
// requests.value.push({
// blockNumber,
// cid,
// requestId,
// ask,
// expiry,
// state,
// imgSrc,
// error
// })
// }
// onMounted(async () => {
// await requestsStore.fetch()
// await requestsStore.listenForNewEvents()
// })
// let storageRequestedFilter = marketplace.filters.StorageRequested
// marketplace.on(storageRequestedFilter, async (requestId, ask, expiry, event) => {
// let {blockNumber} = event
// // onStorageRequested({
// // blockNumber,
// // requestId,
// // ask,
// // expiry,
// // })
// let request = await marketplace.getRequest(requestId)
// let state = await getRequestState(requestId)
// onStorageRequested(blockNumber, request, state)
// })
// // query past events
// let requests = (await marketplace.queryFilter(storageRequestedFilter))
// requests.forEach(async (event) => {
// let {requestId, ask, expiry} = event.args
// let {blockNumber} = event
// // onStorageRequested({
// // blockNumber,
// // requestId,
// // ask,
// // expiry
// // })
// let request = await marketplace.getRequest(requestId)
// let state = await getRequestState(requestId)
// onStorageRequested(blockNumber, request, state)
// })
// let slotFreedFilter = marketplace.filters.SlotFreed
// marketplace.on(slotFreedFilter, (requestId, ask, expiry, event) => {
// let {blockNumber} = event
// onSlotFreed({
// blockNumber,
// requestId,
// ask,
// expiry,
// })
// })
// let slotsFreed = (await marketplace.queryFilter(slotFreedFilter))
// slotsFreed.forEach(event => {
// let {requestId, ask, expiry} = event.args
// let {blockNumber} = event
// onSlotFreed({
// blockNumber,
// requestId,
// ask,
// expiry
// })
// })
// })
// console.log(await ethProvider.getBlockNumber())
const { requests } = storeToRefs(requestsStore)
import { getStateColour } from '@/utils/slots'
import { shortHex } from '@/utils/ids'
import StateIndicator from '@/components/StateIndicator.vue'
defineProps({
slots: {
type: Array,
required: true
}
})
</script>
<template>
<!-- <span>asdf{{ blockNumber.value }}</span> -->
<!-- <span>Yo yo yo yo yo {{ blockNumber }}</span> -->
<ul class="requests">
<!-- <li v-for="(request, idx) in requestsStore.requests" :key="{requestId}"> WORKS! -->
<li v-for="([requestId, { request, state }], idx) in requests" :key="{ requestId }">
{{ idx }}.
<div>CID: {{ request[2][0] }}</div>
<div>RequestID: {{ requestId }}</div>
<div>State: {{ state }}</div>
<CodexImage v-if="state == 'New' || state == 'Fulfilled'" cid="cid" />
<!-- <div v-if="error">{{error}}</div>
<img v-else-if="imgSrc" :src="imgSrc" width="100%"/> -->
</li>
</ul>
<div
class="relative overflow-x-auto overflow-y-auto max-h-screen shadow-md sm:rounded-lg border-t border-gray-50"
>
<table class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400">
<thead class="text-xs text-gray-700 uppercase bg-gray-50 dark:bg-gray-700 dark:text-gray-400">
<tr>
<th scope="col" class="px-6 py-3">SlotID</th>
<th scope="col" class="px-6 py-3">Index</th>
<th scope="col" class="px-6 py-3">Proofs Missed</th>
<th scope="col" class="px-6 py-3">Provider</th>
<th scope="col" class="px-6 py-3">State</th>
</tr>
</thead>
<tbody>
<tr
v-for="({ slotId, slotIdx, state, proofsMissed, provider }, idx) in slots"
:key="{ slotId }"
class="bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-600"
>
<th
scope="row"
class="flex items-center px-6 py-4 font-medium text-gray-900 whitespace-nowrap dark:text-white"
>
<div class="text-base font-semibold">{{ shortHex(slotId) }}</div>
</th>
<td class="px-6 py-4">{{ slotIdx }}</td>
<td class="px-6 py-4">{{ proofsMissed }}</td>
<td class="px-6 py-4">{{ shortHex(provider) }}</td>
<td class="px-6 py-4">
<div class="flex items-center">
<StateIndicator :text="state" :color="getStateColour(state)"></StateIndicator>
</div>
</td>
</tr>
</tbody>
</table>
</div>
</template>

View File

@ -0,0 +1,72 @@
<script setup>
import { computed } from 'vue'
const props = defineProps({
color: {
type: String,
required: true,
validator(value, props) {
// The value must match one of these strings
return ['green', 'blue', 'gray', 'yellow', 'red'].includes(value)
}
},
text: {
type: String,
required: true
}
})
const theme = computed(() => {
if (props.color == 'blue') {
return {
bg: 'bg-blue-100',
bgDark: 'dark:bg-blue-900',
bgDot: 'bg-blue-500',
text: 'text-blue-800',
textDark: 'dark:text-blue-300'
}
} else if (props.color === 'gray') {
return {
bg: 'bg-gray-100',
bgDark: 'dark:bg-gray-900',
bgDot: 'bg-gray-500',
text: 'text-gray-800',
textDark: 'dark:text-gray-300'
}
} else if (props.color === 'yellow') {
return {
bg: 'bg-yellow-100',
bgDark: 'dark:bg-yellow-900',
bgDot: 'bg-yellow-500',
text: 'text-yellow-800',
textDark: 'dark:text-yellow-300'
}
} else if (props.color === 'red') {
return {
bg: 'bg-red-100',
bgDark: 'dark:bg-red-900',
bgDot: 'bg-red-500',
text: 'text-red-800',
textDark: 'dark:text-red-300'
}
} else {
return {
bg: 'bg-green-100',
bgDark: 'dark:bg-green-900',
bgDot: 'bg-green-500',
text: 'text-green-800',
textDark: 'dark:text-green-300'
}
}
})
</script>
<template>
<span
class="inline-flex items-center text-xs font-medium px-2.5 py-0.5 rounded-full"
:class="[theme.bg, theme.text, theme.bgDark, theme.textDark]"
>
<span class="w-2 h-2 me-1 rounded-full" :class="theme.bgDot"></span>
{{ text }}
</span>
</template>

View File

@ -1,9 +1,11 @@
<script setup>
import { computed } from 'vue'
import { price } from '@/utils/requests'
import { getStateColour, price } from '@/utils/requests'
import CodexImage from '@/components/CodexImage.vue'
import StateIndicator from '@/components/StateIndicator.vue'
import { shortHex } from '@/utils/ids'
import Slots from './Slots.vue'
const props = defineProps({
requestId: {
@ -21,77 +23,107 @@ const totalPrice = computed(() => price(props.request))
<template>
<div class="flex">
<CodexImage class="flex-2" :cid="props.request.content.cid"></CodexImage>
<CodexImage class="flex-2" :cid="request.content.cid"></CodexImage>
<div class="py-8 px-4 mx-auto max-w-2xl lg:py-16">
<h2 class="mb-2 text-xl font-semibold leading-none text-gray-900 md:text-2xl dark:text-white">
{{ shortHex(requestId) }}
{{ shortHex(requestId, 12) }}
</h2>
<p class="mb-4 text-xl font-extrabold leading-none text-gray-900 md:text-2xl dark:text-white">
{{ totalPrice }} CDX
</p>
<StateIndicator
class="mb-4"
:text="request.state"
:color="getStateColour(request.state)"
></StateIndicator>
<dl>
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Dataset CID</dt>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">
{{ request.content.cid }}
</dd>
</dl>
<dl class="flex items-center space-x-6">
<div>
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Expiry</dt>
<dl>
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Client</dt>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">
{{ request.expiry }}s
{{ request.client }}
</dd>
</div>
<div>
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Duration</dt>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">
{{ request.ask.duration }}s
</dd>
</div>
</dl>
<div class="flex items-center space-x-4">
<button
type="button"
class="text-white inline-flex items-center bg-primary-700 hover:bg-primary-800 focus:ring-4 focus:outline-none focus:ring-primary-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-primary-600 dark:hover:bg-primary-700 dark:focus:ring-primary-800"
<dl>
<dt class="mb-2 font-semibold leading-none text-gray-900 dark:text-white">Merkle root</dt>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">
{{ request.content.merkleRoot }}
</dd>
</dl>
<div class="relative overflow-x-auto overflow-y-auto max-h-screen mb-10">
<table
class="w-full text-sm text-left rtl:text-right text-gray-500 dark:text-gray-400 mx-auto"
>
<svg
aria-hidden="true"
class="mr-1 -ml-1 w-5 h-5"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
<tbody>
<tr class="bg-white dark:bg-transparent hover:bg-gray-50 dark:hover:bg-gray-600 text-base">
<td
class="flex items-center pr-1 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white border-r"
>
<path
d="M17.414 2.586a2 2 0 00-2.828 0L7 10.172V13h2.828l7.586-7.586a2 2 0 000-2.828z"
></path>
<path
fill-rule="evenodd"
d="M2 6a2 2 0 012-2h4a1 1 0 010 2H4v10h10v-4a1 1 0 112 0v4a2 2 0 01-2 2H4a2 2 0 01-2-2V6z"
clip-rule="evenodd"
></path>
</svg>
Edit
</button>
<button
type="button"
class="inline-flex items-center text-white bg-red-600 hover:bg-red-700 focus:ring-4 focus:outline-none focus:ring-red-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-red-500 dark:hover:bg-red-600 dark:focus:ring-red-900"
Expiry
</td>
<td class="px-6 py-2 font-light">{{ request.expiry }} seconds</td>
</tr>
<tr class="bg-white dark:bg-transparent hover:bg-gray-50 dark:hover:bg-gray-600 text-base">
<td
class="flex items-center pr-1 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white border-r"
>
<svg
aria-hidden="true"
class="w-5 h-5 mr-1.5 -ml-1"
fill="currentColor"
viewBox="0 0 20 20"
xmlns="http://www.w3.org/2000/svg"
Duration
</td>
<td class="px-6 py-2 font-light">{{ request.ask.duration }} seconds</td>
</tr>
<tr class="bg-white dark:bg-transparent hover:bg-gray-50 dark:hover:bg-gray-600 text-base">
<td
class="flex items-center pr-1 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white border-r"
>
<path
fill-rule="evenodd"
d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z"
clip-rule="evenodd"
></path>
</svg>
Delete
</button>
Slot size
</td>
<td class="px-6 py-2 font-light">{{ request.ask.slotSize }} bytes</td>
</tr>
<tr class="bg-white dark:bg-transparent hover:bg-gray-50 dark:hover:bg-gray-600 text-base">
<td
class="flex items-center pr-1 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white border-r"
>
Proof probability
</td>
<td class="px-6 py-2 font-light">{{ request.ask.proofProbability }}</td>
</tr>
<tr class="bg-white dark:bg-transparent hover:bg-gray-50 dark:hover:bg-gray-600 text-base">
<td
class="flex items-center pr-1 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white border-r"
>
Reward
</td>
<td class="px-6 py-2 font-light">{{ request.ask.reward }} CDX</td>
</tr>
<tr
class="bg-white dark:bg-transparent hover:bg-gray-50 dark:hover:tbg-gray-600 text-base"
>
<td
class="flex items-center pr-1 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white border-r"
>
Collateral
</td>
<td class="px-6 py-2 font-light">{{ request.ask.collateral }} CDX</td>
</tr>
<tr class="bg-white dark:bg-transparent hover:bg-gray-50 dark:hover:bg-gray-600 text-base">
<td
class="flex items-center pr-1 py-2 font-medium text-gray-900 whitespace-nowrap dark:text-white border-r"
>
Max slot loss
</td>
<td class="px-6 py-2 font-light">
{{ request.ask.maxSlotLoss }} {{ request.ask.maxSlotLoss == 1 ? 'slot' : 'slots' }}
</td>
</tr>
</tbody>
</table>
</div>
<Slots :slots="request.slots"></Slots>
</div>
</div>
</template>

View File

@ -5,21 +5,13 @@ import { useRouter } from 'vue-router'
import { useRequestsStore } from '@/stores/requests'
import { storeToRefs } from 'pinia'
import CodexImage from '@/components/CodexImage.vue'
import StateIndicator from '@/components/StateIndicator.vue'
import { shortHex } from '@/utils/ids'
import { getStateColour } from '@/utils/requests'
const requestsStore = useRequestsStore()
const { requests } = storeToRefs(requestsStore)
const router = useRouter()
function getStateColour(state) {
if (state === 'New') {
return 'bg-yellow-200'
} else if (state === 'Fulfilled') {
return 'bg-green-500'
} else {
return 'bg-red-500'
}
}
</script>
<template>
@ -159,8 +151,7 @@ function getStateColour(state) {
<td class="px-6 py-4">{{ shortHex(content.cid) }}</td>
<td class="px-6 py-4">
<div class="flex items-center">
<div :class="`h-2.5 w-2.5 rounded-full ${getStateColour(state)} me-2`"></div>
{{ state }}
<StateIndicator :text="state" :color="getStateColour(state)"></StateIndicator>
</div>
</td>
<td class="px-6 py-4">

View File

@ -1,7 +1,8 @@
import { ref, computed, inject } from 'vue'
import { ref, inject } from 'vue'
import { defineStore } from 'pinia'
import { slotId } from '../utils/ids'
import { arrayToObject } from '@/utils/requests'
import { arrayToObject, requestState } from '@/utils/requests'
import { slotState } from '@/utils/slots'
export const useRequestsStore = defineStore('request', () => {
// let fetched = false
@ -50,36 +51,31 @@ export const useRequestsStore = defineStore('request', () => {
// onSlotFreed => update slots[slotId].state with getSlotState
// => add to slotFreedEvents {blockNumber, slotId, slotIdx}
// => add blockNumber to blockNumbers
const requestState = [
'New', // [default] waiting to fill slots
'Fulfilled', // all slots filled, accepting regular proofs
'Cancelled', // not enough slots filled before expiry
'Finished', // successfully completed
'Failed' // too many nodes have failed to provide proofs, data lost
]
const getRequestState = async (requestId) => {
let stateIdx = await marketplace.requestState(requestId)
return requestState[stateIdx]
}
const getSlotState = async (slotId) => {
let stateIdx = await marketplace.slotState(slotId)
return slotState[stateIdx]
}
const getSlots = async (requestId, numSlots) => {
// storageRequestedEvents.value.push({ blockNumber, requestId })
let slots = []
for (let i = 0; i < numSlots; i++) {
let id = slotId(requestId, i)
let state = await marketplace.slotState(id)
slots.push({ slotId: id, slotIdx: i, state })
for (let slotIdx = 0; slotIdx < numSlots; slotIdx++) {
let id = slotId(requestId, slotIdx)
let state = await getSlotState(id)
let proofsMissed = await marketplace.missingProofs(id)
let provider = await marketplace.getHost(id)
slots.push({ slotId: id, slotIdx, state, proofsMissed, provider })
}
return slots
// blockNumbers.value.add(blockNumber)
}
async function fetch() {
// if (fetched) {
// // allow multiple calls without re-fetching
// return
// }
// query past events
loading.value = true
try {

View File

@ -33,14 +33,14 @@ export function short(long, ellipses = '*', start = 3, stop = 6) {
return short
}
export function shortHex(long) {
export function shortHex(long, chars = 4) {
let shortened = ''
let rest = long
if (long.substring(0, 2) === '0x') {
shortened = '0x'
rest = long.substring(2)
}
shortened += short(rest, '..', 4, 4)
shortened += short(rest, '..', chars, chars)
return shortened
}

View File

@ -28,3 +28,23 @@ export function price(request) {
}
return request.ask.reward * request.ask.duration * request.ask.slots
}
export function getStateColour(state) {
if (state === 'New') {
return 'yellow'
} else if (state === 'Fulfilled') {
return 'green'
} else if (state === 'Finished') {
return 'gray'
} else {
return 'red'
}
}
export const requestState = [
'New', // [default] waiting to fill slots
'Fulfilled', // all slots filled, accepting regular proofs
'Cancelled', // not enough slots filled before expiry
'Finished', // successfully completed
'Failed' // too many nodes have failed to provide proofs, data lost
]

22
src/utils/slots.js Normal file
View File

@ -0,0 +1,22 @@
export const slotState = [
'Free', // [default] not filled yet, or host has vacated the slot
'Filled', // host has filled slot
'Finished', // successfully completed
'Failed', // the request has failed
'Paid', // host has been paid
'Cancelled' // when request was cancelled then slot is cancelled as well
]
export function getStateColour(state) {
if (state === 'Free') {
return 'yellow'
} else if (state === 'Filled') {
return 'green'
} else if (state === 'Finished') {
return 'blue'
} else if (state === 'Paid') {
return 'gray'
} else {
return 'red'
}
}