diff --git a/portals-ui/package-lock.json b/portals-ui/package-lock.json index e135bc8514..69db577f3e 100644 --- a/portals-ui/package-lock.json +++ b/portals-ui/package-lock.json @@ -4901,6 +4901,271 @@ "storybook": "^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0" } }, + "node_modules/@svgr/babel-plugin-add-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-add-jsx-attribute/-/babel-plugin-add-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-b9MIk7yhdS1pMCZM8VeNfUlSKVRhsHZNMl5O9SfaX0l0t5wjdgu4IDzGB8bpnGBBOjGST3rRFVsaaEtI4W6f7g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-attribute": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-attribute/-/babel-plugin-remove-jsx-attribute-8.0.0.tgz", + "integrity": "sha512-BcCkm/STipKvbCl6b7QFrMh/vx00vIP63k2eM66MfHJzPr6O2U0jYEViXkHJWqXqQYjdeA9cuCl5KWmlwjDvbA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-remove-jsx-empty-expression": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-remove-jsx-empty-expression/-/babel-plugin-remove-jsx-empty-expression-8.0.0.tgz", + "integrity": "sha512-5BcGCBfBxB5+XSDSWnhTThfI9jcO5f0Ai2V24gZpG+wXF14BzwxxdDb4g6trdOux0rhibGs385BeFMSmxtS3uA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-replace-jsx-attribute-value": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-replace-jsx-attribute-value/-/babel-plugin-replace-jsx-attribute-value-8.0.0.tgz", + "integrity": "sha512-KVQ+PtIjb1BuYT3ht8M5KbzWBhdAjjUPdlMtpuw/VjT8coTrItWX6Qafl9+ji831JaJcu6PJNKCV0bp01lBNzQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-dynamic-title": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-dynamic-title/-/babel-plugin-svg-dynamic-title-8.0.0.tgz", + "integrity": "sha512-omNiKqwjNmOQJ2v6ge4SErBbkooV2aAWwaPFs2vUY7p7GhVkzRkJ00kILXQvRhA6miHnNpXv7MRnnSjdRjK8og==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-svg-em-dimensions": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-svg-em-dimensions/-/babel-plugin-svg-em-dimensions-8.0.0.tgz", + "integrity": "sha512-mURHYnu6Iw3UBTbhGwE/vsngtCIbHE43xCRK7kCw4t01xyGqb2Pd+WXekRRoFOBIY29ZoOhUCTEweDMdrjfi9g==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-react-native-svg": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-react-native-svg/-/babel-plugin-transform-react-native-svg-8.1.0.tgz", + "integrity": "sha512-Tx8T58CHo+7nwJ+EhUwx3LfdNSG9R2OKfaIXXs5soiy5HtgoAEkDay9LIimLOcG8dJQH1wPZp/cnAv6S9CrR1Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-plugin-transform-svg-component": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-plugin-transform-svg-component/-/babel-plugin-transform-svg-component-8.0.0.tgz", + "integrity": "sha512-DFx8xa3cZXTdb/k3kfPeaixecQLgKh5NVBMwD0AQxOzcZawK4oo1Jh9LbrcACUivsCA7TLG8eeWgrDXjTMhRmw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/babel-preset": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/babel-preset/-/babel-preset-8.1.0.tgz", + "integrity": "sha512-7EYDbHE7MxHpv4sxvnVPngw5fuR6pw79SkcrILHJ/iMpuKySNCl5W1qcwPEpU+LgyRXOaAFgH0KhwD18wwg6ug==", + "dev": true, + "license": "MIT", + "dependencies": { + "@svgr/babel-plugin-add-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-attribute": "8.0.0", + "@svgr/babel-plugin-remove-jsx-empty-expression": "8.0.0", + "@svgr/babel-plugin-replace-jsx-attribute-value": "8.0.0", + "@svgr/babel-plugin-svg-dynamic-title": "8.0.0", + "@svgr/babel-plugin-svg-em-dimensions": "8.0.0", + "@svgr/babel-plugin-transform-react-native-svg": "8.1.0", + "@svgr/babel-plugin-transform-svg-component": "8.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@svgr/core": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/core/-/core-8.1.0.tgz", + "integrity": "sha512-8QqtOQT5ACVlmsvKOJNEaWmRPmcojMOzCz4Hs2BGG/toAp/K38LcsMRyLp349glq5AzJbCEeimEoxaX6v/fLrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "camelcase": "^6.2.0", + "cosmiconfig": "^8.1.3", + "snake-case": "^3.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/core/node_modules/camelcase": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz", + "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@svgr/core/node_modules/cosmiconfig": { + "version": "8.3.6", + "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-8.3.6.tgz", + "integrity": "sha512-kcZ6+W5QzcJ3P1Mt+83OUv/oHFqZHIx8DuxG6eZ5RGMERoLqp4BuGjhHLYGK+Kf5XVkQvqBSmAy/nGWN3qDgEA==", + "dev": true, + "license": "MIT", + "dependencies": { + "import-fresh": "^3.3.0", + "js-yaml": "^4.1.0", + "parse-json": "^5.2.0", + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/d-fischer" + }, + "peerDependencies": { + "typescript": ">=4.9.5" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@svgr/hast-util-to-babel-ast": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/@svgr/hast-util-to-babel-ast/-/hast-util-to-babel-ast-8.0.0.tgz", + "integrity": "sha512-EbDKwO9GpfWP4jN9sGdYwPBU0kdomaPIL2Eu4YwmgP+sJeXT+L7bMwJUBnhzfH8Q2qMBqZ4fJwpCyYsAN3mt2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/types": "^7.21.3", + "entities": "^4.4.0" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + } + }, + "node_modules/@svgr/plugin-jsx": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@svgr/plugin-jsx/-/plugin-jsx-8.1.0.tgz", + "integrity": "sha512-0xiIyBsLlr8quN+WyuxooNW9RJ0Dpr8uOnH/xrCVO8GLUcwHISwj1AG0k+LFzteTkAA0GbX0kj9q6Dk70PTiPA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@babel/core": "^7.21.3", + "@svgr/babel-preset": "8.1.0", + "@svgr/hast-util-to-babel-ast": "8.0.0", + "svg-parser": "^2.0.4" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/gregberge" + }, + "peerDependencies": { + "@svgr/core": "*" + } + }, "node_modules/@testing-library/dom": { "version": "10.4.0", "resolved": "https://registry.npmjs.org/@testing-library/dom/-/dom-10.4.0.tgz", @@ -7884,6 +8149,17 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/dot-prop": { "version": "5.3.0", "resolved": "https://registry.npmjs.org/dot-prop/-/dot-prop-5.3.0.tgz", @@ -11402,6 +11678,16 @@ "dev": true, "license": "MIT" }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dev": true, + "license": "MIT", + "dependencies": { + "tslib": "^2.0.3" + } + }, "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", @@ -12161,6 +12447,17 @@ "dev": true, "license": "MIT" }, + "node_modules/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, "node_modules/node-fetch": { "version": "2.6.7", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", @@ -15711,6 +16008,17 @@ "dev": true, "license": "MIT" }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dev": true, + "license": "MIT", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, "node_modules/socks": { "version": "2.8.3", "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.3.tgz", @@ -16389,6 +16697,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/svg-parser": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/svg-parser/-/svg-parser-2.0.4.tgz", + "integrity": "sha512-e4hG1hRwoOdRb37cIMSgzNsxyzKfayW6VOflrwvR+/bzrkyxY/31WkbgnQpgtrNp1SdpJvpUAGTa/ZoiPNDuRQ==", + "dev": true, + "license": "MIT" + }, "node_modules/svg-tags": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/svg-tags/-/svg-tags-1.0.0.tgz", @@ -17366,6 +17681,21 @@ } } }, + "node_modules/vite-plugin-svgr": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/vite-plugin-svgr/-/vite-plugin-svgr-4.3.0.tgz", + "integrity": "sha512-Jy9qLB2/PyWklpYy0xk0UU3TlU0t2UMpJXZvf+hWII1lAmRHrOUKi11Uw8N3rxoNk7atZNYO3pR3vI1f7oi+6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.1.3", + "@svgr/core": "^8.1.0", + "@svgr/plugin-jsx": "^8.1.0" + }, + "peerDependencies": { + "vite": ">=2.6.0" + } + }, "node_modules/vite/node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -18652,7 +18982,8 @@ "tailwindcss": "^3.4.15", "typescript": "~5.6.2", "typescript-eslint": "^8.11.0", - "vite": "^5.4.10" + "vite": "^5.4.10", + "vite-plugin-svgr": "^4.3.0" } }, "sites/ngs-portal/node_modules/postcss-import": { diff --git a/portals-ui/packages/core/src/utilities/acl.ts b/portals-ui/packages/core/src/utilities/acl.ts index b7ef26d818..ba4de2d494 100644 --- a/portals-ui/packages/core/src/utilities/acl.ts +++ b/portals-ui/packages/core/src/utilities/acl.ts @@ -1,5 +1,23 @@ -import { AclEntry } from '../model/acl.ts'; +function checkPermission(mask: number, permission: number): boolean { + return (mask & permission) == permission; +} +const READ_PERMISSION = 0b0001; +const WRITE_PERMISSION = 0b0010; +const EXECUTE_PERMISSION = 0b0100; +const OWNER_PERMISSION = 0b1000; + +export function readAllowed(mask: number): boolean { + return checkPermission(mask, READ_PERMISSION); +} + +export function writeAllowed(mask: number): boolean { + return checkPermission(mask, WRITE_PERMISSION); +} + +export function executeAllowed(mask: number): boolean { + return checkPermission(mask, EXECUTE_PERMISSION); +} -export function readAllowed(entry: AclEntry): boolean { - return (entry.mask & 1) == 1; +export function ownerAllowed(mask: number): boolean { + return checkPermission(mask, OWNER_PERMISSION); } diff --git a/portals-ui/sites/ngs-portal/package.json b/portals-ui/sites/ngs-portal/package.json index 8941962720..b23f9c681c 100644 --- a/portals-ui/sites/ngs-portal/package.json +++ b/portals-ui/sites/ngs-portal/package.json @@ -46,6 +46,7 @@ "tailwindcss": "^3.4.15", "typescript": "~5.6.2", "typescript-eslint": "^8.11.0", - "vite": "^5.4.10" + "vite": "^5.4.10", + "vite-plugin-svgr": "^4.3.0" } } diff --git a/portals-ui/sites/ngs-portal/src/app/config/router-config/router.tsx b/portals-ui/sites/ngs-portal/src/app/config/router-config/router.tsx index e57c0c9de6..7a790f4807 100644 --- a/portals-ui/sites/ngs-portal/src/app/config/router-config/router.tsx +++ b/portals-ui/sites/ngs-portal/src/app/config/router-config/router.tsx @@ -3,7 +3,7 @@ import { createHashRouter } from 'react-router-dom'; import { Layout } from '../../../pages/layout/index.tsx'; import { Home } from '../../../pages/home/home.tsx'; import Pipelines from '../../../pages/pipelines/index.tsx'; -import Projects from '../../../pages/projects/index.tsx'; +import { ProjectsPage } from '../../../pages/projects'; import Runs from '../../../pages/runs/index.tsx'; import { AppRoutes, RoutePath } from '../../../shared/constants/routes.ts'; @@ -11,7 +11,7 @@ const routerConfig: Record = { [AppRoutes.HOME]: { path: RoutePath[AppRoutes.HOME], element: }, [AppRoutes.PROJECTS]: { path: RoutePath[AppRoutes.PROJECTS], - element: , + element: , }, [AppRoutes.PIPELINES]: { path: RoutePath[AppRoutes.PIPELINES], diff --git a/portals-ui/sites/ngs-portal/src/pages/home/components/index.ts b/portals-ui/sites/ngs-portal/src/pages/home/components/index.ts new file mode 100644 index 0000000000..54f6c70a39 --- /dev/null +++ b/portals-ui/sites/ngs-portal/src/pages/home/components/index.ts @@ -0,0 +1,2 @@ +export { PipelinesList } from './pipelines-list'; +export { ProjectsList } from './projects-list'; diff --git a/portals-ui/sites/ngs-portal/src/pages/home/components/pipeline-card.tsx b/portals-ui/sites/ngs-portal/src/pages/home/components/pipeline-card.tsx new file mode 100644 index 0000000000..f60f19e81a --- /dev/null +++ b/portals-ui/sites/ngs-portal/src/pages/home/components/pipeline-card.tsx @@ -0,0 +1,58 @@ +import { Tag, Badge, FlexRow, RichTextView } from '@epam/uui'; +import ContentPersonFillIcon from '@epam/assets/icons/content-person-fill.svg?react'; +import cn from 'classnames'; +import { Link } from 'react-router-dom'; +import type { Pipeline } from '@cloud-pipeline/core'; +import type { CommonProps } from '@cloud-pipeline/components'; +import HighlightedText from '../../../shared/highlight-text'; + +type Props = CommonProps & { + pipeline: Pipeline; + highlightedText?: string; + tags?: string[]; +}; + +export const PipelineCard = ({ + pipeline, + tags, + highlightedText, + className, + style, +}: Props) => { + const { id, name, owner, description } = pipeline; + + return ( +
+ {tags?.length && ( + + {tags.map((tag) => ( + + ))} + + )} + + + + {name} + + + + + + {description && {description}} +
+ ); +}; diff --git a/portals-ui/sites/ngs-portal/src/pages/home/components/pipelines-list.tsx b/portals-ui/sites/ngs-portal/src/pages/home/components/pipelines-list.tsx new file mode 100644 index 0000000000..17f9d52b81 --- /dev/null +++ b/portals-ui/sites/ngs-portal/src/pages/home/components/pipelines-list.tsx @@ -0,0 +1,35 @@ +import type { Pipeline } from '@cloud-pipeline/core'; +import { ItemsPanel } from '../../../widgets/items-panel/items-panel'; +import { PipelineCard } from './pipeline-card'; +import { memo } from 'react'; +import cn from 'classnames'; + +type Props = { + pipelines: Pipeline[]; +}; + +export const PipelinesList = memo(({ pipelines }: Props) => { + const renderItem = (item: Pipeline, search: string, i: number) => { + return ( + + ); + }; + + return ( + + ); +}); diff --git a/portals-ui/sites/ngs-portal/src/pages/home/components/project-card.tsx b/portals-ui/sites/ngs-portal/src/pages/home/components/project-card.tsx new file mode 100644 index 0000000000..bcfdc48029 --- /dev/null +++ b/portals-ui/sites/ngs-portal/src/pages/home/components/project-card.tsx @@ -0,0 +1,84 @@ +import { Tag, Badge, FlexRow, RichTextView } from '@epam/uui'; +import ContentPersonFillIcon from '@epam/assets/icons/content-person-fill.svg?react'; +import cn from 'classnames'; +import { Link } from 'react-router-dom'; +import type { Project } from '@cloud-pipeline/core'; +import { + executeAllowed, + readAllowed, + writeAllowed, +} from '@cloud-pipeline/core'; +import type { CommonProps } from '@cloud-pipeline/components'; +import HighlightedText from '../../../shared/highlight-text'; + +type Props = CommonProps & { + project: Project; + description?: string; + tags?: string[]; + highlightedText?: string; +}; + +export const ProjectCard = ({ + project, + description, + tags, + highlightedText, + className, + style, +}: Props) => { + const { id, name, owner, mask } = project; + + const read = readAllowed(mask); + const write = writeAllowed(mask); + const execute = executeAllowed(mask); + + const hasSomeRights = read || write || execute; + + return ( +
+ {tags?.length && ( + + {tags.map((tag) => ( + + ))} + + )} + + + + {name} + + + + + {hasSomeRights && ( + + {read && ( + + )} + {write && ( + + )} + {execute && ( + + )} + + )} + + {description && {description}} +
+ ); +}; diff --git a/portals-ui/sites/ngs-portal/src/pages/home/components/projects-list.tsx b/portals-ui/sites/ngs-portal/src/pages/home/components/projects-list.tsx new file mode 100644 index 0000000000..7f0c78bdd4 --- /dev/null +++ b/portals-ui/sites/ngs-portal/src/pages/home/components/projects-list.tsx @@ -0,0 +1,37 @@ +import { type Project } from '@cloud-pipeline/core'; +import { Button } from '@epam/uui'; +import { ProjectCard } from './project-card'; +import { ItemsPanel } from '../../../widgets/items-panel/items-panel'; +import cn from 'classnames'; +import { memo } from 'react'; + +type Props = { + projects: Project[]; +}; + +export const ProjectsList = memo(({ projects }: Props) => { + const renderItem = (item: Project, search: string, i: number) => ( + + ); + + return ( + null} /> + } + items={projects} + renderItem={renderItem} + sliced + search + itemKey="id" + viewAll={{ title: 'View all projects', link: '/projects' }} + /> + ); +}); diff --git a/portals-ui/sites/ngs-portal/src/pages/home/home.tsx b/portals-ui/sites/ngs-portal/src/pages/home/home.tsx index 335d6b5ec7..8a8dd33561 100644 --- a/portals-ui/sites/ngs-portal/src/pages/home/home.tsx +++ b/portals-ui/sites/ngs-portal/src/pages/home/home.tsx @@ -1,64 +1,43 @@ import { useEffect } from 'react'; import type { Run } from '@cloud-pipeline/core'; -import { Button } from '@epam/uui'; import { useProjectsState } from '../../state/projects/hooks'; import { loadProjects } from '../../state/projects/load-projects'; -import HighlightedText from '../../shared/highlight-text'; import { ItemsPanel } from '../../widgets/items-panel/items-panel.tsx'; import { usePipelinesState } from '../../state/pipelines/hooks.ts'; import { loadPipelines } from '../../state/pipelines/load-pipelines.ts'; import './style.css'; +import { ProjectsList, PipelinesList } from './components'; export const Home = () => { const { projects } = useProjectsState(); const { pipelines } = usePipelinesState(); + useEffect(() => { loadProjects() .then(() => {}) .catch(() => {}); }, []); + useEffect(() => { loadPipelines() .then(() => {}) .catch(() => {}); }, []); + return (
-
- null} /> - } - items={projects} - renderItem={(item, search) => ( -
- {item.name} -
- )} - sliced - search - itemKey="id" - viewAll={{ title: 'View all projects', link: '/projects' }} - /> -
-
- ( -
- {item.name} -
- )} - sliced - search - itemKey="id" - viewAll={{ title: 'View all pipelines', link: '/pipelines' }} - /> -
+ {projects && ( +
+ +
+ )} + + {pipelines && ( +
+ +
+ )} +
{ - loadProjects() - .then(() => {}) - .catch(() => {}); - }, []); - const { projects, error, pending } = useProjectsState(); - const { search, onSearchChange, filtered } = useSearch({ - items: projects ?? [], - }); - if (error) { - return
{error}
; - } - if (pending) { - return ; - } - if (!projects) { - return
No data
; - } - return ( -
- - ( - {project.name} - )} - itemKey="id" - sliced={20} - /> -
- ); -} +export { ProjectsPage } from './projects'; diff --git a/portals-ui/sites/ngs-portal/src/pages/projects/projects.tsx b/portals-ui/sites/ngs-portal/src/pages/projects/projects.tsx new file mode 100644 index 0000000000..986c4900ff --- /dev/null +++ b/portals-ui/sites/ngs-portal/src/pages/projects/projects.tsx @@ -0,0 +1,47 @@ +import { useEffect } from 'react'; +import { Spinner } from '@epam/uui'; +import { loadProjects } from '../../state/projects/load-projects'; +import { useProjectsState } from '../../state/projects/hooks'; +import { List, ListHeader } from '@cloud-pipeline/components'; +import { useSearch } from '../../shared/hooks/use-search.ts'; +import HighlightedText from '../../shared/highlight-text'; + +export function ProjectsPage() { + useEffect(() => { + loadProjects() + .then(() => {}) + .catch(() => {}); + }, []); + const { projects, error, pending } = useProjectsState(); + const { search, onSearchChange, filtered } = useSearch({ + items: projects ?? [], + }); + if (error) { + return
{error}
; + } + if (pending) { + return ; + } + if (!projects) { + return
No data
; + } + return ( +
+ + ( + {project.name} + )} + itemKey="id" + sliced={20} + /> +
+ ); +} diff --git a/portals-ui/sites/ngs-portal/src/state/projects/load-projects.ts b/portals-ui/sites/ngs-portal/src/state/projects/load-projects.ts index 32027c33b7..ce51f8956e 100644 --- a/portals-ui/sites/ngs-portal/src/state/projects/load-projects.ts +++ b/portals-ui/sites/ngs-portal/src/state/projects/load-projects.ts @@ -10,7 +10,7 @@ export async function loadProjects(): Promise { projects = await fetchProjects(); return projects; } catch (authError) { - error = authError instanceof Error ? authError.message : `${authError}`; + error = authError instanceof Error ? authError.message : String(authError); throw new Error(error); } finally { projectsStore.getState().setProjects({ projects, error }); diff --git a/portals-ui/sites/ngs-portal/src/types.d.ts b/portals-ui/sites/ngs-portal/src/types.d.ts index 509a81d797..d9e77fa3a7 100644 --- a/portals-ui/sites/ngs-portal/src/types.d.ts +++ b/portals-ui/sites/ngs-portal/src/types.d.ts @@ -1 +1,16 @@ declare const CLOUD_PIPELINE_API: string; + +declare module '*.svg' { + import type * as React from 'react'; + export const ReactComponent: React.FunctionComponent< + React.SVGProps + >; + const src: string; + export default src; +} + +declare module '*.svg?react' { + import type * as React from 'react'; + const ReactComponent: React.FunctionComponent>; + export default ReactComponent; +} diff --git a/portals-ui/sites/ngs-portal/src/widgets/items-panel/items-panel.tsx b/portals-ui/sites/ngs-portal/src/widgets/items-panel/items-panel.tsx index e03af80548..bbe3658b70 100644 --- a/portals-ui/sites/ngs-portal/src/widgets/items-panel/items-panel.tsx +++ b/portals-ui/sites/ngs-portal/src/widgets/items-panel/items-panel.tsx @@ -57,6 +57,7 @@ export function ItemsPanel(props: ItemsPanelProps) { } as SearchOptions; }, [items, searchConfig]); const { filtered, search, onSearchChange } = useSearch(searchOptions); + return (
(props: ItemsPanelProps) { renderItem(item, search)} + renderItem={(item, i) => renderItem(item, search, i)} itemKey={itemKey} virtualized={virtualized} sliced={sliced} diff --git a/portals-ui/sites/ngs-portal/src/widgets/items-panel/types.ts b/portals-ui/sites/ngs-portal/src/widgets/items-panel/types.ts index f7b12becf5..a9be81d495 100644 --- a/portals-ui/sites/ngs-portal/src/widgets/items-panel/types.ts +++ b/portals-ui/sites/ngs-portal/src/widgets/items-panel/types.ts @@ -14,7 +14,7 @@ export type ItemsPanelProps = Omit< ListProps, 'renderItem' | 'data' | 'header' > & { - renderItem: (item: Item, search: string) => ReactNode | string; + renderItem: (item: Item, search: string, index: number) => ReactNode | string; items?: Item[]; title?: ReactNode; actions?: ReactNode; diff --git a/portals-ui/sites/ngs-portal/vite.config.ts b/portals-ui/sites/ngs-portal/vite.config.ts index f1d162b731..dcf996fa27 100644 --- a/portals-ui/sites/ngs-portal/vite.config.ts +++ b/portals-ui/sites/ngs-portal/vite.config.ts @@ -1,6 +1,7 @@ import type { ConfigEnv } from 'vite'; import { defineConfig, loadEnv } from 'vite'; import react from '@vitejs/plugin-react'; +import svgr from 'vite-plugin-svgr'; // https://vitejs.dev/config/ export default (cfg: ConfigEnv) => { @@ -10,7 +11,7 @@ export default (cfg: ConfigEnv) => { const cloudPipelineApi = env.CLOUD_PIPELINE_API ?? '/restapi'; return defineConfig({ base, - plugins: [react()], + plugins: [react(), svgr()], define: { CLOUD_PIPELINE_API: JSON.stringify(cloudPipelineApi), },