Add storage request detail view

This commit is contained in:
Eric 2024-06-06 13:04:36 +10:00
parent 6a55e88fc5
commit 552cc2705b
No known key found for this signature in database
10 changed files with 225 additions and 221 deletions

View File

@ -1,6 +1,6 @@
<script setup>
import { onMounted, ref } from 'vue'
import { useEventsStore } from '@/stores/events'
import { onBeforeMount, onMounted, ref } from 'vue'
import { useRequestsStore } from '@/stores/requests'
import { RouterView } from 'vue-router'
import Balance from '@/components/Balance.vue'
import BlockNumber from '@/components/BlockNumber.vue'
@ -10,7 +10,7 @@ import { initDrawers } from 'flowbite'
const alerts = ref([])
const id = ref(0)
const eventsStore = useEventsStore()
const requestsStore = useRequestsStore()
function addAlert(type, event, state) {
alerts.value.push({
@ -35,9 +35,11 @@ function addSlotAlert(type, event, state) {
})
}
onBeforeMount(async () => {})
onMounted(async () => {
await requestsStore.fetch()
initDrawers()
await eventsStore.fetchPastEvents()
function onStorageRequested(blockNumber, requestId, request, state) {
alerts.value.push({
@ -104,7 +106,7 @@ onMounted(async () => {
state: 'Filled'
})
}
await eventsStore.listenForNewEvents(
await requestsStore.listenForNewEvents(
onStorageRequested,
onRequestFulfilled,
onRequestCancelled,

View File

@ -1,5 +1,5 @@
<script setup>
import { inject, ref, onMounted } from 'vue'
import { inject, ref, onMounted, computed } from 'vue'
import { initModals } from 'flowbite'
import SpinnerLoading from '@/components/SpinnerLoading.vue'
@ -13,10 +13,8 @@ defineOptions({
})
const props = defineProps({
// Required string
cid: {
type: String,
required: true
type: [String, undefined]
},
localOnly: {
// only try downloading from the local node
@ -25,8 +23,12 @@ const props = defineProps({
},
alt: String
})
const hidden = computed(() => props.cid === undefined)
onMounted(async () => {
if (hidden.value) {
return
}
loading.value = true
try {
@ -52,7 +54,7 @@ onMounted(async () => {
</script>
<template>
<div class="text-center">
<div v-if="!hidden" class="text-center">
<SpinnerLoading v-if="loading" />
<div v-else-if="error" v-bind="$attrs" class="dark:bg-orange-700 dark:text-orange-200">
<svg

View File

@ -1,10 +1,10 @@
<script setup>
import { onMounted } from 'vue'
import { initModals } from 'flowbite'
import { useEventsStore } from '@/stores/events'
import { useRequestsStore } from '@/stores/requests'
import { storeToRefs } from 'pinia'
const eventsStore = useEventsStore()
const requestsStore = useRequestsStore()
// const ethProvider = inject('ethProvider')
// const marketplace = inject('marketplace')
@ -45,8 +45,8 @@ const eventsStore = useEventsStore()
// })
// }
// onMounted(async () => {
// await eventsStore.fetchPastEvents()
// await eventsStore.listenForNewEvents()
// await requestsStore.fetch()
// await requestsStore.listenForNewEvents()
// })
// let storageRequestedFilter = marketplace.filters.StorageRequested
// marketplace.on(storageRequestedFilter, async (requestId, ask, expiry, event) => {
@ -103,14 +103,14 @@ const eventsStore = useEventsStore()
// })
// console.log(await ethProvider.getBlockNumber())
const { requests } = storeToRefs(eventsStore)
const { requests } = storeToRefs(requestsStore)
</script>
<template>
<!-- <span>asdf{{ blockNumber.value }}</span> -->
<!-- <span>Yo yo yo yo yo {{ blockNumber }}</span> -->
<ul class="requests">
<!-- <li v-for="(request, idx) in eventsStore.requests" :key="{requestId}"> WORKS! -->
<!-- <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>

View File

@ -0,0 +1,97 @@
<script setup>
import { computed } from 'vue'
import { price } from '@/utils/requests'
import CodexImage from '@/components/CodexImage.vue'
import { shortHex } from '@/utils/ids'
const props = defineProps({
requestId: {
type: String,
required: true
},
request: {
type: Object,
required: true
}
})
const totalPrice = computed(() => price(props.request))
</script>
<template>
<div class="flex">
<CodexImage class="flex-2" :cid="props.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) }}
</h2>
<p class="mb-4 text-xl font-extrabold leading-none text-gray-900 md:text-2xl dark:text-white">
{{ totalPrice }} CDX
</p>
<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>
<dd class="mb-4 font-light text-gray-500 sm:mb-5 dark:text-gray-400">
{{ request.expiry }}s
</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"
>
<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"
>
<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"
>
<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"
>
<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>
</div>
</div>
</div>
</template>

View File

@ -1,13 +1,15 @@
<script setup>
import { inject, ref, onMounted, computed } from 'vue'
import { initModals } from 'flowbite'
import { useEventsStore } from '@/stores/events'
import { useRouter } from 'vue-router'
import { useRequestsStore } from '@/stores/requests'
import { storeToRefs } from 'pinia'
import CodexImage from '@/components/CodexImage.vue'
import { shortHex } from '@/utils/ids'
const eventsStore = useEventsStore()
const { requests } = storeToRefs(eventsStore)
const requestsStore = useRequestsStore()
const { requests } = storeToRefs(requestsStore)
const router = useRouter()
function getStateColour(state) {
if (state === 'New') {
@ -119,9 +121,13 @@ function getStateColour(state) {
/>
</div>
</div>
<div class="relative overflow-x-auto overflow-y-auto max-h-screen shadow-md sm:rounded-lg">
<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">
<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">RequestID</th>
<th scope="col" class="px-6 py-3">CID</th>
@ -131,9 +137,10 @@ function getStateColour(state) {
</thead>
<tbody>
<tr
v-for="([requestId, { request, state }], idx) in requests"
v-for="([requestId, { content, state }], idx) in requests"
:key="{ requestId }"
class="bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-600"
class="cursor-pointer bg-white dark:bg-gray-800 hover:bg-gray-50 dark:hover:bg-gray-600"
@click="router.push(`/request/${requestId}`)"
>
<th
scope="row"
@ -141,19 +148,15 @@ function getStateColour(state) {
>
<CodexImage
:local-only="!['New', 'Fulfilled'].includes(state)"
:cid="request[2][0]"
:cid="content.cid"
class="w-10 h-10 rounded-full mt-1"
/>
<!-- class="w-10 h-10 rounded-full"
src="/docs/images/people/profile-picture-4.jpg"
alt="Jese image"
/> -->
<div class="ps-3">
<div class="text-base font-semibold">{{ shortHex(requestId) }}</div>
<div class="font-normal text-gray-500">leslie@flowbite.com</div>
</div>
</th>
<td class="px-6 py-4">{{ shortHex(request[2][0]) }}</td>
<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>
@ -165,189 +168,13 @@ function getStateColour(state) {
<a
href="#"
type="button"
data-modal-show="editUserModal"
class="font-medium text-blue-600 dark:text-blue-500 hover:underline"
>Edit user</a
>View details</a
>
</td>
</tr>
</tbody>
</table>
</div>
<!-- Edit user modal -->
<div
id="editUserModal"
tabindex="-1"
aria-hidden="true"
class="fixed top-0 left-0 right-0 z-50 items-center justify-center hidden w-full p-4 overflow-x-hidden overflow-y-auto md:inset-0 h-[calc(100%-1rem)] max-h-full"
>
<div class="relative w-full max-w-2xl max-h-full">
<!-- Modal content -->
<form class="relative bg-white rounded-lg shadow dark:bg-gray-700">
<!-- Modal header -->
<div class="flex items-start justify-between p-4 border-b rounded-t dark:border-gray-600">
<h3 class="text-xl font-semibold text-gray-900 dark:text-white">Details</h3>
<button
type="button"
class="text-gray-400 bg-transparent hover:bg-gray-200 hover:text-gray-900 rounded-lg text-sm w-8 h-8 ms-auto inline-flex justify-center items-center dark:hover:bg-gray-600 dark:hover:text-white"
data-modal-hide="editUserModal"
>
<svg
class="w-3 h-3"
aria-hidden="true"
xmlns="http://www.w3.org/2000/svg"
fill="none"
viewBox="0 0 14 14"
>
<path
stroke="currentColor"
stroke-linecap="round"
stroke-linejoin="round"
stroke-width="2"
d="m1 1 6 6m0 0 6 6M7 7l6-6M7 7l-6 6"
/>
</svg>
<span class="sr-only">Close modal</span>
</button>
</div>
<!-- Modal body -->
<div class="p-6 space-y-6">
<div class="grid grid-cols-6 gap-6">
<div class="col-span-6 sm:col-span-3">
<label
for="first-name"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>First Name</label
>
<input
type="text"
name="first-name"
id="first-name"
class="shadow-sm bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-600 focus:border-blue-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Bonnie"
required=""
/>
</div>
<div class="col-span-6 sm:col-span-3">
<label
for="last-name"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>Last Name</label
>
<input
type="text"
name="last-name"
id="last-name"
class="shadow-sm bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-600 focus:border-blue-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Green"
required=""
/>
</div>
<div class="col-span-6 sm:col-span-3">
<label
for="email"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>Email</label
>
<input
type="email"
name="email"
id="email"
class="shadow-sm bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-600 focus:border-blue-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="example@company.com"
required=""
/>
</div>
<div class="col-span-6 sm:col-span-3">
<label
for="phone-number"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>Phone Number</label
>
<input
type="number"
name="phone-number"
id="phone-number"
class="shadow-sm bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-600 focus:border-blue-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="e.g. +(12)3456 789"
required=""
/>
</div>
<div class="col-span-6 sm:col-span-3">
<label
for="department"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>Department</label
>
<input
type="text"
name="department"
id="department"
class="shadow-sm bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-600 focus:border-blue-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="Development"
required=""
/>
</div>
<div class="col-span-6 sm:col-span-3">
<label
for="company"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>Company</label
>
<input
type="number"
name="company"
id="company"
class="shadow-sm bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-600 focus:border-blue-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="123456"
required=""
/>
</div>
<div class="col-span-6 sm:col-span-3">
<label
for="current-password"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>Current Password</label
>
<input
type="password"
name="current-password"
id="current-password"
class="shadow-sm bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-600 focus:border-blue-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="••••••••"
required=""
/>
</div>
<div class="col-span-6 sm:col-span-3">
<label
for="new-password"
class="block mb-2 text-sm font-medium text-gray-900 dark:text-white"
>New Password</label
>
<input
type="password"
name="new-password"
id="new-password"
class="shadow-sm bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded-lg focus:ring-blue-600 focus:border-blue-600 block w-full p-2.5 dark:bg-gray-600 dark:border-gray-500 dark:placeholder-gray-400 dark:text-white dark:focus:ring-blue-500 dark:focus:border-blue-500"
placeholder="••••••••"
required=""
/>
</div>
</div>
</div>
<!-- Modal footer -->
<div
class="flex items-center p-6 space-x-3 rtl:space-x-reverse border-t border-gray-200 rounded-b dark:border-gray-600"
>
<button
type="submit"
class="text-white bg-blue-700 hover:bg-blue-800 focus:ring-4 focus:outline-none focus:ring-blue-300 font-medium rounded-lg text-sm px-5 py-2.5 text-center dark:bg-blue-600 dark:hover:bg-blue-700 dark:focus:ring-blue-800"
>
Save all
</button>
</div>
</form>
</div>
</div>
</div>
</template>

View File

@ -1,5 +1,6 @@
import { createRouter, createWebHistory } from 'vue-router'
import RequestsView from '../views/RequestsView.vue'
import RequestView from '../views/RequestView.vue'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
@ -16,6 +17,12 @@ const router = createRouter({
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/SlotsView.vue')
},
{
path: '/request/:requestId',
name: 'request',
component: RequestView,
props: true // pass :requestId to props in RequestView
}
]
})

View File

@ -1,8 +1,10 @@
import { ref, computed, inject } from 'vue'
import { defineStore } from 'pinia'
import { slotId } from '../utils/ids'
import { arrayToObject } from '@/utils/requests'
export const useEventsStore = defineStore('events', () => {
export const useRequestsStore = defineStore('request', () => {
// let fetched = false
const marketplace = inject('marketplace')
const ethProvider = inject('ethProvider')
let {
@ -24,6 +26,8 @@ export const useEventsStore = defineStore('events', () => {
// const requestFailedEvents = ref([]) // {blockNumber, requestId}
// const requestFinishedEvents = ref([]) // {blockNumber, requestId}
const loading = ref(false)
const fetched = ref(false)
// const request = computed(() => count.value * 2)
// onStorageRequested => add request to requests ref, along with slots
// => add to storageRequestedEvents {blockNumber, requestId}
@ -59,10 +63,8 @@ export const useEventsStore = defineStore('events', () => {
return requestState[stateIdx]
}
const getSlots = async (requestId, request) => {
const getSlots = async (requestId, numSlots) => {
// storageRequestedEvents.value.push({ blockNumber, requestId })
let ask = request[1]
let numSlots = ask[0]
let slots = []
for (let i = 0; i < numSlots; i++) {
let id = slotId(requestId, i)
@ -73,7 +75,11 @@ export const useEventsStore = defineStore('events', () => {
// blockNumbers.value.add(blockNumber)
}
async function fetchPastEvents() {
async function fetch() {
// if (fetched) {
// // allow multiple calls without re-fetching
// return
// }
// query past events
loading.value = true
try {
@ -81,11 +87,12 @@ export const useEventsStore = defineStore('events', () => {
pastRequests.forEach(async (event) => {
let { requestId, ask, expiry } = event.args
// let { blockNumber } = event
let request = await marketplace.getRequest(requestId)
let arrRequest = await marketplace.getRequest(requestId)
let request = arrayToObject(arrRequest)
let state = await getRequestState(requestId)
let slots = getSlots(requestId, request)
let slots = await getSlots(requestId, request.ask.slots)
requests.value.set(requestId, {
request,
...request,
state,
slots,
requestFinishedId: null
@ -95,6 +102,7 @@ export const useEventsStore = defineStore('events', () => {
console.error(`failed to load past contract events: ${error.message}`)
} finally {
loading.value = false
fetched.value = true
}
}
@ -217,7 +225,6 @@ export const useEventsStore = defineStore('events', () => {
// function increment() {
// count.value++
// }
return {
requests,
// slots,
@ -229,8 +236,9 @@ export const useEventsStore = defineStore('events', () => {
// requestCancelledEvents,
// requestFailedEvents,
// requestFinishedEvents,
fetchPastEvents,
fetch,
listenForNewEvents,
loading
loading,
fetched
}
})

30
src/utils/requests.js Normal file
View File

@ -0,0 +1,30 @@
export function arrayToObject(arrRequest) {
let { client, ask, content, expiry, nonce } = arrRequest
let { slots, slotSize, duration, proofProbability, reward, collateral, maxSlotLoss } = ask
let { cid, merkleRoot } = content
return {
client,
ask: {
slots,
slotSize,
duration,
proofProbability,
reward,
collateral,
maxSlotLoss
},
content: {
cid,
merkleRoot
},
expiry,
nonce
}
}
export function price(request) {
if (!request) {
return 0
}
return request.ask.reward * request.ask.duration * request.ask.slots
}

27
src/views/RequestView.vue Normal file
View File

@ -0,0 +1,27 @@
<script setup>
import { computed } from 'vue'
import { storeToRefs } from 'pinia'
import { useRequestsStore } from '@/stores/requests'
import { useRoute } from 'vue-router'
import StorageRequest from '@/components/StorageRequest.vue'
import SkeletonLoading from '@/components/SkeletonLoading.vue'
const requestsStore = useRequestsStore()
const { requests, loading, fetched } = storeToRefs(requestsStore)
const isLoading = computed(() =>
!fetched.value ||
loading.value ||
!requests.value ||
requests.value.size === 0 ||
!requests.value.has(route.params.requestId)
)
const request = computed(() => requests?.value?.get(route.params.requestId) )
const route = useRoute()
</script>
<template>
<div>
<SkeletonLoading v-if="isLoading" type="image" />
<StorageRequest v-else :requestId="route.params.requestId" :request="request" />
</div>
</template>

View File

@ -1,16 +1,20 @@
<script setup>
import { storeToRefs } from 'pinia'
import { useEventsStore } from '@/stores/events'
import { useRequestsStore } from '@/stores/requests'
import StorageRequests from '@/components/StorageRequests.vue'
import SkeletonLoading from '@/components/SkeletonLoading.vue'
import { computed } from 'vue'
const eventsStore = useEventsStore()
const { loading } = storeToRefs(eventsStore)
const requestsStore = useRequestsStore()
const { loading, fetched, requests } = storeToRefs(requestsStore)
const isLoading = computed(
() => loading.value || !fetched.value || !requests.value || requests.value?.size === 0
)
</script>
<template>
<div>
<SkeletonLoading v-if="loading" type="image" />
<SkeletonLoading v-if="isLoading" type="text" />
<StorageRequests v-else />
</div>
</template>