This is an automated email from the ASF dual-hosted git repository. ganning pushed a commit to branch public-private-resources in repository https://gitbox.apache.org/repos/asf/airavata-portals.git
commit a38ed8309b027ee04bd39dcebe8e8ddbe41f6b43 Author: ganning127 <[email protected]> AuthorDate: Mon Jun 30 23:57:42 2025 -0700 support public/private resources --- .gitignore | 0 airavata-research-portal/package-lock.json | 174 +++++++++------------ .../src/components/add/AddGitUrl.tsx | 13 ++ .../src/components/add/DatasetSearch.tsx | 2 +- .../src/components/add/RepoSearch.tsx | 2 +- .../src/components/home/ResourceCard.tsx | 12 +- .../resources/PrivateResourceTooltip.tsx | 10 ++ .../src/components/resources/ResourceDetails.tsx | 21 ++- .../src/interfaces/PrivacyEnum.ts | 8 + 9 files changed, 135 insertions(+), 107 deletions(-) diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..e69de29bb diff --git a/airavata-research-portal/package-lock.json b/airavata-research-portal/package-lock.json index dc7728207..d5639e572 100644 --- a/airavata-research-portal/package-lock.json +++ b/airavata-research-portal/package-lock.json @@ -816,6 +816,84 @@ "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", "wrap-ansi": "^8.1.0", "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "string-width-cjs": { + "version": "npm:[email protected]", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "dependencies": { + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + }, + "strip-ansi-cjs": { + "version": "npm:[email protected]", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "wrap-ansi-cjs": { + "version": "npm:[email protected]", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "dependencies": { + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + } + } + } } }, "@jridgewell/gen-mapping": { @@ -3699,40 +3777,6 @@ "strip-ansi": "^7.0.1" } }, - "string-width-cjs": { - "version": "npm:[email protected]", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, "strip-ansi": { "version": "7.1.0", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", @@ -3742,23 +3786,6 @@ "ansi-regex": "^6.0.1" } }, - "strip-ansi-cjs": { - "version": "npm:[email protected]", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - } - } - }, "strip-json-comments": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", @@ -4011,51 +4038,6 @@ } } }, - "wrap-ansi-cjs": { - "version": "npm:[email protected]", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", - "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "dependencies": { - "ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true - }, - "emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "dev": true - }, - "string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "dev": true, - "requires": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - } - }, - "strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "dev": true, - "requires": { - "ansi-regex": "^5.0.1" - } - } - } - }, "yallist": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", diff --git a/airavata-research-portal/src/components/add/AddGitUrl.tsx b/airavata-research-portal/src/components/add/AddGitUrl.tsx index 79f2a2183..94948f023 100644 --- a/airavata-research-portal/src/components/add/AddGitUrl.tsx +++ b/airavata-research-portal/src/components/add/AddGitUrl.tsx @@ -2,6 +2,8 @@ import {Button, Code, Input, Text, VStack} from "@chakra-ui/react"; import {useState} from "react"; import yaml from "js-yaml"; import {CreateResourceRequest} from "@/interfaces/Requests/CreateResourceRequest"; +import {isPrivacyEnum, PrivacyEnum} from "@/interfaces/PrivacyEnum.ts"; +import {toaster} from "@/components/ui/toaster.tsx"; export const AddGitUrl = ({ nextStage, @@ -47,6 +49,16 @@ export const AddGitUrl = ({ // eslint-disable-next-line @typescript-eslint/no-explicit-any const parsed = yaml.load(fileContent) as any; + + console.log(parsed); + if (!isPrivacyEnum(parsed.project.privacy)) { + toaster.create({ + title: "Invalid `privacy` field", + description: `Received '${parsed.privacy}'. Valid values are 'PUBLIC' or 'PRIVATE'`, + type: "error" + }) + return; + } setCreateResourceRequest({ ...createResourceRequest, name: parsed.project.name, @@ -54,6 +66,7 @@ export const AddGitUrl = ({ description: parsed.project.description, tags: parsed.project.tags, authors: parsed.project.authors, + privacy: parsed.project.privacy as PrivacyEnum }); nextStage(); } catch (error) { diff --git a/airavata-research-portal/src/components/add/DatasetSearch.tsx b/airavata-research-portal/src/components/add/DatasetSearch.tsx index 915f24189..9df9f97bb 100644 --- a/airavata-research-portal/src/components/add/DatasetSearch.tsx +++ b/airavata-research-portal/src/components/add/DatasetSearch.tsx @@ -39,7 +39,7 @@ export const DatasetSearchInput = ({ const timeout = setTimeout(async () => { try { - const response = await api.get(`${CONTROLLER.resources}/public/search`, { + const response = await api.get(`${CONTROLLER.resources}/search`, { params: { type: ResourceTypeEnum.DATASET, name: datasetSearch, diff --git a/airavata-research-portal/src/components/add/RepoSearch.tsx b/airavata-research-portal/src/components/add/RepoSearch.tsx index cc230b0d1..b49ceb634 100644 --- a/airavata-research-portal/src/components/add/RepoSearch.tsx +++ b/airavata-research-portal/src/components/add/RepoSearch.tsx @@ -60,7 +60,7 @@ const RepoSearchInput = ({ const timeout = setTimeout(async () => { try { - const response = await api.get(`${CONTROLLER.resources}/public/search`, { + const response = await api.get(`${CONTROLLER.resources}/search`, { params: { type: ResourceTypeEnum.REPOSITORY, name: repoSearch, diff --git a/airavata-research-portal/src/components/home/ResourceCard.tsx b/airavata-research-portal/src/components/home/ResourceCard.tsx index 6846a1909..c612e959c 100644 --- a/airavata-research-portal/src/components/home/ResourceCard.tsx +++ b/airavata-research-portal/src/components/home/ResourceCard.tsx @@ -27,6 +27,8 @@ import {ModelCardButton} from "../models/ModelCardButton"; import {useState} from "react"; import {Link} from 'react-router'; import {ResourceOptions} from "@/components/resources/ResourceOptions.tsx"; +import {PrivacyEnum} from "@/interfaces/PrivacyEnum.ts"; +import {PrivateResourceTooltip} from "@/components/resources/PrivateResourceTooltip.tsx"; export const ResourceCard = ({ resource, @@ -87,8 +89,14 @@ export const ResourceCard = ({ <Card.Header> <HStack justifyContent={'space-between'} alignItems={'center'} flexWrap={'wrap'}> <Card.Title>{resource.name}</Card.Title> - <ResourceOptions deleteable={deletable} resource={resource} onDeleteSuccess={hideCardCallback} - onUnStarSuccess={removeOnUnStar ? hideCardCallback : dummyOnUnStarSuccess}/> + + <HStack alignItems={'center'}> + {resource.privacy === PrivacyEnum.PRIVATE && + <PrivateResourceTooltip/> + } + <ResourceOptions deleteable={deletable} resource={resource} onDeleteSuccess={hideCardCallback} + onUnStarSuccess={removeOnUnStar ? hideCardCallback : dummyOnUnStarSuccess}/> + </HStack> </HStack> </Card.Header> diff --git a/airavata-research-portal/src/components/resources/PrivateResourceTooltip.tsx b/airavata-research-portal/src/components/resources/PrivateResourceTooltip.tsx new file mode 100644 index 000000000..160d930a2 --- /dev/null +++ b/airavata-research-portal/src/components/resources/PrivateResourceTooltip.tsx @@ -0,0 +1,10 @@ +import {Tooltip} from "@/components/ui/tooltip.tsx"; +import {IoEyeOffOutline} from "react-icons/io5"; + +export const PrivateResourceTooltip = () => { + return ( + <Tooltip content={"This resource is private and can only be seen by the resource's authors"}> + <IoEyeOffOutline/> + </Tooltip> + ) +} \ No newline at end of file diff --git a/airavata-research-portal/src/components/resources/ResourceDetails.tsx b/airavata-research-portal/src/components/resources/ResourceDetails.tsx index fdb3c4a35..ee9ad28a1 100644 --- a/airavata-research-portal/src/components/resources/ResourceDetails.tsx +++ b/airavata-research-portal/src/components/resources/ResourceDetails.tsx @@ -54,6 +54,8 @@ import {CONTROLLER} from "@/lib/controller"; import {DatasetSpecificDetails} from "../datasets/DatasetSpecificDetails"; import {ResourceOptions} from "@/components/resources/ResourceOptions.tsx"; import {toaster} from "@/components/ui/toaster.tsx"; +import {PrivacyEnum} from "@/interfaces/PrivacyEnum.ts"; +import {PrivateResourceTooltip} from "@/components/resources/PrivateResourceTooltip.tsx"; async function getResource(id: string) { const response = await api.get(`${CONTROLLER.resources}/public/${id}`); @@ -148,13 +150,18 @@ const ResourceDetails = () => { {resource.name} </Heading> - <ResourceOptions - resource={resource} - onDeleteSuccess={goToResources} - deleteable={true} - onUnStarSuccess={() => { - }} - /> + <HStack> + {resource.privacy === PrivacyEnum.PRIVATE && + <PrivateResourceTooltip/> + } + <ResourceOptions + resource={resource} + onDeleteSuccess={goToResources} + deleteable={true} + onUnStarSuccess={() => { + }} + /> + </HStack> </HStack> diff --git a/airavata-research-portal/src/interfaces/PrivacyEnum.ts b/airavata-research-portal/src/interfaces/PrivacyEnum.ts index be9aade1d..a23747c72 100644 --- a/airavata-research-portal/src/interfaces/PrivacyEnum.ts +++ b/airavata-research-portal/src/interfaces/PrivacyEnum.ts @@ -1,4 +1,12 @@ export enum PrivacyEnum { PUBLIC = "PUBLIC", PRIVATE = "PRIVATE", +} + +export function isPrivacyEnum(value: string | undefined): boolean { + if (value === undefined || value === null) { + return false; + } + + return Object.values(PrivacyEnum).includes(value as PrivacyEnum); } \ No newline at end of file
