diff --git a/client/src/components/atoms/Account.module.scss b/client/src/components/atoms/Account.module.scss index 22e5eac..b0b6898 100644 --- a/client/src/components/atoms/Account.module.scss +++ b/client/src/components/atoms/Account.module.scss @@ -47,7 +47,7 @@ display: inline-block; fill: currentColor; margin-right: $spacer / 8; - transition: .2s ease-out; + transition: 0.2s ease-out; } } diff --git a/client/src/components/atoms/CategoryImage.module.scss b/client/src/components/atoms/CategoryImage.module.scss index a19c59d..d28f8f6 100644 --- a/client/src/components/atoms/CategoryImage.module.scss +++ b/client/src/components/atoms/CategoryImage.module.scss @@ -15,7 +15,7 @@ .header { // stylelint-disable value-keyword-case - composes: categoryImage; + composes: categoryimage; // stylelint-enable value-keyword-case height: 8rem; margin-top: $spacer / $line-height; @@ -23,7 +23,7 @@ .dimmed { // stylelint-disable value-keyword-case - composes: categoryImage; + composes: categoryimage; // stylelint-enable value-keyword-case opacity: .6; } diff --git a/client/src/components/atoms/Form/Input.module.scss b/client/src/components/atoms/Form/Input.module.scss index e5adb6e..7cc3fad 100644 --- a/client/src/components/atoms/Form/Input.module.scss +++ b/client/src/components/atoms/Form/Input.module.scss @@ -33,8 +33,8 @@ width: 1.25rem; height: 1.25rem; top: 50%; - margin-top: -.6rem; - fill: rgba($brand-grey-light, .7); + margin-top: -0.6rem; + fill: rgba($brand-grey-light, 0.7); } } @@ -50,7 +50,7 @@ padding: $spacer / 3; margin: 0; border-radius: $border-radius; - transition: .2s ease-out; + transition: 0.2s ease-out; min-height: 43px; appearance: none; @@ -65,8 +65,8 @@ font-size: $font-size-base; color: $brand-grey-light; font-weight: $font-weight-base; - transition: .2s ease-out; - opacity: .7; + transition: 0.2s ease-out; + opacity: 0.7; } &[readonly], @@ -155,7 +155,7 @@ font-size: $font-size-small; line-height: 1.2; border: 2px solid $brand-grey-lighter; - border-radius: .2rem; + border-radius: 0.2rem; position: absolute; left: 0; right: 0; diff --git a/client/src/components/molecules/AccountStatus/Indicator.module.scss b/client/src/components/molecules/AccountStatus/Indicator.module.scss index 55ee748..b8c8cbc 100644 --- a/client/src/components/molecules/AccountStatus/Indicator.module.scss +++ b/client/src/components/molecules/AccountStatus/Indicator.module.scss @@ -18,7 +18,7 @@ /* yellow triangle */ .statusIndicatorCloseEnough { // stylelint-disable value-keyword-case - composes: statusIndicator; + composes: statusindicator; // stylelint-enable value-keyword-case background: none; width: 0; @@ -31,7 +31,7 @@ /* green circle */ .statusIndicatorActive { // stylelint-disable value-keyword-case - composes: statusIndicator; + composes: statusindicator; // stylelint-enable value-keyword-case border-radius: 50%; background: $green; diff --git a/client/src/components/molecules/AccountStatus/Popover.module.scss b/client/src/components/molecules/AccountStatus/Popover.module.scss index 273b3b3..a4faa66 100644 --- a/client/src/components/molecules/AccountStatus/Popover.module.scss +++ b/client/src/components/molecules/AccountStatus/Popover.module.scss @@ -7,12 +7,12 @@ $popoverWidth: 18rem; width: $popoverWidth; padding: $spacer / 2; background: $brand-black; - border-radius: .1rem; - border: .1rem solid $brand-grey-light; - box-shadow: 0 6px 16px 0 rgba($brand-black, .3); + border-radius: 0.1rem; + border: 0.1rem solid $brand-grey-light; + box-shadow: 0 6px 16px 0 rgba($brand-black, 0.3); color: $brand-grey-light; font-size: $font-size-small; - animation: showPopup .2s ease-in forwards; + animation: showPopup 0.2s ease-in forwards; white-space: initial; text-align: left; } @@ -28,7 +28,7 @@ $popoverWidth: 18rem; } .popoverInfoline { - border-bottom: .05rem solid $brand-grey; + border-bottom: 0.05rem solid $brand-grey; padding: $spacer / 3 0; &:first-child { diff --git a/client/src/components/molecules/VersionNumbers/VersionStatus.module.scss b/client/src/components/molecules/VersionNumbers/VersionStatus.module.scss index 7595ed4..bd1ddc9 100644 --- a/client/src/components/molecules/VersionNumbers/VersionStatus.module.scss +++ b/client/src/components/molecules/VersionNumbers/VersionStatus.module.scss @@ -24,11 +24,11 @@ // stylelint-disable value-keyword-case .indicator { - composes: statusIndicator from '../AccountStatus/Indicator.module.scss'; + composes: statusindicator from '../AccountStatus/Indicator.module.scss'; } .indicatorActive { - composes: statusIndicatorActive from '../AccountStatus/Indicator.module.scss'; + composes: statusindicatoractive from '../AccountStatus/Indicator.module.scss'; } // stylelint-enable value-keyword-case diff --git a/client/src/components/organisms/WalletSelector.module.scss b/client/src/components/organisms/WalletSelector.module.scss index 2989d26..aabc9ea 100644 --- a/client/src/components/organisms/WalletSelector.module.scss +++ b/client/src/components/organisms/WalletSelector.module.scss @@ -24,7 +24,7 @@ align-items: flex-start; text-align: left; cursor: pointer; - transition: border .2s ease-out; + transition: border 0.2s ease-out; margin-bottom: $spacer; position: relative; diff --git a/client/src/components/templates/Asset/AssetDetails.tsx b/client/src/components/templates/Asset/AssetDetails.tsx index 66eb46e..7b8d654 100644 --- a/client/src/components/templates/Asset/AssetDetails.tsx +++ b/client/src/components/templates/Asset/AssetDetails.tsx @@ -8,9 +8,12 @@ import styles from './AssetDetails.module.scss' import AssetFilesDetails from './AssetFilesDetails' import Report from './Report' import Web3 from 'web3' +import AssetsJob from './AssetJob' interface AssetDetailsProps { + ocean: any metadata: MetaData + computeMetadata?: MetaData ddo: DDO } @@ -30,10 +33,19 @@ const MetaFixedItem = ({ name, value }: { name: string; value: string }) => ( ) -export default function AssetDetails({ metadata, ddo }: AssetDetailsProps) { +export default function AssetDetails({ + metadata, + ddo, + computeMetadata, + ocean +}: AssetDetailsProps) { const { main, additionalInformation } = metadata const price = main.price && Web3.utils.fromWei(main.price.toString()) + console.log(computeMetadata) + const isCompute = computeMetadata ? true : false + console.log(isCompute) + const metaFixed = [ { name: 'Author', @@ -120,7 +132,18 @@ export default function AssetDetails({ metadata, ddo }: AssetDetailsProps) { - + {isCompute ? ( + + ) : ( + + )} > ) } diff --git a/client/src/components/templates/Asset/AssetJob.module.scss b/client/src/components/templates/Asset/AssetJob.module.scss new file mode 100644 index 0000000..248d5ee --- /dev/null +++ b/client/src/components/templates/Asset/AssetJob.module.scss @@ -0,0 +1,100 @@ +@import '../../../styles/variables'; + +.box { + margin-bottom: $spacer / 2; + background: $brand-white; + border-radius: $border-radius; + border: 1px solid $brand-grey-lighter; + padding: $spacer / 2; +} + +.dataType { + padding-top: 10px; +} + +.buttonWrapper { + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + height: 120px; +} + +.error { + color: $red; + align-items: center; + justify-content: center; +} + +.bold { + font-weight: 900; +} + +.expiry { + color: $brand-grey-light; + float: right; +} + +.job { + border-top: 1px solid #e2e2e2; +} + +.dragndrop { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; + height: 90px; + color: $brand-grey-light; + background-color: $brand-white; +} + +.filleddragndrop { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + cursor: pointer; + font-weight: 900; + height: 90px; + color: $brand-grey; + background-color: $brand-white; +} + +.inputWrap { + margin-top: $spacer / 4; + background: $brand-gradient; + border-radius: $border-radius; + padding: 2px; + display: flex; + position: relative; + + &.isFocused { + background: $brand-black; + } + + > div, + > div > div { + width: 100%; + } +} + +.jobButtonWrapper { + text-align: right; + margin-top: $spacer / 4; +} + +.table { + margin-top: $spacer / 2; + margin-bottom: 0; +} + +.spinner { + padding-bottom: 30px; +} + +.jobloading { + padding-left: 23px; +} diff --git a/client/src/components/templates/Asset/AssetJob.tsx b/client/src/components/templates/Asset/AssetJob.tsx new file mode 100644 index 0000000..6e88cb0 --- /dev/null +++ b/client/src/components/templates/Asset/AssetJob.tsx @@ -0,0 +1,159 @@ +import React, { ChangeEvent, useState } from 'react' +import { DDO, MetaData } from '@oceanprotocol/squid' +import Input from '../../atoms/Form/Input' +import computeOptions from '../../../data/computeOptions.json' +import styles from './AssetJob.module.scss' +import Spinner from '../../atoms/Spinner' +import Button from '../../atoms/Button' +import { messages } from './AssetFile' +import ReactDropzone from 'react-dropzone' + +interface JobsProps { + ocean: any + computeMetadata?: MetaData + ddo: DDO +} + +export default function AssetsJobs({ ddo, computeMetadata, ocean }: JobsProps) { + let rawAlgorithmMeta = { + rawcode: `console.log('Hello world'!)`, + format: 'docker-image', + version: '0.1', + container: {} + } + + const [isJobStarting, setIsJobStarting] = useState(false) + const [step, setStep] = useState(99) + + const [computeType, setComputeType] = useState('') + const [computeValue, setComputeValue] = useState({}) + const [algorithmRawCode, setAlgorithmRawCode] = useState('') + + const [file, setFile] = useState(null) + + const onDrop = async (files: any) => { + setFile(files[0]) + const fileText = await readFileContent(files[0]) + setAlgorithmRawCode(fileText) + } + const handleSelectChange = (event: ChangeEvent) => { + const comType = event.target.value + setComputeType(comType) + + const selectedComputeOption = computeOptions.find( + x => x.name === comType + ) + if (selectedComputeOption !== undefined) + setComputeValue(selectedComputeOption.value) + } + const readFileContent = (file: File): Promise => { + return new Promise(resolve => { + const reader = new FileReader() + reader.onload = function(e) { + resolve(reader.result as string) + } + reader.readAsText(file) + }) + } + + const startJob = async () => { + try { + setIsJobStarting(true) + const accounts = await ocean.accounts.list() + const ComputeOutput = { + publishAlgorithmLog: false, + publishOutput: false, + brizoAddress: ocean.config.brizoAddress, + brizoUri: ocean.config.brizoUri, + metadataUri: ocean.config.aquariusUri, + nodeUri: ocean.config.nodeUri, + owner: accounts[0].getId(), + secretStoreUri: ocean.config.secretStoreUri + } + + const agreement = await ocean.compute + .order(accounts[0], ddo.id) + .next((step: number) => setStep(step)) + + rawAlgorithmMeta.container = computeValue + rawAlgorithmMeta.rawcode = algorithmRawCode + + const status = await ocean.compute.start( + accounts[0], + agreement, + undefined, + encodeURIComponent(JSON.stringify(rawAlgorithmMeta)), + ComputeOutput + ) + + console.log(status) + } catch (error) { + console.error(error.message) + } + setIsJobStarting(false) + } + + return ( + <> + + + New job + + x.name)} + onChange={handleSelectChange} + /> + + + + onDrop(acceptedFiles)} + > + {({ getRootProps, getInputProps }) => ( + + + {file === null && ( + + Click or drop your notebook here + + )} + {file !== null && ( + + You selected:{' '} + {(file as any).path} + + )} + + )} + + + + startJob()} + disabled={ + isJobStarting || + file === null || + computeType === '' + } + name="Purchase access" + > + Start job + + + + {isJobStarting ? : ''} + + + > + ) +} diff --git a/client/src/components/templates/Asset/index.tsx b/client/src/components/templates/Asset/index.tsx index 8b6e03a..c6c4461 100644 --- a/client/src/components/templates/Asset/index.tsx +++ b/client/src/components/templates/Asset/index.tsx @@ -20,8 +20,10 @@ interface AssetProps { } interface AssetState { + ocean: any ddo: DDO metadata: MetaData + computeMetadata?: MetaData error: string isLoading: boolean } @@ -30,8 +32,10 @@ class Asset extends Component { public static contextType = User public state = { + ocean: undefined, ddo: ({} as any) as DDO, metadata: ({ main: { name: '' } } as any) as MetaData, + computeMetadata: undefined, error: '', isLoading: true } @@ -44,10 +48,19 @@ class Asset extends Component { try { const { ocean } = this.context const ddo = await ocean.assets.resolve(this.props.match.params.did) + const { attributes } = ddo.findServiceByType('metadata') + let computeAttributes + + try { + computeAttributes = ddo.findServiceByType('compute') + } catch (error) {} + this.setState({ + ocean, ddo, metadata: attributes, + computeMetadata: computeAttributes, isLoading: false }) } catch (error) { @@ -59,7 +72,14 @@ class Asset extends Component { } public render() { - const { metadata, ddo, error, isLoading } = this.state + const { + metadata, + ddo, + error, + isLoading, + computeMetadata, + ocean + } = this.state const { main, additionalInformation } = metadata const hasError = error !== '' @@ -88,7 +108,12 @@ class Asset extends Component { } > - + ) diff --git a/client/src/data/computeOptions.json b/client/src/data/computeOptions.json new file mode 100644 index 0000000..ebc3f1b --- /dev/null +++ b/client/src/data/computeOptions.json @@ -0,0 +1,18 @@ +[ + { + "name": "nodejs", + "value": { + "entrypoint": "node $ALGO", + "image": "node", + "tag": "10" + } + }, + { + "name": "pyhton3.6", + "value": { + "entrypoint": "python3.6 $ALGO", + "image": "python", + "tag": "3.6" + } + } +] \ No newline at end of file diff --git a/client/src/data/form-publish.json b/client/src/data/form-publish.json index 9e93b66..df85c27 100644 --- a/client/src/data/form-publish.json +++ b/client/src/data/form-publish.json @@ -11,6 +11,16 @@ "required": true, "help": "Enter a concise title. You will be able to enter a more thorough description in the next step." }, + "datasetType":{ + "label": "Dataset type", + "help": "Pick the type which best fits your data set.", + "type": "select", + "required": true, + "options": [ + "access", + "compute" + ] + }, "files": { "label": "Files", "placeholder": "e.g. https://file.com/file.json", diff --git a/client/src/routes/Publish/index.tsx b/client/src/routes/Publish/index.tsx index 84e71bb..d6ba9d1 100644 --- a/client/src/routes/Publish/index.tsx +++ b/client/src/routes/Publish/index.tsx @@ -14,7 +14,7 @@ import Content from '../../components/atoms/Content' import withTracker from '../../hoc/withTracker' type AssetType = 'dataset' | 'algorithm' | 'container' | 'workflow' | 'other' - +type DatasetType = 'compute' | 'access' interface PublishState { name?: string dateCreated?: string @@ -26,6 +26,7 @@ interface PublishState { type?: AssetType copyrightHolder?: string categories?: string + datasetType?: DatasetType currentStep?: number publishingStep?: number @@ -60,6 +61,7 @@ class Publish extends Component<{}, PublishState> { license: '', copyrightHolder: '', categories: '', + datasetType: 'access' as DatasetType, currentStep: 1, isPublishing: false, @@ -133,7 +135,8 @@ class Publish extends Component<{}, PublishState> { isPublishing: false, isPublished: false, publishingStep: 0, - currentStep: 1 + currentStep: 1, + datasetType: 'access' as DatasetType }) } @@ -256,6 +259,32 @@ class Publish extends Component<{}, PublishState> { } } + private async createComputeService( + ocean: any, + publisher: any, + price: string, + datePublished: string + ) { + const { templates } = ocean.keeper + const serviceAgreementTemplate = await templates.escrowComputeExecutionTemplate.getServiceAgreementTemplate() + const name = 'dataAssetComputingServiceAgreement' + return { + type: 'compute', + serviceEndpoint: ocean.brizo.getComputeEndpoint(), + templateId: templates.escrowComputeExecutionTemplate.getId(), + attributes: { + main: { + creator: publisher.getId(), + datePublished, + price, + timeout: 3600, + name + }, + serviceAgreementTemplate + } + } + } + private handleRegisterAsset = async (event: FormEvent) => { event.preventDefault() @@ -305,8 +334,21 @@ class Publish extends Component<{}, PublishState> { } try { + let computeService: any[] = [] + console.log('dataset type ', this.state.datasetType) + if (this.state.datasetType === 'compute') { + const service = await this.createComputeService( + ocean, + account[0], + this.state.price, + this.state.dateCreated + ) + computeService = [service] + console.log('compute ', computeService) + } + const asset = await this.context.ocean.assets - .create(newAsset, account[0]) + .create(newAsset, account[0], computeService) .next((publishingStep: number) => this.setState({ publishingStep }) )