1
0
mirror of https://github.com/oceanprotocol/market.git synced 2024-12-02 05:57:29 +01:00

Merge branch 'main' into feature/multinetwork

This commit is contained in:
Matthias Kretschmann 2021-06-11 11:11:45 +02:00
commit b8930cc738
Signed by: m
GPG Key ID: 606EEEF3C479A91F
17 changed files with 359 additions and 152 deletions

View File

@ -2,6 +2,9 @@
# "development", "ropsten", "rinkeby", "mainnet", "polygon", "moonbeamalpha" # "development", "ropsten", "rinkeby", "mainnet", "polygon", "moonbeamalpha"
GATSBY_NETWORK="rinkeby" GATSBY_NETWORK="rinkeby"
## Define a GATSBY_RBAC_URL to implement permission based restrictions
#GATSBY_RBAC_URL="http://localhost:3000"
#GATSBY_INFURA_PROJECT_ID="xxx" #GATSBY_INFURA_PROJECT_ID="xxx"
#GATSBY_MARKET_FEE_ADDRESS="0xxx" #GATSBY_MARKET_FEE_ADDRESS="0xxx"
#GATSBY_ANALYTICS_ID="xxx" #GATSBY_ANALYTICS_ID="xxx"

View File

@ -10,6 +10,8 @@ module.exports = {
// List of all supported chainIds. Used to populate the Chains user preferences list. // List of all supported chainIds. Used to populate the Chains user preferences list.
chainIdsSupported: [1, 3, 4, 137, 1287], chainIdsSupported: [1, 3, 4, 137, 1287],
rbacUrl: process.env.GATSBY_RBAC_URL,
infuraProjectId: process.env.GATSBY_INFURA_PROJECT_ID || 'xxx', infuraProjectId: process.env.GATSBY_INFURA_PROJECT_ID || 'xxx',
// The ETH address the marketplace fee will be sent to. // The ETH address the marketplace fee will be sent to.

5
package-lock.json generated
View File

@ -65003,6 +65003,11 @@
"clsx": "^1.1.1" "clsx": "^1.1.1"
} }
}, },
"reactjs-popup": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/reactjs-popup/-/reactjs-popup-2.0.4.tgz",
"integrity": "sha512-G5jTXL2JkClKAYAdqedf+K9QvbNsFWvdbrXW1/vWiyanuCU/d7DtQzQux+uKOz2HeNVRsFQHvs7abs0Z7VLAhg=="
},
"read": { "read": {
"version": "1.0.7", "version": "1.0.7",
"resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz", "resolved": "https://registry.npmjs.org/read/-/read-1.0.7.tgz",

View File

@ -25,3 +25,7 @@
width: 4.5rem; width: 4.5rem;
padding: calc(var(--spacer) / 2) calc(var(--spacer) / 4); padding: calc(var(--spacer) / 2) calc(var(--spacer) / 4);
} }
.loaderWrap {
margin-right: calc(var(--spacer) / 6);
}

View File

@ -4,17 +4,28 @@ import filesize from 'filesize'
import classNames from 'classnames/bind' import classNames from 'classnames/bind'
import cleanupContentType from '../../utils/cleanupContentType' import cleanupContentType from '../../utils/cleanupContentType'
import styles from './File.module.css' import styles from './File.module.css'
import Loader from '../atoms/Loader'
const cx = classNames.bind(styles) const cx = classNames.bind(styles)
function LoaderArea() {
return (
<div className={styles.loaderWrap}>
<Loader />
</div>
)
}
export default function File({ export default function File({
file, file,
className, className,
small small,
isLoading
}: { }: {
file: FileMetadata file: FileMetadata
className?: string className?: string
small?: boolean small?: boolean
isLoading?: boolean
}): ReactElement { }): ReactElement {
if (!file) return null if (!file) return null
@ -26,17 +37,23 @@ export default function File({
return ( return (
<ul className={styleClasses}> <ul className={styleClasses}>
{file.contentType || file.contentLength ? ( {isLoading === false || isLoading === undefined ? (
<> <>
<li>{cleanupContentType(file.contentType)}</li> {file.contentType || file.contentLength ? (
<li> <>
{file.contentLength && file.contentLength !== '0' <li>{cleanupContentType(file.contentType)}</li>
? filesize(Number(file.contentLength)) <li>
: ''} {file.contentLength && file.contentLength !== '0'
</li> ? filesize(Number(file.contentLength))
: ''}
</li>
</>
) : (
<li className={styles.empty}>No file info available</li>
)}
</> </>
) : ( ) : (
<li className={styles.empty}>No file info available</li> <LoaderArea />
)} )}
</ul> </ul>
) )

View File

@ -39,7 +39,7 @@ import { secondsToString } from '../../../../utils/metadata'
import { getPreviousOrders, getPrice } from '../../../../utils/subgraph' import { getPreviousOrders, getPrice } from '../../../../utils/subgraph'
const SuccessAction = () => ( const SuccessAction = () => (
<Button style="text" to="/history" size="small"> <Button style="text" to="/history?defaultTab=ComputeJobs" size="small">
Go to history Go to history
</Button> </Button>
) )
@ -47,11 +47,13 @@ const SuccessAction = () => (
export default function Compute({ export default function Compute({
isBalanceSufficient, isBalanceSufficient,
dtBalance, dtBalance,
file file,
fileIsLoading
}: { }: {
isBalanceSufficient: boolean isBalanceSufficient: boolean
dtBalance: string dtBalance: string
file: FileMetadata file: FileMetadata
fileIsLoading?: boolean
}): ReactElement { }): ReactElement {
const { appConfig } = useSiteMetadata() const { appConfig } = useSiteMetadata()
const { accountId } = useWeb3() const { accountId } = useWeb3()
@ -369,7 +371,7 @@ export default function Compute({
return ( return (
<> <>
<div className={styles.info}> <div className={styles.info}>
<File file={file} small /> <File file={file} isLoading={fileIsLoading} small />
<Price price={price} conversion /> <Price price={price} conversion />
</div> </div>

View File

@ -34,12 +34,14 @@ export default function Consume({
ddo, ddo,
file, file,
isBalanceSufficient, isBalanceSufficient,
dtBalance dtBalance,
fileIsLoading
}: { }: {
ddo: DDO ddo: DDO
file: FileMetadata file: FileMetadata
isBalanceSufficient: boolean isBalanceSufficient: boolean
dtBalance: string dtBalance: string
fileIsLoading?: boolean
}): ReactElement { }): ReactElement {
const { accountId } = useWeb3() const { accountId } = useWeb3()
const { ocean } = useOcean() const { ocean } = useOcean()
@ -160,7 +162,7 @@ export default function Consume({
<aside className={styles.consume}> <aside className={styles.consume}>
<div className={styles.info}> <div className={styles.info}>
<div className={styles.filewrapper}> <div className={styles.filewrapper}>
<File file={file} /> <File file={file} isLoading={fileIsLoading} />
</div> </div>
<div className={styles.pricewrapper}> <div className={styles.pricewrapper}>
<Price price={price} conversion /> <Price price={price} conversion />

View File

@ -1,8 +1,9 @@
import React, { ReactElement, useState, useEffect } from 'react' import React, { ReactElement, useState, useEffect } from 'react'
import Permission from '../Permission'
import styles from './index.module.css' import styles from './index.module.css'
import Compute from './Compute' import Compute from './Compute'
import Consume from './Consume' import Consume from './Consume'
import { Logger } from '@oceanprotocol/lib' import { Logger, File as FileMetadata, DID } from '@oceanprotocol/lib'
import Tabs from '../../atoms/Tabs' import Tabs from '../../atoms/Tabs'
import compareAsBN from '../../../utils/compareAsBN' import compareAsBN from '../../../utils/compareAsBN'
import Pool from './Pool' import Pool from './Pool'
@ -11,6 +12,8 @@ import { useAsset } from '../../../providers/Asset'
import { useOcean } from '../../../providers/Ocean' import { useOcean } from '../../../providers/Ocean'
import { useWeb3 } from '../../../providers/Web3' import { useWeb3 } from '../../../providers/Web3'
import Web3Feedback from '../../molecules/Web3Feedback' import Web3Feedback from '../../molecules/Web3Feedback'
import { getFileInfo } from '../../../utils/provider'
import axios from 'axios'
export default function AssetActions(): ReactElement { export default function AssetActions(): ReactElement {
const { accountId } = useWeb3() const { accountId } = useWeb3()
@ -19,8 +22,31 @@ export default function AssetActions(): ReactElement {
const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>() const [isBalanceSufficient, setIsBalanceSufficient] = useState<boolean>()
const [dtBalance, setDtBalance] = useState<string>() const [dtBalance, setDtBalance] = useState<string>()
const [fileMetadata, setFileMetadata] = useState<FileMetadata>(Object)
const [fileIsLoading, setFileIsLoading] = useState<boolean>(false)
const isCompute = Boolean(ddo?.findServiceByType('compute')) const isCompute = Boolean(ddo?.findServiceByType('compute'))
useEffect(() => {
if (!config) return
const source = axios.CancelToken.source()
async function initFileInfo() {
setFileIsLoading(true)
try {
const fileInfo = await getFileInfo(
DID.parse(`${ddo.id}`),
config.providerUri,
source.token
)
setFileMetadata(fileInfo.data[0])
} catch (error) {
Logger.error(error.message)
} finally {
setFileIsLoading(false)
}
}
initFileInfo()
}, [config, ddo.id])
// Get and set user DT balance // Get and set user DT balance
useEffect(() => { useEffect(() => {
if (!ocean || !accountId) return if (!ocean || !accountId) return
@ -56,14 +82,16 @@ export default function AssetActions(): ReactElement {
<Compute <Compute
dtBalance={dtBalance} dtBalance={dtBalance}
isBalanceSufficient={isBalanceSufficient} isBalanceSufficient={isBalanceSufficient}
file={metadata?.main.files[0]} file={fileMetadata}
fileIsLoading={fileIsLoading}
/> />
) : ( ) : (
<Consume <Consume
ddo={ddo} ddo={ddo}
dtBalance={dtBalance} dtBalance={dtBalance}
isBalanceSufficient={isBalanceSufficient} isBalanceSufficient={isBalanceSufficient}
file={metadata?.main.files[0]} file={fileMetadata}
fileIsLoading={fileIsLoading}
/> />
) )
@ -88,7 +116,9 @@ export default function AssetActions(): ReactElement {
return ( return (
<> <>
<Tabs items={tabs} className={styles.actions} /> <Permission eventType="consume">
<Tabs items={tabs} className={styles.actions} />
</Permission>
{type !== 'algorithm' && ( {type !== 'algorithm' && (
<Web3Feedback <Web3Feedback
isBalanceSufficient={isBalanceSufficient} isBalanceSufficient={isBalanceSufficient}

View File

@ -0,0 +1,62 @@
import React, { ReactElement, useEffect, useState } from 'react'
import { useWeb3 } from '../../providers/Web3'
import rbacRequest from '../../utils/rbac'
import Alert from '../atoms/Alert'
import Loader from '../atoms/Loader'
import appConfig from '../../../app.config'
export default function Permission({
eventType,
children
}: {
eventType: string
children: ReactElement
}): ReactElement {
const url = appConfig.rbacUrl
const [data, updateData] = useState<boolean | 'ERROR'>()
const [errorMessage, updateError] = useState<string>()
const [messageState, updateMessageState] =
useState<'error' | 'warning' | 'info' | 'success'>()
const { accountId } = useWeb3()
useEffect(() => {
if (url === undefined) return
const getData = async () => {
if (accountId === undefined) {
updateError('Please make sure your wallet is connected to proceed.')
updateMessageState('info')
} else {
const data = await rbacRequest(eventType, accountId)
updateData(data)
if (data === 'ERROR') {
updateError(
'There was an error verifying your permissions. Please refresh the page or conntact your network administrator'
)
updateMessageState('error')
} else if (data === false) {
updateError(
`Sorry, you don't have permission to ${eventType}. Please make sure you have connected your registered address.`
)
updateMessageState('warning')
} else if (data !== true) {
updateError(
'An unkown error occured. Please conntact your network administrator'
)
updateMessageState('error')
}
}
}
getData()
}, [eventType, accountId, url])
if (url === undefined || data === true) {
return <>{children}</>
}
return (
<>
<Alert text={errorMessage} state={messageState} />
<br />
<Loader />
</>
)
}

View File

@ -31,9 +31,17 @@ const tabs = [
] ]
export default function HistoryPage(): ReactElement { export default function HistoryPage(): ReactElement {
const url = new URL(window.location.href)
const defaultTab = url.searchParams.get('defaultTab')
let defaultTabIndex = 0
defaultTab === 'ComputeJobs' ? (defaultTabIndex = 4) : (defaultTabIndex = 0)
return ( return (
<article className={styles.content}> <article className={styles.content}>
<Tabs items={tabs} className={styles.tabs} /> <Tabs
items={tabs}
className={styles.tabs}
defaultIndex={defaultTabIndex}
/>
</article> </article>
) )
} }

View File

@ -11,6 +11,7 @@ import Button from '../atoms/Button'
import Bookmarks from '../molecules/Bookmarks' import Bookmarks from '../molecules/Bookmarks'
import axios from 'axios' import axios from 'axios'
import { queryMetadata } from '../../utils/aquarius' import { queryMetadata } from '../../utils/aquarius'
import Permission from '../organisms/Permission'
import { getHighestLiquidityDIDs } from '../../utils/subgraph' import { getHighestLiquidityDIDs } from '../../utils/subgraph'
import { DDO, Logger } from '@oceanprotocol/lib' import { DDO, Logger } from '@oceanprotocol/lib'
import { useSiteMetadata } from '../../hooks/useSiteMetadata' import { useSiteMetadata } from '../../hooks/useSiteMetadata'
@ -132,33 +133,35 @@ export default function HomePage(): ReactElement {
}, [chainIds]) }, [chainIds])
return ( return (
<> <Permission eventType="browse">
<Container narrow className={styles.searchWrap}> <>
<SearchBar size="large" /> <Container narrow className={styles.searchWrap}>
</Container> <SearchBar size="large" />
</Container>
<section className={styles.section}> <section className={styles.section}>
<h3>Bookmarks</h3> <h3>Bookmarks</h3>
{/* <Bookmarks /> */} {/* <Bookmarks /> */}
</section> </section>
{queryAndDids && (
<SectionQueryResult
title="Highest Liquidity"
query={queryAndDids[0]}
queryData={queryAndDids[1]}
/>
)}
{queryAndDids && (
<SectionQueryResult <SectionQueryResult
title="Highest Liquidity" title="Recently Published"
query={queryAndDids[0]} query={getQueryLatest(chainIds)}
queryData={queryAndDids[1]} action={
<Button style="text" to="/search?sort=created&sortOrder=desc">
All data sets and algorithms
</Button>
}
/> />
)} </>
</Permission>
<SectionQueryResult
title="Recently Published"
query={getQueryLatest(chainIds)}
action={
<Button style="text" to="/search?sort=created&sortOrder=desc">
All data sets and algorithms
</Button>
}
/>
</>
) )
} }

View File

@ -1,4 +1,5 @@
import React, { ReactElement, useState, useEffect } from 'react' import React, { ReactElement, useState, useEffect } from 'react'
import Permission from '../../organisms/Permission'
import { Formik, FormikState } from 'formik' import { Formik, FormikState } from 'formik'
import { usePublish } from '../../../hooks/usePublish' import { usePublish } from '../../../hooks/usePublish'
import styles from './index.module.css' import styles from './index.module.css'
@ -215,86 +216,92 @@ export default function PublishPage({
} }
return isInPurgatory && purgatoryData ? null : ( return isInPurgatory && purgatoryData ? null : (
<Formik <Permission eventType="publish">
initialValues={ <Formik
publishType === 'dataset' ? datasetInitialValues : algoInitialValues initialValues={
} publishType === 'dataset' ? datasetInitialValues : algoInitialValues
initialStatus="empty" }
validationSchema={ initialStatus="empty"
publishType === 'dataset' ? validationSchema : validationSchemaAlgorithm validationSchema={
} publishType === 'dataset'
onSubmit={async (values, { resetForm }) => { ? validationSchema
// move user's focus to top of screen : validationSchemaAlgorithm
window.scrollTo({ top: 0, left: 0, behavior: 'smooth' }) }
// kick off publishing onSubmit={async (values, { resetForm }) => {
publishType === 'dataset' // move user's focus to top of screen
? await handleSubmit(values, resetForm) window.scrollTo({ top: 0, left: 0, behavior: 'smooth' })
: await handleAlgorithmSubmit(values, resetForm) // kick off publishing
}} publishType === 'dataset'
enableReinitialize ? await handleSubmit(values, resetForm)
> : await handleAlgorithmSubmit(values, resetForm)
{({ values }) => { }}
const tabs = [ enableReinitialize
{ >
title: 'Data Set', {({ values }) => {
content: <TabContent values={values} publishType={publishType} /> const tabs = [
}, {
{ title: 'Data Set',
title: 'Algorithm', content: <TabContent values={values} publishType={publishType} />
content: <TabContent values={values} publishType={publishType} /> },
} {
] title: 'Algorithm',
content: <TabContent values={values} publishType={publishType} />
}
]
return ( return (
<> <>
<Persist <Persist
name={ name={
publishType === 'dataset' publishType === 'dataset'
? formNameDatasets ? formNameDatasets
: formNameAlgorithms : formNameAlgorithms
} }
ignoreFields={['isSubmitting']} ignoreFields={['isSubmitting']}
/>
{hasFeedback ? (
<MetadataFeedback
title={title}
error={error}
success={success}
loading={publishStepText}
setError={setError}
successAction={{
name: `Go to ${
publishType === 'dataset' ? 'data set' : 'algorithm'
} `,
to: `/asset/${did}`
}}
/> />
) : (
<>
<Alert
text={content.warning}
state="info"
className={styles.alert}
/>
<Tabs {hasFeedback ? (
className={styles.tabs} <MetadataFeedback
items={tabs} title={title}
handleTabChange={(title) => { error={error}
setPublishType(title.toLowerCase().replace(' ', '') as any) success={success}
title === 'Algorithm' loading={publishStepText}
? setdatasetInitialValues(values) setError={setError}
: setAlgoInitialValues(values) successAction={{
name: `Go to ${
publishType === 'dataset' ? 'data set' : 'algorithm'
} `,
to: `/asset/${did}`
}} }}
/> />
</> ) : (
)} <>
<Alert
text={content.warning}
state="info"
className={styles.alert}
/>
{debug === true && <Debug values={values} />} <Tabs
</> className={styles.tabs}
) items={tabs}
}} handleTabChange={(title) => {
</Formik> setPublishType(
title.toLowerCase().replace(' ', '') as any
)
title === 'Algorithm'
? setdatasetInitialValues(values)
: setAlgoInitialValues(values)
}}
/>
</>
)}
{debug === true && <Debug values={values} />}
</>
)
}}
</Formik>
</Permission>
) )
} }

View File

@ -23,7 +23,6 @@ export default function Page({
return ( return (
<> <>
<Seo title={title} description={description} uri={uri} /> <Seo title={title} description={description} uri={uri} />
<Container> <Container>
{title && !noPageHeader && ( {title && !noPageHeader && (
<PageHeader <PageHeader

View File

@ -1,4 +1,5 @@
import React, { ReactElement, useState, useEffect } from 'react' import React, { ReactElement, useState, useEffect } from 'react'
import Permission from '../../organisms/Permission'
import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache' import { QueryResult } from '@oceanprotocol/lib/dist/node/metadatacache/MetadataCache'
import SearchBar from '../../molecules/SearchBar' import SearchBar from '../../molecules/SearchBar'
import AssetList from '../../organisms/AssetList' import AssetList from '../../organisms/AssetList'
@ -62,34 +63,36 @@ export default function SearchPage({
} }
return ( return (
<> <Permission eventType="browse">
<div className={styles.search}> <>
{(text || owner) && ( <div className={styles.search}>
<SearchBar initialValue={(text || owner) as string} /> {(text || owner) && (
)} <SearchBar initialValue={(text || owner) as string} />
<div className={styles.row}> )}
<ServiceFilter <div className={styles.row}>
serviceType={service} <ServiceFilter
setServiceType={setServiceType} serviceType={service}
/> setServiceType={setServiceType}
<Sort />
sortType={sortType} <Sort
sortDirection={sortDirection} sortType={sortType}
setSortType={setSortType} sortDirection={sortDirection}
setSortDirection={setSortDirection} setSortType={setSortType}
setSortDirection={setSortDirection}
/>
</div>
</div>
<div className={styles.results}>
<AssetList
assets={queryResult?.results}
showPagination
isLoading={loading}
page={queryResult?.page}
totalPages={queryResult?.totalPages}
onPageChange={setPage}
/> />
</div> </div>
</div> </>
<div className={styles.results}> </Permission>
<AssetList
assets={queryResult?.results}
showPagination
isLoading={loading}
page={queryResult?.page}
totalPages={queryResult?.totalPages}
onPageChange={setPage}
/>
</div>
</>
) )
} }

View File

@ -1,4 +1,5 @@
import React, { ReactElement, useEffect, useState } from 'react' import React, { ReactElement, useEffect, useState } from 'react'
import Permission from '../../components/organisms/Permission'
import { PageProps } from 'gatsby' import { PageProps } from 'gatsby'
import PageTemplateAssetDetails from '../../components/templates/PageAssetDetails' import PageTemplateAssetDetails from '../../components/templates/PageAssetDetails'
import AssetProvider from '../../providers/Asset' import AssetProvider from '../../providers/Asset'
@ -12,10 +13,12 @@ export default function PageGatsbyAssetDetails(props: PageProps): ReactElement {
}, [props.location.pathname]) }, [props.location.pathname])
return ( return (
<AssetProvider asset={did}> <Permission eventType="browse">
<OceanProvider> <AssetProvider asset={did}>
<PageTemplateAssetDetails uri={props.location.pathname} /> <OceanProvider>
</OceanProvider> <PageTemplateAssetDetails uri={props.location.pathname} />
</AssetProvider> </OceanProvider>
</AssetProvider>
</Permission>
) )
} }

View File

@ -1,6 +1,6 @@
import axios, { CancelToken, AxiosResponse } from 'axios' import axios, { CancelToken, AxiosResponse } from 'axios'
import { toast } from 'react-toastify' import { toast } from 'react-toastify'
import { File as FileMetadata, Logger } from '@oceanprotocol/lib' import { DID, File as FileMetadata, Logger } from '@oceanprotocol/lib'
export async function fileinfo( export async function fileinfo(
url: string, url: string,
@ -57,3 +57,26 @@ export async function fileinfo(
Logger.error(error.message) Logger.error(error.message)
} }
} }
export async function getFileInfo(
url: string | DID,
providerUri: string,
cancelToken: CancelToken
): Promise<AxiosResponse> {
let postBody
try {
if (url instanceof DID)
postBody = {
did: url.getDid(),
cancelToken
}
else
postBody = {
url,
cancelToken
}
return await axios.post(`${providerUri}/api/v1/services/fileinfo`, postBody)
} catch (error) {
Logger.error(error.message)
}
}

34
src/utils/rbac.ts Normal file
View File

@ -0,0 +1,34 @@
import fetch from 'cross-fetch'
import appConfig from '../../app.config'
export default async function rbacRequest(
eventType: string,
address: string
): Promise<boolean | 'ERROR'> {
const url = appConfig.rbacUrl
if (url === undefined) {
return true
} else {
const data = {
component: 'market',
eventType,
authService: 'address',
credentials: {
address
}
}
try {
const response = await fetch(url, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(data)
})
return await response.json()
} catch (error) {
console.error('Error parsing json: ' + error.message)
return 'ERROR'
}
}
}