From 45eea31d66d339b39f92bb9aff930dbe8f6f4078 Mon Sep 17 00:00:00 2001 From: Bogdan Fazakas Date: Mon, 19 Sep 2022 15:54:07 +0300 Subject: [PATCH 01/21] WIP enforce docker container --- content/publish/form.json | 13 +-- .../FormFields/ContainerInput/index.tsx | 98 +++++++++++++++++++ .../@shared/FormFields/URLInput/index.tsx | 4 +- .../@shared/FormInput/InputElement.tsx | 3 + src/components/Publish/Metadata/index.tsx | 5 +- 5 files changed, 114 insertions(+), 9 deletions(-) create mode 100644 src/components/@shared/FormFields/ContainerInput/index.tsx diff --git a/content/publish/form.json b/content/publish/form.json index fef5b8d7b..c2deac798 100644 --- a/content/publish/form.json +++ b/content/publish/form.json @@ -55,15 +55,16 @@ { "name": "dockerImageCustom", "label": "Docker Image URL", - "placeholder": "e.g. oceanprotocol/algo_dockers or https://example.com/image_path", - "help": "Provide the name of a public Docker image or the full url if you have it hosted in a 3rd party repo", + "placeholder": "e.g. oceanprotocol/algo_dockers:latest or https://example.com/image_path:image_tag", + "help": "Provide the name and the tag of a public Docker hub image or the full url if you have it hosted in a 3rd party repo", + "type": "container", "required": true }, { - "name": "dockerImageCustomTag", - "label": "Docker Image Tag", - "placeholder": "e.g. latest", - "help": "Provide the tag for your Docker image.", + "name": "dockerImageChecksum", + "label": "Docker container checksum", + "placeholder": "e.g. sha256:xiXqb7Vet0FbN9q0GFMgUdi5C22wjJT0i2G6lYKC2jl6QxkKzVz7KaPDgqfTMjNF", + "help": "Provide the checksum of your docker container image.", "required": true }, { diff --git a/src/components/@shared/FormFields/ContainerInput/index.tsx b/src/components/@shared/FormFields/ContainerInput/index.tsx new file mode 100644 index 000000000..705426bc8 --- /dev/null +++ b/src/components/@shared/FormFields/ContainerInput/index.tsx @@ -0,0 +1,98 @@ +import React, { ReactElement, useState } from 'react' +import { useField, useFormikContext } from 'formik' +import UrlInput from '../URLInput' +import { InputProps } from '@shared/FormInput' +import { FormPublishData } from 'src/components/Publish/_types' +import { LoggerInstance } from '@oceanprotocol/lib' +import { toast } from 'react-toastify' +import axios from 'axios' +import isUrl from 'is-url-superb' + +export default function FilesInput(props: InputProps): ReactElement { + const [field, meta, helpers] = useField(props.name) + const [isLoading, setIsLoading] = useState(false) + const { values, setFieldError } = useFormikContext() + + async function getContainerChecksum( + image: string, + tag: string + ): Promise { + try { + const response = await axios.post( + `https://dockerhub-proxy.oceanprotocol.com`, + { + image, + tag + } + ) + if ( + !response || + response.status !== 200 || + response.data.status !== 'success' + ) { + toast.error( + 'Could not fetch docker hub image info. Please input the container checksum manually' + ) + return null + } + return response.data.result.checksum + } catch (error) { + LoggerInstance.error(error.message) + toast.error( + 'Could not fetch docker hub image info. Please input the container checksum manually' + ) + return null + } + } + + async function handleValidation(e: React.SyntheticEvent, container: string) { + e.preventDefault() + try { + console.log('isUrl(container)', isUrl(container)) + setIsLoading(true) + if (!isUrl(container, { lenient: false })) { + const parsedContainerValue = container.split(':') + const checksum = await getContainerChecksum( + parsedContainerValue[0], + parsedContainerValue[1] + ) + values.metadata.dockerImageCustom = parsedContainerValue[0] + values.metadata.dockerImageCustomTag = parsedContainerValue[1] + if (checksum) { + values.metadata.dockerImageCustomChecksum = checksum + } + } else { + console.log('open input modal') + } + } catch (error) { + setFieldError(`${field.name}[0].url`, error.message) + LoggerInstance.error(error.message) + } finally { + setIsLoading(false) + } + } + + function handleClose() { + helpers.setValue(meta.initialValue) + helpers.setTouched(false) + } + + return ( + <> + + + {/* {field?.value?.[0]?.valid === true ? ( + + ) : ( + + )} */} + + ) +} diff --git a/src/components/@shared/FormFields/URLInput/index.tsx b/src/components/@shared/FormFields/URLInput/index.tsx index 3890032c9..af7d0fdf3 100644 --- a/src/components/@shared/FormFields/URLInput/index.tsx +++ b/src/components/@shared/FormFields/URLInput/index.tsx @@ -12,12 +12,14 @@ export default function URLInput({ handleButtonClick, isLoading, name, + checkUrl, ...props }: { submitText: string handleButtonClick(e: React.SyntheticEvent, data: string): void isLoading: boolean name: string + checkUrl?: boolean }): ReactElement { const [field, meta] = useField(name) const [isButtonDisabled, setIsButtonDisabled] = useState(true) @@ -28,7 +30,7 @@ export default function URLInput({ setIsButtonDisabled( !field?.value || field.value === '' || - !isUrl(field.value) || + (checkUrl && !isUrl(field.value)) || field.value.includes('javascript:') || meta?.error ) diff --git a/src/components/@shared/FormInput/InputElement.tsx b/src/components/@shared/FormInput/InputElement.tsx index 343889238..791e5ba73 100644 --- a/src/components/@shared/FormInput/InputElement.tsx +++ b/src/components/@shared/FormInput/InputElement.tsx @@ -11,6 +11,7 @@ import AssetSelection, { } from '../FormFields/AssetSelection' import Nft from '../FormFields/Nft' import InputRadio from './InputRadio' +import ContainerInput from '@shared/FormFields/ContainerInput' const cx = classNames.bind(styles) @@ -107,6 +108,8 @@ export default function InputElement({ ) case 'files': return + case 'container': + return case 'providerUrl': return case 'nft': diff --git a/src/components/Publish/Metadata/index.tsx b/src/components/Publish/Metadata/index.tsx index 2be4852bd..1954acbb6 100644 --- a/src/components/Publish/Metadata/index.tsx +++ b/src/components/Publish/Metadata/index.tsx @@ -124,11 +124,12 @@ export default function MetadataFields(): ReactElement { /> Date: Tue, 20 Sep 2022 10:32:17 +0300 Subject: [PATCH 02/21] more UI updates --- .../FormFields/ContainerInput/Info.module.css | 49 +++++++++++++++++ .../FormFields/ContainerInput/Info.tsx | 24 ++++++++ .../FormFields/ContainerInput/index.tsx | 55 +++++++++++-------- src/components/Publish/Metadata/index.tsx | 2 +- 4 files changed, 105 insertions(+), 25 deletions(-) create mode 100644 src/components/@shared/FormFields/ContainerInput/Info.module.css create mode 100644 src/components/@shared/FormFields/ContainerInput/Info.tsx diff --git a/src/components/@shared/FormFields/ContainerInput/Info.module.css b/src/components/@shared/FormFields/ContainerInput/Info.module.css new file mode 100644 index 000000000..a9a95d6d8 --- /dev/null +++ b/src/components/@shared/FormFields/ContainerInput/Info.module.css @@ -0,0 +1,49 @@ +.info { + border-radius: var(--border-radius); + padding: calc(var(--spacer) / 2); + border: 1px solid var(--border-color); + background-color: var(--background-highlight); + position: relative; +} + +.info ul { + margin: 0; +} + +.info li { + display: inline-block; + font-size: var(--font-size-small); + margin-right: calc(var(--spacer) / 2); + color: var(--color-secondary); +} + +.info li.success { + color: var(--brand-alert-green); +} + +.url { + margin: 0; + font-size: var(--font-size-base); + line-height: var(--line-height); + overflow-wrap: break-word; + word-wrap: break-word; + word-break: break-all; + padding-right: calc(var(--spacer) / 2); +} + +.warning { + margin-top: calc(var(--spacer) / 3); + margin-left: 0; +} + +.removeButton { + cursor: pointer; + border: none; + position: absolute; + top: -0.2rem; + right: 0; + font-size: var(--font-size-h3); + cursor: pointer; + color: var(--font-color-text); + background-color: transparent; +} diff --git a/src/components/@shared/FormFields/ContainerInput/Info.tsx b/src/components/@shared/FormFields/ContainerInput/Info.tsx new file mode 100644 index 000000000..9054b3101 --- /dev/null +++ b/src/components/@shared/FormFields/ContainerInput/Info.tsx @@ -0,0 +1,24 @@ +import React, { ReactElement } from 'react' +import styles from './Info.module.css' + +export default function ImageInfo({ + image, + tag, + handleClose +}: { + image: string + tag: string + handleClose(): void +}): ReactElement { + return ( +
+

{`${image}:${tag}`}

+
    +
  • ✓ Image found
  • +
+ +
+ ) +} diff --git a/src/components/@shared/FormFields/ContainerInput/index.tsx b/src/components/@shared/FormFields/ContainerInput/index.tsx index 705426bc8..f59e5c5c7 100644 --- a/src/components/@shared/FormFields/ContainerInput/index.tsx +++ b/src/components/@shared/FormFields/ContainerInput/index.tsx @@ -7,11 +7,14 @@ import { LoggerInstance } from '@oceanprotocol/lib' import { toast } from 'react-toastify' import axios from 'axios' import isUrl from 'is-url-superb' +import ImageInfo from './Info' -export default function FilesInput(props: InputProps): ReactElement { +export default function ContainerInput(props: InputProps): ReactElement { const [field, meta, helpers] = useField(props.name) const [isLoading, setIsLoading] = useState(false) - const { values, setFieldError } = useFormikContext() + const [isValid, setIsValid] = useState(false) + const { values, setFieldError, setFieldValue } = + useFormikContext() async function getContainerChecksum( image: string, @@ -52,17 +55,16 @@ export default function FilesInput(props: InputProps): ReactElement { setIsLoading(true) if (!isUrl(container, { lenient: false })) { const parsedContainerValue = container.split(':') - const checksum = await getContainerChecksum( - parsedContainerValue[0], - parsedContainerValue[1] - ) - values.metadata.dockerImageCustom = parsedContainerValue[0] - values.metadata.dockerImageCustomTag = parsedContainerValue[1] + const image = parsedContainerValue[0] + const tag = parsedContainerValue[1] + const checksum = await getContainerChecksum(image, tag) + setFieldValue('metadata.dockerImageCustom', image) + setFieldValue('metadata.dockerImageCustomTag', tag) if (checksum) { - values.metadata.dockerImageCustomChecksum = checksum + setFieldValue('metadata.dockerImageCustomChecksum', checksum) + setIsValid(true) } } else { - console.log('open input modal') } } catch (error) { setFieldError(`${field.name}[0].url`, error.message) @@ -73,26 +75,31 @@ export default function FilesInput(props: InputProps): ReactElement { } function handleClose() { - helpers.setValue(meta.initialValue) + setFieldValue('metadata.dockerImageCustom', '') + setFieldValue('metadata.dockerImageCustomTag', '') + setFieldValue('metadata.dockerImageCustomChecksum', '') + setIsValid(false) helpers.setTouched(false) } return ( <> - - - {/* {field?.value?.[0]?.valid === true ? ( - + {isValid ? ( + ) : ( - - )} */} + + )} ) } diff --git a/src/components/Publish/Metadata/index.tsx b/src/components/Publish/Metadata/index.tsx index 1954acbb6..b9e878432 100644 --- a/src/components/Publish/Metadata/index.tsx +++ b/src/components/Publish/Metadata/index.tsx @@ -129,7 +129,7 @@ export default function MetadataFields(): ReactElement { )} component={Input} name="metadata.dockerImageCustomChecksum" - disabled + disabled={values.metadata.dockerImageCustomChecksum !== null} /> Date: Tue, 20 Sep 2022 10:43:17 +0300 Subject: [PATCH 03/21] fix build --- src/components/@shared/FormFields/ContainerInput/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/components/@shared/FormFields/ContainerInput/index.tsx b/src/components/@shared/FormFields/ContainerInput/index.tsx index f59e5c5c7..ccc64edc4 100644 --- a/src/components/@shared/FormFields/ContainerInput/index.tsx +++ b/src/components/@shared/FormFields/ContainerInput/index.tsx @@ -64,7 +64,6 @@ export default function ContainerInput(props: InputProps): ReactElement { setFieldValue('metadata.dockerImageCustomChecksum', checksum) setIsValid(true) } - } else { } } catch (error) { setFieldError(`${field.name}[0].url`, error.message) From fe4aa79ffb15d332752cbc95b925e9d3a7939747 Mon Sep 17 00:00:00 2001 From: Bogdan Fazakas Date: Tue, 20 Sep 2022 11:30:11 +0300 Subject: [PATCH 04/21] fix default images --- src/@utils/docker.ts | 50 ++++--------------- .../FormFields/ContainerInput/index.tsx | 33 +----------- src/components/Publish/Metadata/index.tsx | 2 +- src/components/Publish/_constants.tsx | 10 ++-- src/components/Publish/_utils.ts | 18 ++++--- 5 files changed, 27 insertions(+), 86 deletions(-) diff --git a/src/@utils/docker.ts b/src/@utils/docker.ts index 9453f5447..a94dd0a6b 100644 --- a/src/@utils/docker.ts +++ b/src/@utils/docker.ts @@ -1,16 +1,18 @@ import { LoggerInstance } from '@oceanprotocol/lib' import axios from 'axios' -import isUrl from 'is-url-superb' import { toast } from 'react-toastify' -async function isDockerHubImageValid( +export async function getContainerChecksum( image: string, tag: string -): Promise { +): Promise { try { const response = await axios.post( `https://dockerhub-proxy.oceanprotocol.com`, - { image, tag } + { + image, + tag + } ) if ( !response || @@ -18,46 +20,16 @@ async function isDockerHubImageValid( response.data.status !== 'success' ) { toast.error( - 'Could not fetch docker hub image info. Please check image name and tag and try again' + 'Could not fetch docker hub image info. Please input the container checksum manually' ) - return false + return null } - - return true + return response.data.result.checksum } catch (error) { LoggerInstance.error(error.message) toast.error( - 'Could not fetch docker hub image info. Please check image name and tag and try again' + 'Could not fetch docker hub image info. Please input the container checksum manually' ) - return false + return null } } - -async function is3rdPartyImageValid(imageURL: string): Promise { - try { - const response = await axios.head(imageURL) - if (!response || response.status !== 200) { - toast.error( - 'Could not fetch docker image info. Please check URL and try again' - ) - return false - } - return true - } catch (error) { - LoggerInstance.error(error.message) - toast.error( - 'Could not fetch docker image info. Please check URL and try again' - ) - return false - } -} - -export async function validateDockerImage( - dockerImage: string, - tag: string -): Promise { - const isValid = isUrl(dockerImage) - ? await is3rdPartyImageValid(dockerImage) - : await isDockerHubImageValid(dockerImage, tag) - return isValid -} diff --git a/src/components/@shared/FormFields/ContainerInput/index.tsx b/src/components/@shared/FormFields/ContainerInput/index.tsx index ccc64edc4..b2375aa65 100644 --- a/src/components/@shared/FormFields/ContainerInput/index.tsx +++ b/src/components/@shared/FormFields/ContainerInput/index.tsx @@ -8,6 +8,7 @@ import { toast } from 'react-toastify' import axios from 'axios' import isUrl from 'is-url-superb' import ImageInfo from './Info' +import { getContainerChecksum } from '@utils/docker' export default function ContainerInput(props: InputProps): ReactElement { const [field, meta, helpers] = useField(props.name) @@ -16,38 +17,6 @@ export default function ContainerInput(props: InputProps): ReactElement { const { values, setFieldError, setFieldValue } = useFormikContext() - async function getContainerChecksum( - image: string, - tag: string - ): Promise { - try { - const response = await axios.post( - `https://dockerhub-proxy.oceanprotocol.com`, - { - image, - tag - } - ) - if ( - !response || - response.status !== 200 || - response.data.status !== 'success' - ) { - toast.error( - 'Could not fetch docker hub image info. Please input the container checksum manually' - ) - return null - } - return response.data.result.checksum - } catch (error) { - LoggerInstance.error(error.message) - toast.error( - 'Could not fetch docker hub image info. Please input the container checksum manually' - ) - return null - } - } - async function handleValidation(e: React.SyntheticEvent, container: string) { e.preventDefault() try { diff --git a/src/components/Publish/Metadata/index.tsx b/src/components/Publish/Metadata/index.tsx index b9e878432..1954acbb6 100644 --- a/src/components/Publish/Metadata/index.tsx +++ b/src/components/Publish/Metadata/index.tsx @@ -129,7 +129,7 @@ export default function MetadataFields(): ReactElement { )} component={Input} name="metadata.dockerImageCustomChecksum" - disabled={values.metadata.dockerImageCustomChecksum !== null} + disabled /> { if (dockerImage === '') return const preset = algorithmContainerPresets.find( (preset) => `${preset.image}:${preset.tag}` === dockerImage ) + preset.checksum = await getContainerChecksum(preset.image, preset.tag) + console.log('preset') return preset } @@ -111,20 +114,19 @@ export async function transformPublishFormToDdo( entrypoint: dockerImage === 'custom' ? dockerImageCustomEntrypoint - : getAlgorithmContainerPreset(dockerImage).entrypoint, + : (await getAlgorithmContainerPreset(dockerImage)).entrypoint, image: dockerImage === 'custom' ? dockerImageCustom - : getAlgorithmContainerPreset(dockerImage).image, + : (await getAlgorithmContainerPreset(dockerImage)).image, tag: dockerImage === 'custom' ? dockerImageCustomTag - : getAlgorithmContainerPreset(dockerImage).tag, + : (await getAlgorithmContainerPreset(dockerImage)).tag, checksum: dockerImage === 'custom' - ? // ? dockerImageCustomChecksum - '' - : getAlgorithmContainerPreset(dockerImage).checksum + ? dockerImageCustomChecksum + : (await getAlgorithmContainerPreset(dockerImage)).checksum } } }) From 2ac422f1751addf9b6d5ccb776c50bb0b4a6889d Mon Sep 17 00:00:00 2001 From: Bogdan Fazakas Date: Tue, 20 Sep 2022 17:32:55 +0300 Subject: [PATCH 05/21] more ui touches --- .../FormFields/ContainerInput/Info.module.css | 11 +++---- .../FormFields/ContainerInput/Info.tsx | 9 ++++-- .../FormFields/ContainerInput/index.tsx | 31 +++++++++---------- src/components/Publish/Metadata/index.tsx | 1 - src/components/Publish/_utils.ts | 1 - 5 files changed, 27 insertions(+), 26 deletions(-) diff --git a/src/components/@shared/FormFields/ContainerInput/Info.module.css b/src/components/@shared/FormFields/ContainerInput/Info.module.css index a9a95d6d8..ed25b655f 100644 --- a/src/components/@shared/FormFields/ContainerInput/Info.module.css +++ b/src/components/@shared/FormFields/ContainerInput/Info.module.css @@ -21,7 +21,11 @@ color: var(--brand-alert-green); } -.url { +.info li.error { + color: var(--brand-alert-red); +} + +.contianer { margin: 0; font-size: var(--font-size-base); line-height: var(--line-height); @@ -31,11 +35,6 @@ padding-right: calc(var(--spacer) / 2); } -.warning { - margin-top: calc(var(--spacer) / 3); - margin-left: 0; -} - .removeButton { cursor: pointer; border: none; diff --git a/src/components/@shared/FormFields/ContainerInput/Info.tsx b/src/components/@shared/FormFields/ContainerInput/Info.tsx index 9054b3101..a24332273 100644 --- a/src/components/@shared/FormFields/ContainerInput/Info.tsx +++ b/src/components/@shared/FormFields/ContainerInput/Info.tsx @@ -4,17 +4,22 @@ import styles from './Info.module.css' export default function ImageInfo({ image, tag, + valid, handleClose }: { image: string tag: string + valid: boolean handleClose(): void }): ReactElement { + const displayText = valid + ? '✓ Image found, container checksum automatically added!' + : 'x Container checksum could not be fetched automatically, please add it manually' return (
-

{`${image}:${tag}`}

+

{`Image: ${image} Tag: ${tag}`}

    -
  • ✓ Image found
  • +
  • {displayText}