This is an automated email from the ASF dual-hosted git repository.
lahirujayathilake pushed a commit to branch cybershuttle-staging
in repository https://gitbox.apache.org/repos/asf/airavata.git
The following commit(s) were added to refs/heads/cybershuttle-staging by this
push:
new 214908200f add research portal
214908200f is described below
commit 214908200fb37a51061373fddb0d208adb5ed288
Author: ganning127 <[email protected]>
AuthorDate: Sun Mar 30 11:33:30 2025 -0400
add research portal
---
modules/research-framework/portal/.gitignore | 24 +
modules/research-framework/portal/README.md | 54 ++
modules/research-framework/portal/index.html | 13 +
modules/research-framework/portal/public/vite.svg | 1 +
modules/research-framework/portal/src/App.tsx | 41 ++
.../portal/src/assets/airavata-logo.png | Bin 0 -> 14892 bytes
.../research-framework/portal/src/assets/react.svg | 1 +
.../portal/src/components/GridContainer.tsx | 17 +
.../portal/src/components/Metadata.tsx | 66 +++
.../portal/src/components/NavBar.tsx | 58 +++
.../portal/src/components/PageHeader.tsx | 28 ++
.../portal/src/components/datasets/DatasetCard.tsx | 70 +++
.../src/components/datasets/DatasetDetails.tsx | 54 ++
.../portal/src/components/datasets/index.tsx | 42 ++
.../src/components/home/AddRepositoryButton.tsx | 124 +++++
.../portal/src/components/home/AddZipButton.tsx | 113 +++++
.../portal/src/components/home/ButtonWithIcon.tsx | 22 +
.../portal/src/components/home/ProjectCard.tsx | 84 ++++
.../portal/src/components/home/ProjectsSection.tsx | 28 ++
.../portal/src/components/home/SessionCard.tsx | 71 +++
.../portal/src/components/home/SessionsSection.tsx | 17 +
.../portal/src/components/home/index.tsx | 61 +++
.../portal/src/components/models/ModelCard.tsx | 34 ++
.../portal/src/components/models/ModelDetails.tsx | 96 ++++
.../portal/src/components/models/index.tsx | 40 ++
.../src/components/notebooks/NotebookCard.tsx | 64 +++
.../src/components/notebooks/ProjectDetails.tsx | 98 ++++
.../portal/src/components/notebooks/index.tsx | 55 ++
.../src/components/repositories/RepositoryCard.tsx | 39 ++
.../portal/src/components/repositories/index.tsx | 39 ++
.../portal/src/components/ui/color-mode.tsx | 107 ++++
.../portal/src/components/ui/input-group.tsx | 53 ++
.../portal/src/components/ui/provider.tsx | 12 +
.../portal/src/data/MOCK_DATA.js | 555 +++++++++++++++++++++
.../portal/src/interfaces/AuthorType.tsx | 5 +
.../portal/src/interfaces/DatasetType.tsx | 7 +
.../portal/src/interfaces/MetadataType.tsx | 14 +
.../portal/src/interfaces/ModelType.tsx | 10 +
.../portal/src/interfaces/ProjectType.tsx | 7 +
.../portal/src/interfaces/SessionType.tsx | 11 +
modules/research-framework/portal/src/main.tsx | 12 +
.../research-framework/portal/src/vite-env.d.ts | 1 +
42 files changed, 2248 insertions(+)
diff --git a/modules/research-framework/portal/.gitignore
b/modules/research-framework/portal/.gitignore
new file mode 100644
index 0000000000..a547bf36d8
--- /dev/null
+++ b/modules/research-framework/portal/.gitignore
@@ -0,0 +1,24 @@
+# Logs
+logs
+*.log
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+pnpm-debug.log*
+lerna-debug.log*
+
+node_modules
+dist
+dist-ssr
+*.local
+
+# Editor directories and files
+.vscode/*
+!.vscode/extensions.json
+.idea
+.DS_Store
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/modules/research-framework/portal/README.md
b/modules/research-framework/portal/README.md
new file mode 100644
index 0000000000..40ede56ea6
--- /dev/null
+++ b/modules/research-framework/portal/README.md
@@ -0,0 +1,54 @@
+# React + TypeScript + Vite
+
+This template provides a minimal setup to get React working in Vite with HMR
and some ESLint rules.
+
+Currently, two official plugins are available:
+
+-
[@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react/README.md)
uses [Babel](https://babeljs.io/) for Fast Refresh
+- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react-swc)
uses [SWC](https://swc.rs/) for Fast Refresh
+
+## Expanding the ESLint configuration
+
+If you are developing a production application, we recommend updating the
configuration to enable type-aware lint rules:
+
+```js
+export default tseslint.config({
+ extends: [
+ // Remove ...tseslint.configs.recommended and replace with this
+ ...tseslint.configs.recommendedTypeChecked,
+ // Alternatively, use this for stricter rules
+ ...tseslint.configs.strictTypeChecked,
+ // Optionally, add this for stylistic rules
+ ...tseslint.configs.stylisticTypeChecked,
+ ],
+ languageOptions: {
+ // other options...
+ parserOptions: {
+ project: ['./tsconfig.node.json', './tsconfig.app.json'],
+ tsconfigRootDir: import.meta.dirname,
+ },
+ },
+})
+```
+
+You can also install
[eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x)
and
[eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom)
for React-specific lint rules:
+
+```js
+// eslint.config.js
+import reactX from 'eslint-plugin-react-x'
+import reactDom from 'eslint-plugin-react-dom'
+
+export default tseslint.config({
+ plugins: {
+ // Add the react-x and react-dom plugins
+ 'react-x': reactX,
+ 'react-dom': reactDom,
+ },
+ rules: {
+ // other rules...
+ // Enable its recommended typescript rules
+ ...reactX.configs['recommended-typescript'].rules,
+ ...reactDom.configs.recommended.rules,
+ },
+})
+```
diff --git a/modules/research-framework/portal/index.html
b/modules/research-framework/portal/index.html
new file mode 100644
index 0000000000..e4b78eae12
--- /dev/null
+++ b/modules/research-framework/portal/index.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<html lang="en">
+ <head>
+ <meta charset="UTF-8" />
+ <link rel="icon" type="image/svg+xml" href="/vite.svg" />
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+ <title>Vite + React + TS</title>
+ </head>
+ <body>
+ <div id="root"></div>
+ <script type="module" src="/src/main.tsx"></script>
+ </body>
+</html>
diff --git a/modules/research-framework/portal/public/vite.svg
b/modules/research-framework/portal/public/vite.svg
new file mode 100644
index 0000000000..e7b8dfb1b2
--- /dev/null
+++ b/modules/research-framework/portal/public/vite.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
class="iconify iconify--logos" width="31.88" height="32"
preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 257"><defs><linearGradient
id="IconifyId1813088fe1fbc01fb466" x1="-.828%" x2="57.636%" y1="7.652%"
y2="78.411%"><stop offset="0%" stop-color="#41D1FF"></stop><stop offset="100%"
stop-color="#BD34FE"></stop></linearGradient><linearGradient
id="IconifyId1813088fe1fbc01fb [...]
\ No newline at end of file
diff --git a/modules/research-framework/portal/src/App.tsx
b/modules/research-framework/portal/src/App.tsx
new file mode 100644
index 0000000000..a03737144d
--- /dev/null
+++ b/modules/research-framework/portal/src/App.tsx
@@ -0,0 +1,41 @@
+import { useColorMode } from "./components/ui/color-mode";
+import { BrowserRouter, Route, Routes } from "react-router";
+import Home from "./components/home";
+import ProjectDetails from "./components/notebooks/ProjectDetails";
+import { Models } from "./components/models";
+import { ModelDetails } from "./components/models/ModelDetails";
+import { Datasets } from "./components/datasets";
+import { DatasetDetails } from "./components/datasets/DatasetDetails";
+
+function App() {
+ const colorMode = useColorMode();
+ if (colorMode.colorMode === "dark") {
+ colorMode.toggleColorMode();
+ }
+
+ return (
+ <>
+ <BrowserRouter>
+ <Routes>
+ <Route index element={<Home />} />
+ <Route path="/notebook">
+ <Route path=":slug" element={<ProjectDetails />} />
+ </Route>
+ <Route path="/repository">
+ <Route path=":slug" element={<ProjectDetails />} />
+ </Route>
+ <Route path="/models">
+ <Route index element={<Models />} />
+ <Route path=":id" element={<ModelDetails />} />
+ </Route>
+ <Route path="/datasets">
+ <Route index element={<Datasets />} />
+ <Route path=":slug" element={<DatasetDetails />} />
+ </Route>
+ </Routes>
+ </BrowserRouter>
+ </>
+ );
+}
+
+export default App;
diff --git a/modules/research-framework/portal/src/assets/airavata-logo.png
b/modules/research-framework/portal/src/assets/airavata-logo.png
new file mode 100644
index 0000000000..06aea97295
Binary files /dev/null and
b/modules/research-framework/portal/src/assets/airavata-logo.png differ
diff --git a/modules/research-framework/portal/src/assets/react.svg
b/modules/research-framework/portal/src/assets/react.svg
new file mode 100644
index 0000000000..6c87de9bb3
--- /dev/null
+++ b/modules/research-framework/portal/src/assets/react.svg
@@ -0,0 +1 @@
+<svg xmlns="http://www.w3.org/2000/svg"
xmlns:xlink="http://www.w3.org/1999/xlink" aria-hidden="true" role="img"
class="iconify iconify--logos" width="35.93" height="32"
preserveAspectRatio="xMidYMid meet" viewBox="0 0 256 228"><path fill="#00D8FF"
d="M210.483 73.824a171.49 171.49 0 0 0-8.24-2.597c.465-1.9.893-3.777
1.273-5.621c6.238-30.281
2.16-54.676-11.769-62.708c-13.355-7.7-35.196.329-57.254 19.526a171.23 171.23 0
0 0-6.375 5.848a155.866 155.866 0 0 0-4.241-3.917C100.759 3.829 77.587 [...]
\ No newline at end of file
diff --git a/modules/research-framework/portal/src/components/GridContainer.tsx
b/modules/research-framework/portal/src/components/GridContainer.tsx
new file mode 100644
index 0000000000..834a6d3b87
--- /dev/null
+++ b/modules/research-framework/portal/src/components/GridContainer.tsx
@@ -0,0 +1,17 @@
+import { SimpleGrid } from "@chakra-ui/react";
+
+export const GridContainer = ({ children }: { children: React.ReactNode }) => {
+ return (
+ <SimpleGrid
+ columns={{ base: 1, md: 2, lg: 3 }}
+ gap={4}
+ mt={2}
+ maxH="50vh"
+ overflow="scroll"
+ p={4}
+ bg="gray.50"
+ >
+ {children}
+ </SimpleGrid>
+ );
+};
diff --git a/modules/research-framework/portal/src/components/Metadata.tsx
b/modules/research-framework/portal/src/components/Metadata.tsx
new file mode 100644
index 0000000000..92b12dd7b8
--- /dev/null
+++ b/modules/research-framework/portal/src/components/Metadata.tsx
@@ -0,0 +1,66 @@
+import { MetadataType } from "@/interfaces/MetadataType";
+import {
+ Image,
+ HStack,
+ Box,
+ Text,
+ Heading,
+ Avatar,
+ Separator,
+ Badge,
+} from "@chakra-ui/react";
+
+export const Metadata = ({ metadata }: { metadata: MetadataType }) => {
+ return (
+ <>
+ <HStack
+ alignItems={"flex-start"}
+ mb={4}
+ gap={8}
+ justifyContent="space-between"
+ >
+ <Box>
+ <Heading as="h1" size="4xl" mb={4}>
+ {metadata.title}
+ </Heading>
+
+ <HStack>
+ <Avatar.Root shape="full" size="xl">
+ <Avatar.Fallback name={metadata.author.name} />
+ <Avatar.Image src={metadata.author.avatar} />
+ </Avatar.Root>
+
+ <Box>
+ <Text fontWeight="bold">{metadata.author.name}</Text>
+ <Text color="gray.500">{metadata.author.role}</Text>
+ </Box>
+ </HStack>
+ </Box>
+
+ <Image
+ src={metadata.images.headerImage}
+ alt="Notebook Header"
+ rounded="md"
+ maxW="300px"
+ />
+ </HStack>
+
+ <Separator my={6} />
+ <Box>
+ <Heading fontWeight="bold" size="2xl">
+ About
+ </Heading>
+
+ <HStack my={2}>
+ {metadata.tags.map((tag) => (
+ <Badge key={tag} size="lg" rounded="md">
+ {tag}
+ </Badge>
+ ))}
+ </HStack>
+
+ <Text>{metadata.description}</Text>
+ </Box>
+ </>
+ );
+};
diff --git a/modules/research-framework/portal/src/components/NavBar.tsx
b/modules/research-framework/portal/src/components/NavBar.tsx
new file mode 100644
index 0000000000..f62d1ac85a
--- /dev/null
+++ b/modules/research-framework/portal/src/components/NavBar.tsx
@@ -0,0 +1,58 @@
+import { Text, Flex, Spacer, Image, HStack, Avatar } from "@chakra-ui/react";
+import ApacheAiravataLogo from "../assets/airavata-logo.png";
+import { Link } from "react-router";
+
+const NAV_CONTENT = [
+ {
+ title: "Home",
+ url: "/",
+ },
+ {
+ title: "Datasets",
+ url: "/datasets",
+ },
+ {
+ title: "Models",
+ url: "/models",
+ },
+];
+
+const NavBar = () => {
+ return (
+ <Flex
+ as="nav"
+ align="center"
+ p={4}
+ boxShadow="sm"
+ position="sticky"
+ top="0"
+ zIndex="1000"
+ bg="white" // Ensure background is not transparent
+ >
+ {/* Logo */}
+ <Link to="/">
+ <Image src={ApacheAiravataLogo} alt="Logo" boxSize="30px" />
+ </Link>
+
+ {/* Navigation Links */}
+ <Flex ml={4} gap={6}>
+ {NAV_CONTENT.map((item) => (
+ <Link key={item.title} to={item.url} color="gray.700">
+ {item.title}
+ </Link>
+ ))}
+ </Flex>
+
+ <Spacer />
+
+ {/* Search Bar */}
+ <HStack _hover={{ cursor: "pointer" }}>
+ <Text>John Doe</Text>
+ <Avatar.Root variant="subtle">
+ <Avatar.Fallback name="John Doe" />
+ </Avatar.Root>
+ </HStack>
+ </Flex>
+ );
+};
+export default NavBar;
diff --git a/modules/research-framework/portal/src/components/PageHeader.tsx
b/modules/research-framework/portal/src/components/PageHeader.tsx
new file mode 100644
index 0000000000..f4e1e8ee85
--- /dev/null
+++ b/modules/research-framework/portal/src/components/PageHeader.tsx
@@ -0,0 +1,28 @@
+import { Box, HStack, Text, Heading, Icon } from "@chakra-ui/react";
+import { ReactNode } from "react";
+
+export const PageHeader = ({
+ title,
+ icon,
+ description,
+}: {
+ title: string;
+ icon?: ReactNode;
+ description: string;
+}) => {
+ const fontSize = "4xl";
+ return (
+ <Box>
+ <HStack alignItems="center" gap={4}>
+ {icon && <Icon as={icon} fontSize={fontSize} />}
+ <Heading as="h1" size={fontSize}>
+ {title}
+ </Heading>
+ </HStack>
+
+ <Text color="gray.600" mt={2}>
+ {description}
+ </Text>
+ </Box>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/datasets/DatasetCard.tsx
b/modules/research-framework/portal/src/components/datasets/DatasetCard.tsx
new file mode 100644
index 0000000000..95d3a84ddf
--- /dev/null
+++ b/modules/research-framework/portal/src/components/datasets/DatasetCard.tsx
@@ -0,0 +1,70 @@
+import { DatasetType } from "@/interfaces/DatasetType";
+import {
+ Text,
+ Box,
+ Card,
+ HStack,
+ Avatar,
+ Image,
+ Badge,
+} from "@chakra-ui/react";
+import { Link } from "react-router";
+
+export const DatasetCard = ({ dataset }: { dataset: DatasetType }) => {
+ return (
+ <Box>
+ <Card.Root overflow="hidden">
+ <Image
+ src={dataset.metadata.images.headerImage}
+ maxH={100}
+ alt="Green double couch with wooden legs"
+ />
+ <Link to={`/datasets/${dataset.metadata.slug}`}>
+ <Card.Body
+ gap="2"
+ _hover={{
+ bg: "gray.100",
+ }}
+ >
+ <HStack justifyContent="space-between" mb={1}>
+ <Card.Title>{dataset.metadata.title}</Card.Title>
+ <Badge
+ colorPalette={dataset.private ? "red" : "green"}
+ rounded="md"
+ >
+ {dataset.private ? "Private" : "Public"}
+ </Badge>
+ </HStack>
+ <HStack flexWrap={"wrap"}>
+ <Badge colorPalette={"purple"} fontWeight="bold" size="md">
+ {dataset.metadata.type}
+ </Badge>
+ {dataset.metadata.tags.map((tag: string) => (
+ <Badge size="md" key={tag}>
+ {tag}
+ </Badge>
+ ))}
+ </HStack>
+ <Text color="fg.muted" lineClamp={2}>
+ {dataset.metadata.description}.
+ </Text>
+ </Card.Body>
+ </Link>
+
+ <Card.Footer justifyContent="space-between">
+ <HStack mt={4}>
+ <Avatar.Root shape="full" size="xl">
+ <Avatar.Fallback name={dataset.metadata.author.name} />
+ <Avatar.Image src={dataset.metadata.author.avatar} />
+ </Avatar.Root>
+
+ <Box>
+ <Text fontWeight="bold">{dataset.metadata.author.name}</Text>
+ <Text color="gray.500">{dataset.metadata.author.role}</Text>
+ </Box>
+ </HStack>
+ </Card.Footer>
+ </Card.Root>
+ </Box>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx
b/modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx
new file mode 100644
index 0000000000..79719752aa
--- /dev/null
+++
b/modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx
@@ -0,0 +1,54 @@
+import { Metadata } from "../Metadata";
+import NavBar from "../NavBar";
+// @ts-expect-error This is fine
+import { MOCK_DATASETS } from "../../data/MOCK_DATA";
+import { DatasetType } from "@/interfaces/DatasetType";
+import { useEffect, useState } from "react";
+import { Badge, Box, Container, HStack, Icon, Spinner } from
"@chakra-ui/react";
+import { Link, useParams } from "react-router";
+import { BiArrowBack } from "react-icons/bi";
+
+async function getDataset(slug: string | undefined) {
+ return MOCK_DATASETS.find(
+ (dataset: DatasetType) => dataset.metadata.slug === slug
+ );
+}
+
+export const DatasetDetails = () => {
+ const [dataset, setDataset] = useState<DatasetType | null>(null);
+ const { slug } = useParams();
+
+ useEffect(() => {
+ async function getData() {
+ const n = await getDataset(slug);
+ setDataset(n);
+ }
+ getData();
+ }, []);
+
+ if (!dataset) return <Spinner />;
+ return (
+ <>
+ <NavBar />
+
+ <Container maxW="breakpoint-lg" mx="auto" p={4} mt={16}>
+ <Box>
+ <HStack alignItems="center" mb={4}>
+ <Icon>
+ <BiArrowBack />
+ </Icon>
+ <Link to="/datasets">Back to Datasets</Link>
+ </HStack>
+ </Box>
+ <Badge
+ colorPalette={dataset.private ? "red" : "green"}
+ rounded="md"
+ mb={1}
+ >
+ {dataset.private ? "Private" : "Public"}
+ </Badge>
+ <Metadata metadata={dataset.metadata} />
+ </Container>
+ </>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/datasets/index.tsx
b/modules/research-framework/portal/src/components/datasets/index.tsx
new file mode 100644
index 0000000000..82d79d9cb5
--- /dev/null
+++ b/modules/research-framework/portal/src/components/datasets/index.tsx
@@ -0,0 +1,42 @@
+import NavBar from "../NavBar";
+// @ts-expect-error This is fine
+import { MOCK_DATASETS } from "../../data/MOCK_DATA";
+import { Container, HStack, Input, SimpleGrid } from "@chakra-ui/react";
+import { PageHeader } from "../PageHeader";
+import { LuSearch } from "react-icons/lu";
+import { InputGroup } from "../ui/input-group";
+import { ButtonWithIcon } from "../home/ButtonWithIcon";
+import { FaPlus } from "react-icons/fa";
+import { DatasetCard } from "./DatasetCard";
+import { DatasetType } from "@/interfaces/DatasetType";
+
+export const Datasets = () => {
+ return (
+ <>
+ <NavBar />
+
+ <Container maxW="container.lg" p={4}>
+ <HStack alignItems="flex-end" justify="space-between">
+ <PageHeader
+ title="Datasets"
+ description="Public and Private Scientific Datasets."
+ />
+ <ButtonWithIcon colorPalette="purple" icon={FaPlus}>
+ Dataset
+ </ButtonWithIcon>
+ </HStack>
+ <InputGroup mt={4} endElement={<LuSearch />} w="100%">
+ <Input placeholder="Search" rounded="md" />
+ </InputGroup>
+
+ <SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} gap={4} mt={4}>
+ {MOCK_DATASETS.map((dataset: DatasetType) => {
+ return (
+ <DatasetCard dataset={dataset} key={dataset.metadata.slug} />
+ );
+ })}
+ </SimpleGrid>
+ </Container>
+ </>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/home/AddRepositoryButton.tsx
b/modules/research-framework/portal/src/components/home/AddRepositoryButton.tsx
new file mode 100644
index 0000000000..aad756a3e2
--- /dev/null
+++
b/modules/research-framework/portal/src/components/home/AddRepositoryButton.tsx
@@ -0,0 +1,124 @@
+import {
+ Dialog,
+ Button,
+ Field,
+ Input,
+ CloseButton,
+ Portal,
+ useDialog,
+ Code,
+ VStack,
+ SimpleGrid,
+ Textarea,
+} from "@chakra-ui/react";
+import { FaPlus } from "react-icons/fa";
+import { ButtonWithIcon } from "./ButtonWithIcon";
+
+export const AddRepositoryButton = () => {
+ const dialog = useDialog();
+ // useEffect(() => {
+ // dialog.setOpen(true);
+ // }, []);
+ return (
+ <>
+ <Dialog.RootProvider value={dialog} size="lg">
+ <Dialog.Trigger asChild>
+ <ButtonWithIcon
+ bg="cyan.600"
+ _hover={{ bg: "cyan.700" }}
+ icon={FaPlus}
+ >
+ Repository
+ </ButtonWithIcon>
+ </Dialog.Trigger>
+ <Portal>
+ <Dialog.Backdrop />
+ <Dialog.Positioner>
+ <Dialog.Content>
+ <Dialog.Body p="4">
+ <Dialog.Title>Add Git Repository</Dialog.Title>
+ <Dialog.Description mt="2">
+ Start by providing a GitHub URL. If the repository contains a
+ <Code colorPalette="blue">cybershuttle.yml</Code> file, the
+ author/tag/workspace information will be pulled from it. If
+ not, please provide them manually.
+ </Dialog.Description>
+
+ <VStack gap={4} mt="4">
+ <Field.Root>
+ <Field.Label>URL</Field.Label>
+ <Input
+ type="url"
+ flexGrow={1}
+ placeholder="https://github.com/example/repo.git"
+ />
+ </Field.Root>
+ <Field.Root>
+ <Field.Label>Authors</Field.Label>
+ <Input
+ flexGrow={1}
+ placeholder="John Doe <[email protected]>; Jane Doe
<[email protected]>"
+ />
+ </Field.Root>
+
+ <Field.Root>
+ <Field.Label>Tags</Field.Label>
+ <Input
+ flexGrow={1}
+ placeholder="bmtk-workshop; neuroscience; notebooks"
+ />
+ </Field.Root>
+ <SimpleGrid columns={5} gap={4}>
+ <Field.Root>
+ <Field.Label>Dir</Field.Label>
+ <Input flexGrow={1} placeholder="/workspace" />
+ </Field.Root>
+ <Field.Root>
+ <Field.Label>Nodes</Field.Label>
+ <Input flexGrow={1} placeholder="1" />
+ </Field.Root>
+
+ <Field.Root>
+ <Field.Label>Min CPU</Field.Label>
+ <Input flexGrow={1} placeholder="1" />
+ </Field.Root>
+ <Field.Root>
+ <Field.Label>Min GPU</Field.Label>
+ <Input flexGrow={1} placeholder="1" />
+ </Field.Root>
+ <Field.Root>
+ <Field.Label>Min RAM</Field.Label>
+ <Input flexGrow={1} placeholder="1" />
+ </Field.Root>
+ </SimpleGrid>
+
+ <Field.Root>
+ <Field.Label>Data</Field.Label>
+ <Textarea
+ flexGrow={1}
+ rows={4}
+ placeholder="cyber://321aed:/workspace/data/A
+allen://8a8b2f:/workspace/data/B
+dropbox://4a34d1:/workspace/data/C"
+ />
+ </Field.Root>
+ <Button
+ bg="cyan.600"
+ _hover={{ bg: "cyan.700" }}
+ width="100%"
+ type="submit"
+ >
+ Add Git Repository
+ </Button>
+ </VStack>
+ </Dialog.Body>
+ <Dialog.CloseTrigger asChild>
+ <CloseButton _hover={{ bg: "gray.100" }} size="sm" />
+ </Dialog.CloseTrigger>
+ </Dialog.Content>
+ </Dialog.Positioner>
+ </Portal>
+ </Dialog.RootProvider>
+ </>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/home/AddZipButton.tsx
b/modules/research-framework/portal/src/components/home/AddZipButton.tsx
new file mode 100644
index 0000000000..aae98694c0
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/AddZipButton.tsx
@@ -0,0 +1,113 @@
+import {
+ Dialog,
+ Button,
+ CloseButton,
+ Portal,
+ useDialog,
+ Code,
+ Box,
+ Icon,
+ FileUpload,
+ Spacer,
+ useFileUpload,
+} from "@chakra-ui/react";
+import { FaPlus } from "react-icons/fa";
+import { ButtonWithIcon } from "./ButtonWithIcon";
+import { LuUpload } from "react-icons/lu";
+
+export const AddZipButton = () => {
+ const dialog = useDialog();
+ // useEffect(() => {
+ // dialog.setOpen(true);
+ // }, []);
+ const fileUpload = useFileUpload({
+ maxFiles: 1,
+ accept: ".zip",
+ });
+
+ return (
+ <>
+ <Dialog.RootProvider
+ value={dialog}
+ size="lg"
+ onExitComplete={() => {
+ fileUpload.clearFiles(); // Clear files when modal closes
+ }}
+ >
+ <Dialog.Trigger asChild>
+ <ButtonWithIcon
+ bg="pink.500"
+ _hover={{ bg: "pink.600" }}
+ icon={FaPlus}
+ >
+ Zip
+ </ButtonWithIcon>
+ </Dialog.Trigger>
+ <Portal>
+ <Dialog.Backdrop />
+ <Dialog.Positioner>
+ <Dialog.Content>
+ <Dialog.Body p="4">
+ <Dialog.Title>Add Zip File</Dialog.Title>
+ <Dialog.Description mt="2">
+ Upload a ZIP file containing your repository. If the
+ repository contains a{" "}
+ <Code colorPalette="blue">cybershuttle.yml</Code> file, the
+ author/tag/workspace information will be pulled from it. If
+ not, please provide them manually.
+ </Dialog.Description>
+ <FileUpload.RootProvider
+ alignItems="stretch"
+ mt={4}
+ value={fileUpload}
+ >
+ <FileUpload.HiddenInput />
+ {fileUpload.acceptedFiles.length === 0 && (
+ <FileUpload.Dropzone>
+ <Icon size="md" color="fg.muted">
+ <LuUpload />
+ </Icon>
+ <FileUpload.DropzoneContent>
+ <Box>Drag and drop your .zip file here</Box>
+ </FileUpload.DropzoneContent>
+ </FileUpload.Dropzone>
+ )}
+
+ <FileUpload.ItemGroup>
+ <FileUpload.Context>
+ {({ acceptedFiles }) =>
+ acceptedFiles.map((file) => (
+ <FileUpload.Item key={file.name} file={file}>
+ <FileUpload.ItemPreview />
+ <FileUpload.ItemName />
+ <FileUpload.ItemSizeText />
+ <Spacer />
+ <FileUpload.ItemDeleteTrigger />
+ </FileUpload.Item>
+ ))
+ }
+ </FileUpload.Context>
+ </FileUpload.ItemGroup>
+ </FileUpload.RootProvider>
+
+ <Button
+ bg="pink.500"
+ _hover={{ bg: "pink.600" }}
+ width="100%"
+ type="submit"
+ mt={4}
+ disabled={fileUpload.acceptedFiles.length === 0}
+ >
+ Add Zip File
+ </Button>
+ </Dialog.Body>
+ <Dialog.CloseTrigger asChild>
+ <CloseButton _hover={{ bg: "gray.100" }} size="sm" />
+ </Dialog.CloseTrigger>
+ </Dialog.Content>
+ </Dialog.Positioner>
+ </Portal>
+ </Dialog.RootProvider>
+ </>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/home/ButtonWithIcon.tsx
b/modules/research-framework/portal/src/components/home/ButtonWithIcon.tsx
new file mode 100644
index 0000000000..c970dcb185
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/ButtonWithIcon.tsx
@@ -0,0 +1,22 @@
+import { Button, ButtonProps, Icon } from "@chakra-ui/react";
+import { IconType } from "react-icons";
+
+interface ButtonWithIconProps extends ButtonProps {
+ icon: IconType;
+ children: React.ReactNode;
+}
+
+export const ButtonWithIcon = ({
+ icon: UserIcon,
+ children,
+ ...rest
+}: ButtonWithIconProps) => {
+ return (
+ <Button {...rest}>
+ <Icon size="xs">
+ <UserIcon fontWeight="solid" />
+ </Icon>
+ {children}
+ </Button>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/home/ProjectCard.tsx
b/modules/research-framework/portal/src/components/home/ProjectCard.tsx
new file mode 100644
index 0000000000..05173203c2
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/ProjectCard.tsx
@@ -0,0 +1,84 @@
+import { ProjectType } from "@/interfaces/ProjectType";
+import {
+ Text,
+ Box,
+ Card,
+ HStack,
+ Avatar,
+ Button,
+ Icon,
+ Image,
+ Badge,
+} from "@chakra-ui/react";
+import { FaPlay } from "react-icons/fa";
+import { Link } from "react-router";
+
+export const ProjectCard = ({ project }: { project: ProjectType }) => {
+ return (
+ <Box>
+ <Card.Root overflow="hidden">
+ <Image
+ src={project.metadata.images.headerImage}
+ maxH={100}
+ alt="Green double couch with wooden legs"
+ />
+ <Link to={`/${project.metadata.type}/${project.metadata.slug}`}>
+ <Card.Body
+ gap="2"
+ _hover={{
+ bg: "gray.100",
+ }}
+ >
+ <Card.Title>{project.metadata.title}</Card.Title>
+ <HStack flexWrap={"wrap"}>
+ <Badge
+ colorPalette={
+ project.metadata.type === "notebook" ? "cyan" : "pink"
+ }
+ fontWeight="bold"
+ size="md"
+ >
+ {project.metadata.type}
+ </Badge>
+ {project.metadata.tags.map((tag: string) => (
+ <Badge size="md" key={tag}>
+ {tag}
+ </Badge>
+ ))}
+ </HStack>
+ <Text color="fg.muted" lineClamp={2}>
+ {project.metadata.description}.
+ </Text>
+ </Card.Body>
+ </Link>
+
+ <Card.Footer justifyContent="space-between">
+ <HStack mt={4}>
+ <Avatar.Root shape="full" size="xl">
+ <Avatar.Fallback name={project.metadata.author.name} />
+ <Avatar.Image src={project.metadata.author.avatar} />
+ </Avatar.Root>
+
+ <Box>
+ <Text fontWeight="bold">{project.metadata.author.name}</Text>
+ <Text color="gray.500">{project.metadata.author.role}</Text>
+ </Box>
+ </HStack>
+
+ <Button
+ bg={project.metadata.type === "notebook" ? "cyan.500" : "pink.500"}
+ _hover={{
+ bg:
+ project.metadata.type === "notebook" ? "cyan.600" : "pink.600",
+ }}
+ >
+ Run
+ <Icon ml={1} size={"sm"}>
+ <FaPlay />
+ </Icon>
+ </Button>
+ </Card.Footer>
+ </Card.Root>
+ </Box>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/home/ProjectsSection.tsx
b/modules/research-framework/portal/src/components/home/ProjectsSection.tsx
new file mode 100644
index 0000000000..836621dbd8
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/ProjectsSection.tsx
@@ -0,0 +1,28 @@
+import { ProjectType } from "@/interfaces/ProjectType";
+import { Input } from "@chakra-ui/react";
+import { LuSearch } from "react-icons/lu";
+// @ts-expect-error This is fine
+import { MOCK_PROJECTS } from "../../data/MOCK_DATA";
+import { ProjectCard } from "./ProjectCard";
+import { InputGroup } from "../ui/input-group";
+import { GridContainer } from "../GridContainer";
+
+export const ProjectsSection = () => {
+ // shuffle projects
+ // const shuffledProjects = MOCK_PROJECTS.sort(() => 0.5 - Math.random());
+
+ const projects = MOCK_PROJECTS;
+ return (
+ <>
+ <InputGroup mt={2} endElement={<LuSearch />} w="100%">
+ <Input placeholder="Search" rounded="md" />
+ </InputGroup>
+
+ <GridContainer>
+ {projects.map((project: ProjectType) => {
+ return <ProjectCard key={project.metadata.slug} project={project} />;
+ })}
+ </GridContainer>
+ </>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/home/SessionCard.tsx
b/modules/research-framework/portal/src/components/home/SessionCard.tsx
new file mode 100644
index 0000000000..a6bf2ee4be
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/SessionCard.tsx
@@ -0,0 +1,71 @@
+import { SessionType } from "@/interfaces/SessionType";
+import {
+ Box,
+ Text,
+ Card,
+ Heading,
+ HStack,
+ SimpleGrid,
+ Badge,
+} from "@chakra-ui/react";
+
+const getColorPalette = (status: string) => {
+ switch (status) {
+ case "running":
+ return "green";
+ case "stopped":
+ return "red";
+ case "pending":
+ return "yellow";
+ default:
+ return "gray";
+ }
+};
+export const SessionCard = ({ session }: { session: SessionType }) => {
+ return (
+ <Card.Root
+ size="sm"
+ bg={
+ session.title.toLocaleLowerCase() === "jupyter"
+ ? "green.50"
+ : "purple.50"
+ }
+ >
+ <Card.Header>
+ <HStack justify="space-between" alignItems="flex-start">
+ <Box>
+ <Heading size="lg">{session.title}</Heading>
+ <Text color="fg.muted">{session.started}</Text>
+ </Box>
+ <Badge size="md" colorPalette={getColorPalette(session.status)}>
+ {session.status}
+ </Badge>
+ </HStack>
+ </Card.Header>
+ <Card.Body>
+ <SimpleGrid columns={2}>
+ <KeyValue label="Models" value={session.models.join(", ")} />
+ <KeyValue label="Datasets" value={session.datasets.join(", ")} />
+ <KeyValue label="Nodes" value={session.nodes.toString()} />
+ <KeyValue label="RAM" value={session.ram.toString()} />
+ <KeyValue label="Storage" value={session.storage.toString()} />
+ </SimpleGrid>
+ </Card.Body>
+ </Card.Root>
+ );
+};
+
+export const KeyValue = ({
+ label,
+ value,
+}: {
+ label: string;
+ value: string;
+}) => {
+ return (
+ <HStack>
+ <Text fontWeight="bold">{label}:</Text>
+ <Text>{value}</Text>
+ </HStack>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/home/SessionsSection.tsx
b/modules/research-framework/portal/src/components/home/SessionsSection.tsx
new file mode 100644
index 0000000000..d3dd2eeea4
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/SessionsSection.tsx
@@ -0,0 +1,17 @@
+// @ts-expect-error This is fine
+import { MOCK_SESSIONS } from "../../data/MOCK_DATA";
+import { SessionType } from "@/interfaces/SessionType";
+import { SessionCard } from "./SessionCard";
+import { GridContainer } from "../GridContainer";
+
+export const SessionsSection = () => {
+ return (
+ <>
+ <GridContainer>
+ {MOCK_SESSIONS.map((session: SessionType) => {
+ return <SessionCard key={session.sessionId} session={session} />;
+ })}
+ </GridContainer>
+ </>
+ );
+};
diff --git a/modules/research-framework/portal/src/components/home/index.tsx
b/modules/research-framework/portal/src/components/home/index.tsx
new file mode 100644
index 0000000000..6a96f64e5e
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/index.tsx
@@ -0,0 +1,61 @@
+import { Box, HStack, Container } from "@chakra-ui/react";
+
+import NavBar from "../NavBar";
+import { PageHeader } from "../PageHeader";
+import { AddRepositoryButton } from "./AddRepositoryButton";
+import { AddZipButton } from "./AddZipButton";
+import { ProjectsSection } from "./ProjectsSection";
+import { ButtonWithIcon } from "./ButtonWithIcon";
+import { FaPlus } from "react-icons/fa";
+import { SessionsSection } from "./SessionsSection";
+
+const Home = () => {
+ return (
+ <Box>
+ <NavBar />
+
+ <Container maxW="container.xl" p={4}>
+ <HStack alignItems="flex-end" justify="space-between">
+ <PageHeader
+ title="Notebooks"
+ description="Community-Published Scientific Notebooks and
Repositories."
+ />
+
+ <HStack gap={4}>
+ <AddZipButton />
+ <AddRepositoryButton />
+ </HStack>
+ </HStack>
+
+ <ProjectsSection />
+
+ <HStack alignItems="flex-end" justify="space-between" mt={4}>
+ <PageHeader
+ title="Sessions"
+ description="Stop or attach to past sessions. Each session
preserves your code and data."
+ />
+
+ <HStack gap={4}>
+ <ButtonWithIcon
+ bg="purple.500"
+ _hover={{ bg: "purple.600" }}
+ icon={FaPlus}
+ >
+ VSCode
+ </ButtonWithIcon>
+ <ButtonWithIcon
+ bg="green.600"
+ _hover={{ bg: "green.700" }}
+ icon={FaPlus}
+ >
+ Jupyter
+ </ButtonWithIcon>
+ </HStack>
+ </HStack>
+ <SessionsSection />
+ </Container>
+ </Box>
+ );
+};
+
+export default Home;
diff --git
a/modules/research-framework/portal/src/components/models/ModelCard.tsx
b/modules/research-framework/portal/src/components/models/ModelCard.tsx
new file mode 100644
index 0000000000..9fd0898e54
--- /dev/null
+++ b/modules/research-framework/portal/src/components/models/ModelCard.tsx
@@ -0,0 +1,34 @@
+import { Badge, Box, Card, HStack, Heading } from "@chakra-ui/react";
+import { ModelType } from "@/interfaces/ModelType";
+import { Link } from "react-router";
+export const ModelCard = ({ model }: { model: ModelType }) => {
+ return (
+ <Box>
+ <Card.Root
+ size="sm"
+ _hover={{
+ bg: "gray.100",
+ cursor: "pointer",
+ }}
+ >
+ <Link to={`/models/${model.appModuleId}`}>
+ <Card.Header>
+ <HStack
+ justifyContent="space-between"
+ alignItems="flex-start"
+ flexWrap="wrap"
+ >
+ <Heading size="md"> {model.appModuleName}</Heading>
+ {model.appModuleVersion && (
+ <Badge size="md" colorPalette="green">
+ {model.appModuleVersion}
+ </Badge>
+ )}
+ </HStack>
+ </Card.Header>
+ <Card.Body color="fg.muted">{model.appModuleDescription}</Card.Body>
+ </Link>
+ </Card.Root>
+ </Box>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/models/ModelDetails.tsx
b/modules/research-framework/portal/src/components/models/ModelDetails.tsx
new file mode 100644
index 0000000000..db3eff60ff
--- /dev/null
+++ b/modules/research-framework/portal/src/components/models/ModelDetails.tsx
@@ -0,0 +1,96 @@
+import { useParams } from "react-router";
+import NavBar from "../NavBar";
+// @ts-expect-error This is fine
+import { MOCK_MODELS } from "../../data/MOCK_DATA";
+import { ModelType } from "@/interfaces/ModelType";
+import { useState, useEffect } from "react";
+import {
+ Badge,
+ Button,
+ Code,
+ Container,
+ HStack,
+ Heading,
+ Text,
+} from "@chakra-ui/react";
+
+async function getModel(id: string | undefined) {
+ return MOCK_MODELS.find((model: ModelType) => model.appModuleId === id);
+}
+
+export const ModelDetails = () => {
+ const { id } = useParams();
+ const [model, setModel] = useState<ModelType | null>(null);
+ const [copyClicked, setCopyClicked] = useState(false);
+
+ useEffect(() => {
+ if (!id) return;
+
+ async function getData() {
+ const n = await getModel(id);
+ setModel(n);
+ }
+ getData();
+ }, [id]);
+
+ if (!model) return null;
+
+ const templateCode = `exp = md.AlphaFold2.initialize(
+ name=...,
+ input_seq=...,
+ max_template_date=...,
+ model_preset=...,
+ multimers_per_model=...,
+)
+exp.create_task()
+exp.plan().launch()`;
+
+ return (
+ <>
+ <NavBar />
+
+ <Container maxW="breakpoint-md" p={4}>
+ <HStack
+ alignItems="center"
+ mt={4}
+ justifyContent="space-between"
+ flexWrap="wrap"
+ >
+ <Heading size="4xl">{model.appModuleName}</Heading>
+ {model.appModuleVersion && (
+ <Badge size="md" colorPalette="green">
+ {model.appModuleVersion}
+ </Badge>
+ )}
+ </HStack>
+ <Text color="fg.muted" mt={2}>
+ {model.appModuleDescription}
+ </Text>
+
+ <Heading size="2xl" mt={4}>
+ Example
+ <Button
+ size="xs"
+ ml={2}
+ colorPalette="teal"
+ onClick={() => {
+ navigator.clipboard.writeText(templateCode);
+ setCopyClicked(true);
+ setTimeout(() => setCopyClicked(false), 2000);
+ }}
+ >
+ {copyClicked ? "Copied!" : "Copy"}
+ </Button>
+ </Heading>
+
+ <Code
+ mt={2}
+ p={2}
+ display="block"
+ whiteSpace="pre"
+ children={templateCode}
+ />
+ </Container>
+ </>
+ );
+};
diff --git a/modules/research-framework/portal/src/components/models/index.tsx
b/modules/research-framework/portal/src/components/models/index.tsx
new file mode 100644
index 0000000000..12679290e8
--- /dev/null
+++ b/modules/research-framework/portal/src/components/models/index.tsx
@@ -0,0 +1,40 @@
+import NavBar from "../NavBar";
+// @ts-expect-error This is fine
+import { MOCK_MODELS } from "../../data/MOCK_DATA";
+import { Container, HStack, Input, SimpleGrid } from "@chakra-ui/react";
+import { PageHeader } from "../PageHeader";
+import { ModelCard } from "./ModelCard";
+import { ModelType } from "@/interfaces/ModelType";
+import { LuSearch } from "react-icons/lu";
+import { InputGroup } from "../ui/input-group";
+import { ButtonWithIcon } from "../home/ButtonWithIcon";
+import { FaPlus } from "react-icons/fa";
+
+export const Models = () => {
+ return (
+ <>
+ <NavBar />
+
+ <Container maxW="container.lg" p={4}>
+ <HStack alignItems="flex-end" justify="space-between">
+ <PageHeader
+ title="Models"
+ description="Public and Private Scientific models that can be run
with varying inputs."
+ />
+ <ButtonWithIcon colorPalette="teal" icon={FaPlus}>
+ Model
+ </ButtonWithIcon>
+ </HStack>
+ <InputGroup mt={4} endElement={<LuSearch />} w="100%">
+ <Input placeholder="Search" rounded="md" />
+ </InputGroup>
+
+ <SimpleGrid columns={{ base: 1, md: 2, lg: 3 }} gap={4} mt={4}>
+ {MOCK_MODELS.map((model: ModelType) => {
+ return <ModelCard model={model} key={model.appModuleId} />;
+ })}
+ </SimpleGrid>
+ </Container>
+ </>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/notebooks/NotebookCard.tsx
b/modules/research-framework/portal/src/components/notebooks/NotebookCard.tsx
new file mode 100644
index 0000000000..d2bda42287
--- /dev/null
+++
b/modules/research-framework/portal/src/components/notebooks/NotebookCard.tsx
@@ -0,0 +1,64 @@
+import {
+ Box,
+ Image,
+ Icon,
+ Text,
+ Card,
+ HStack,
+ Avatar,
+ Button,
+} from "@chakra-ui/react";
+import { FaPlay } from "react-icons/fa";
+import { Link } from "react-router";
+
+export const NotebookCard = ({ notebook }: { notebook: any }) => {
+ return (
+ <Box>
+ <Card.Root overflow="hidden">
+ <Image
+ src={notebook.images.headerImage}
+ alt="Green double couch with wooden legs"
+ />
+ <Link to={notebook.slug}>
+ <Card.Body
+ gap="2"
+ _hover={{
+ bg: "gray.100",
+ }}
+ >
+ <Card.Title>{notebook.title}</Card.Title>
+ <HStack>
+ {notebook.tags.map((tag: string) => (
+ <Text key={tag} bg="gray.200" p={1} rounded="md">
+ {tag}
+ </Text>
+ ))}
+ </HStack>
+ <Card.Description>{notebook.description}.</Card.Description>
+ </Card.Body>
+ </Link>
+
+ <Card.Footer justifyContent="space-between">
+ <HStack mt={4}>
+ <Avatar.Root shape="full" size="xl">
+ <Avatar.Fallback name={notebook.author.name} />
+ <Avatar.Image src={notebook.author.avatar} />
+ </Avatar.Root>
+
+ <Box>
+ <Text fontWeight="bold">{notebook.author.name}</Text>
+ <Text color="gray.500">{notebook.author.role}</Text>
+ </Box>
+ </HStack>
+
+ <Button>
+ Run
+ <Icon ml={1} size={"sm"}>
+ <FaPlay />
+ </Icon>
+ </Button>
+ </Card.Footer>
+ </Card.Root>
+ </Box>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/notebooks/ProjectDetails.tsx
b/modules/research-framework/portal/src/components/notebooks/ProjectDetails.tsx
new file mode 100644
index 0000000000..2cf9590076
--- /dev/null
+++
b/modules/research-framework/portal/src/components/notebooks/ProjectDetails.tsx
@@ -0,0 +1,98 @@
+import { Link, useParams } from "react-router";
+import NavBar from "../NavBar";
+import {
+ Container,
+ Spinner,
+ HStack,
+ Box,
+ Separator,
+ Button,
+ Icon,
+ Link as ChakraLink,
+} from "@chakra-ui/react";
+// @ts-expect-error This is fine
+import { MOCK_PROJECTS } from "../../data/MOCK_DATA";
+import { useEffect, useState } from "react";
+import { BiArrowBack } from "react-icons/bi";
+import { ProjectType } from "@/interfaces/ProjectType";
+import { Metadata } from "../Metadata";
+
+async function getProject(slug: string | undefined) {
+ return MOCK_PROJECTS.find(
+ (project: ProjectType) => project.metadata.slug === slug
+ );
+}
+
+const ProjectDetails = () => {
+ const { slug } = useParams();
+ const [project, setProject] = useState<ProjectType | null>(null);
+
+ useEffect(() => {
+ if (!slug) return;
+
+ async function getData() {
+ const n = await getProject(slug);
+ setProject(n);
+ }
+ getData();
+ }, [slug]);
+
+ if (!project) return <Spinner />;
+
+ const isNotebook = project?.notebookViewer !== undefined;
+
+ return (
+ <>
+ <NavBar />
+
+ <Container maxW="breakpoint-lg" mx="auto" p={4} mt={16}>
+ <Box>
+ <HStack alignItems="center" mb={4}>
+ <Icon>
+ <BiArrowBack />
+ </Icon>
+ <Link to="/">Back to Projects</Link>
+ </HStack>
+ </Box>
+ <Metadata metadata={project.metadata} />
+
+ <Separator my={8} />
+
+ <Button colorPalette="teal" mb={4} w="full">
+ <ChakraLink
+ target="_blank"
+ href={project?.notebookViewer || project?.repositoryUrl}
+ color="white"
+ fontWeight="bold"
+ display="block"
+ width="100%"
+ textAlign="center"
+ >
+ Open
+ </ChakraLink>
+ </Button>
+
+ <Box border="2px solid black">
+ <Box height="600px" borderRadius="md" overflow="hidden">
+ {isNotebook ? (
+ <iframe
+ title="notebook"
+ src={project?.notebookViewer}
+ width="100%"
+ height="100%"
+ />
+ ) : (
+ <iframe
+ width="100%"
+ height="100%"
+
src={`https://emgithub.com/iframe.html?target=${project.repositoryUrl}%2Fblob%2Fmaster%2FREADME.md&style=default&type=markdown&showBorder=on&showLineNumbers=on&showFileMeta=on&showFullPath=on&showCopy=on`}
+ ></iframe>
+ )}
+ </Box>
+ </Box>
+ </Container>
+ </>
+ );
+};
+
+export default ProjectDetails;
diff --git
a/modules/research-framework/portal/src/components/notebooks/index.tsx
b/modules/research-framework/portal/src/components/notebooks/index.tsx
new file mode 100644
index 0000000000..7fa14096d3
--- /dev/null
+++ b/modules/research-framework/portal/src/components/notebooks/index.tsx
@@ -0,0 +1,55 @@
+import { Box, Container, HStack, Input, SimpleGrid } from "@chakra-ui/react";
+import NavBar from "../NavBar";
+import { PageHeader } from "../PageHeader";
+import { FaCode } from "react-icons/fa";
+import { MOCK_NOTEBOOKS } from "../../data/MOCK_DATA";
+import { NotebookCard } from "./NotebookCard";
+import { InputGroup } from "../ui/input-group";
+import { LuSearch } from "react-icons/lu";
+import { TagsInput } from "react-tag-input-component";
+import { useState } from "react";
+
+const Notebooks = () => {
+ const [tags, setTags] = useState<string[]>([]);
+ let filteredNotebooks = MOCK_NOTEBOOKS;
+ if (tags) {
+ filteredNotebooks = MOCK_NOTEBOOKS.filter((notebook) => {
+ return tags.every((tag) => notebook.tags.includes(tag));
+ });
+ }
+ return (
+ <>
+ <NavBar />
+
+ <Container maxW="container.lg" mt={8}>
+ <PageHeader
+ title="Notebooks"
+ icon={<FaCode />}
+ description="Create and manage your notebooks. From here, you can
create new notebooks, view existing ones, and manage them."
+ />
+ <InputGroup endElement={<LuSearch />} w="100%" mt={16}>
+ <Input placeholder="Search" rounded="md" />
+ </InputGroup>
+ <Box mt={4}>
+ <TagsInput
+ value={tags}
+ onChange={setTags}
+ placeHolder="Filter by tags"
+ />
+ </Box>
+ <SimpleGrid
+ columns={{ base: 1, md: 2, lg: 3 }}
+ mt={4}
+ gap={12}
+ justifyContent="space-around"
+ >
+ {filteredNotebooks.map((notebook: any) => {
+ return <NotebookCard key={notebook.slug} notebook={notebook} />;
+ })}
+ </SimpleGrid>
+ </Container>
+ </>
+ );
+};
+
+export default Notebooks;
diff --git
a/modules/research-framework/portal/src/components/repositories/RepositoryCard.tsx
b/modules/research-framework/portal/src/components/repositories/RepositoryCard.tsx
new file mode 100644
index 0000000000..5d5f2ba8e4
--- /dev/null
+++
b/modules/research-framework/portal/src/components/repositories/RepositoryCard.tsx
@@ -0,0 +1,39 @@
+import {
+ Card,
+ Heading,
+ Box,
+ Text,
+ Link as ChakraLink,
+ HStack,
+} from "@chakra-ui/react";
+export const RepositoryCard = ({ repository }) => {
+ return (
+ <Card.Root width="100%">
+ <Card.Header>
+ <HStack justifyContent="space-between" alignItems="flex-start">
+ <Box>
+ <Heading size="md">{repository.title}</Heading>
+ <Text color="gray.500" mt={2}>
+ {repository.date}
+ </Text>
+ </Box>
+
+ <Text>
+ <ChakraLink
+ href={repository.githubUrl}
+ target="_blank"
+ bg="black"
+ color="white"
+ p={2}
+ rounded="md"
+ fontWeight="bold"
+ >
+ View on GitHub
+ </ChakraLink>
+ </Text>
+ </HStack>
+ </Card.Header>
+ <Card.Body color="fg.muted">{repository.description}</Card.Body>
+ </Card.Root>
+ );
+};
diff --git
a/modules/research-framework/portal/src/components/repositories/index.tsx
b/modules/research-framework/portal/src/components/repositories/index.tsx
new file mode 100644
index 0000000000..4b1e1ebf2d
--- /dev/null
+++ b/modules/research-framework/portal/src/components/repositories/index.tsx
@@ -0,0 +1,39 @@
+import { Container, VStack } from "@chakra-ui/react";
+import NavBar from "../NavBar";
+import { BsGithub } from "react-icons/bs";
+import { PageHeader } from "../PageHeader";
+import { MOCK_REPOSITORIES } from "../../data/MOCK_DATA";
+import { RepositoryCard } from "./RepositoryCard";
+
+const Repositories = () => {
+ return (
+ <>
+ <NavBar />
+
+ <Container maxW="container.lg" mt={8}>
+ <PageHeader
+ title="Repositories"
+ icon={<BsGithub />}
+ description="View the most viewed repositories on GitHub, all
central to this page."
+ />
+
+ <VStack gap={4} mt={8}>
+ {
+ // This is the code that will be replaced by the code snippet
+ // from the instructions
+ MOCK_REPOSITORIES.map((repository) => {
+ return (
+ <RepositoryCard
+ repository={repository}
+ key={repository.githubUrl}
+ />
+ );
+ })
+ }
+ </VStack>
+ </Container>
+ </>
+ );
+};
+
+export default Repositories;
diff --git a/modules/research-framework/portal/src/components/ui/color-mode.tsx
b/modules/research-framework/portal/src/components/ui/color-mode.tsx
new file mode 100644
index 0000000000..f93feabca9
--- /dev/null
+++ b/modules/research-framework/portal/src/components/ui/color-mode.tsx
@@ -0,0 +1,107 @@
+"use client"
+
+import type { IconButtonProps, SpanProps } from "@chakra-ui/react"
+import { ClientOnly, IconButton, Skeleton, Span } from "@chakra-ui/react"
+import { ThemeProvider, useTheme } from "next-themes"
+import type { ThemeProviderProps } from "next-themes"
+import * as React from "react"
+import { LuMoon, LuSun } from "react-icons/lu"
+
+export interface ColorModeProviderProps extends ThemeProviderProps {}
+
+export function ColorModeProvider(props: ColorModeProviderProps) {
+ return (
+ <ThemeProvider attribute="class" disableTransitionOnChange {...props} />
+ )
+}
+
+export type ColorMode = "light" | "dark"
+
+export interface UseColorModeReturn {
+ colorMode: ColorMode
+ setColorMode: (colorMode: ColorMode) => void
+ toggleColorMode: () => void
+}
+
+export function useColorMode(): UseColorModeReturn {
+ const { resolvedTheme, setTheme } = useTheme()
+ const toggleColorMode = () => {
+ setTheme(resolvedTheme === "dark" ? "light" : "dark")
+ }
+ return {
+ colorMode: resolvedTheme as ColorMode,
+ setColorMode: setTheme,
+ toggleColorMode,
+ }
+}
+
+export function useColorModeValue<T>(light: T, dark: T) {
+ const { colorMode } = useColorMode()
+ return colorMode === "dark" ? dark : light
+}
+
+export function ColorModeIcon() {
+ const { colorMode } = useColorMode()
+ return colorMode === "dark" ? <LuMoon /> : <LuSun />
+}
+
+interface ColorModeButtonProps extends Omit<IconButtonProps, "aria-label"> {}
+
+export const ColorModeButton = React.forwardRef<
+ HTMLButtonElement,
+ ColorModeButtonProps
+>(function ColorModeButton(props, ref) {
+ const { toggleColorMode } = useColorMode()
+ return (
+ <ClientOnly fallback={<Skeleton boxSize="8" />}>
+ <IconButton
+ onClick={toggleColorMode}
+ variant="ghost"
+ aria-label="Toggle color mode"
+ size="sm"
+ ref={ref}
+ {...props}
+ css={{
+ _icon: {
+ width: "5",
+ height: "5",
+ },
+ }}
+ >
+ <ColorModeIcon />
+ </IconButton>
+ </ClientOnly>
+ )
+})
+
+export const LightMode = React.forwardRef<HTMLSpanElement, SpanProps>(
+ function LightMode(props, ref) {
+ return (
+ <Span
+ color="fg"
+ display="contents"
+ className="chakra-theme light"
+ colorPalette="gray"
+ colorScheme="light"
+ ref={ref}
+ {...props}
+ />
+ )
+ },
+)
+
+export const DarkMode = React.forwardRef<HTMLSpanElement, SpanProps>(
+ function DarkMode(props, ref) {
+ return (
+ <Span
+ color="fg"
+ display="contents"
+ className="chakra-theme dark"
+ colorPalette="gray"
+ colorScheme="dark"
+ ref={ref}
+ {...props}
+ />
+ )
+ },
+)
diff --git
a/modules/research-framework/portal/src/components/ui/input-group.tsx
b/modules/research-framework/portal/src/components/ui/input-group.tsx
new file mode 100644
index 0000000000..5d8fb32ad2
--- /dev/null
+++ b/modules/research-framework/portal/src/components/ui/input-group.tsx
@@ -0,0 +1,53 @@
+import type { BoxProps, InputElementProps } from "@chakra-ui/react"
+import { Group, InputElement } from "@chakra-ui/react"
+import * as React from "react"
+
+export interface InputGroupProps extends BoxProps {
+ startElementProps?: InputElementProps
+ endElementProps?: InputElementProps
+ startElement?: React.ReactNode
+ endElement?: React.ReactNode
+ children: React.ReactElement<InputElementProps>
+ startOffset?: InputElementProps["paddingStart"]
+ endOffset?: InputElementProps["paddingEnd"]
+}
+
+export const InputGroup = React.forwardRef<HTMLDivElement, InputGroupProps>(
+ function InputGroup(props, ref) {
+ const {
+ startElement,
+ startElementProps,
+ endElement,
+ endElementProps,
+ children,
+ startOffset = "6px",
+ endOffset = "6px",
+ ...rest
+ } = props
+
+ const child =
+ React.Children.only<React.ReactElement<InputElementProps>>(children)
+
+ return (
+ <Group ref={ref} {...rest}>
+ {startElement && (
+ <InputElement pointerEvents="none" {...startElementProps}>
+ {startElement}
+ </InputElement>
+ )}
+ {React.cloneElement(child, {
+ ...(startElement && {
+ ps: `calc(var(--input-height) - ${startOffset})`,
+ }),
+ ...(endElement && { pe: `calc(var(--input-height) - ${endOffset})`
}),
+ ...children.props,
+ })}
+ {endElement && (
+ <InputElement placement="end" {...endElementProps}>
+ {endElement}
+ </InputElement>
+ )}
+ </Group>
+ )
+ },
+)
diff --git a/modules/research-framework/portal/src/components/ui/provider.tsx
b/modules/research-framework/portal/src/components/ui/provider.tsx
new file mode 100644
index 0000000000..c1f896c87b
--- /dev/null
+++ b/modules/research-framework/portal/src/components/ui/provider.tsx
@@ -0,0 +1,12 @@
+"use client";
+
+import { ChakraProvider, defaultSystem } from "@chakra-ui/react";
+import { ColorModeProvider, type ColorModeProviderProps } from "./color-mode";
+
+export function Provider(props: ColorModeProviderProps) {
+ return (
+ <ChakraProvider value={defaultSystem}>
+ <ColorModeProvider {...props} />
+ </ChakraProvider>
+ );
+}
diff --git a/modules/research-framework/portal/src/data/MOCK_DATA.js
b/modules/research-framework/portal/src/data/MOCK_DATA.js
new file mode 100644
index 0000000000..774b31c38b
--- /dev/null
+++ b/modules/research-framework/portal/src/data/MOCK_DATA.js
@@ -0,0 +1,555 @@
+export const MOCK_PROJECTS = [
+ {
+ "metadata": {
+ "type": "notebook",
+ "title": "A new framework for molecular sciences",
+ "slug": "a-new-framework-for-molecular-sciences",
+ "description": "This study presents a new framework for molecular
sciences that can be used to study the molecular structure of molecules and
their interactions with other molecules. The framework is based on the concept
of molecular structure and is designed to be used in conjunction with other
molecular sciences tools.",
+ "author": {
+ "name": "Dr. John Doe",
+ "avatar":
"https://media.istockphoto.com/id/1356638196/photo/teenage-student-using-the-microscope-in-the-laboratory.jpg?s=612x612&w=0&k=20&c=v4ZITw5Wm3QmlcNRssQjShgE5l61E9TW62kK3k-G3jY=",
+ "role": "contributor"
+ },
+ "tags": ["science", "molecular", "framework"],
+ "date": "2021-01-01",
+ "images": {
+ "headerImage":
"https://scitechdaily.com/images/Artistic-Molecular-Structure.jpg"
+ },
+ },
+ "notebookViewer": "https://jupyter.org/try-jupyter/lab/"
+ },
+ {
+ "metadata": {
+ "type": "notebook",
+ "title": "The role of machine learning in molecular sciences",
+ "slug": "the-role-of-machine-learning-in-molecular-sciences",
+ "description": "This study presents the role of machine learning in
molecular sciences and how it can be used to study the molecular structure of
molecules and their interactions with other molecules. The study is based on
the concept of molecular structure and is designed to be used in conjunction
with other molecular sciences tools.",
+ "author": {
+ "name": "Dr. Jane Doe",
+ "avatar":
"https://media.istockphoto.com/id/1356638196/photo/teenage-student-using-the-microscope-in-the-laboratory.jpg?s=612x612&w=0&k=20&c=v4ZITw5Wm3QmlcNRssQjShgE5l61E9TW62kK3k-G3jY=",
+ "role": "contributor"
+ },
+ "tags": ["science", "molecular", "machine learning"],
+ "date": "2021-01-02",
+ "images": {
+ "headerImage":
"https://scitechdaily.com/images/Artistic-Molecular-Structure.jpg"
+ },
+ },
+ "notebookViewer": "https://jupyter.org/try-jupyter/lab/"
+ },
+ {
+ "metadata": {
+ "type": "notebook",
+ "title": "The future of molecular sciences",
+ "slug": "the-future-of-molecular-sciences",
+ "description": "This study presents the future of molecular sciences and
how it can be used to study the molecular structure of molecules and their
interactions with other molecules. The study is based on the concept of
molecular structure and is designed to be used in conjunction with other
molecular sciences tools.",
+ "author": {
+ "name": "Dr. James Doe",
+ "avatar":
"https://media.istockphoto.com/id/1356638196/photo/teenage-student-using-the-microscope-in-the-laboratory.jpg?s=612x612&w=0&k=20&c=v4ZITw5Wm3QmlcNRssQjShgE5l61E9TW62kK3k-G3jY=",
+ "role": "contributor"
+ },
+ "tags": ["science", "molecular", "future"],
+ "date": "2021-01-03",
+ "images": {
+ "headerImage":
"https://scitechdaily.com/images/Artistic-Molecular-Structure.jpg"
+ },
+ },
+ "notebookViewer": "https://jupyter.org/try-jupyter/lab/"
+ },
+ {
+ "metadata": {
+ "type": "repository",
+ "title": "Apache Airavata Repository",
+ "slug": "apache-airavata-repository",
+ "description": "Apache Airavata is a software framework for executing
and managing computational workflows on distributed computing resources
including local clusters, supercomputers, national grids, academic and
commercial clouds.",
+ "author": {
+ "name": "Apache Software Foundation",
+ "avatar": "https://avatars.githubusercontent.com/u/47359?v=4",
+ "role": "contributor"
+ },
+ "tags": ["apache", "airavata", "repository"],
+ "date": "2021-01-01",
+ "images": {
+ "headerImage":
"https://www.gatech.edu/sites/default/files/styles/hero_16_9_extra_extra_large_1800x1013_/public/2022-04/Tech-tower-wreck.jpg?h=fb7e085a&itok=MGAERbjU"
+ },
+ },
+ "repositoryUrl": "https://github.com/apache/airavata"
+ },
+ {
+ "metadata": {
+ "type": "repository",
+ "title": "TensorFlow Repository",
+ "slug": "tensorflow-repository",
+ "description": "TensorFlow is an end-to-end open-source platform for
machine learning, providing a comprehensive, flexible ecosystem of tools,
libraries, and community resources.",
+ "author": {
+ "name": "Google",
+ "avatar": "https://avatars.githubusercontent.com/u/1342004?v=4",
+ "role": "contributor"
+ },
+ "tags": ["tensorflow", "machine learning", "repository"],
+ "date": "2022-05-15",
+ "images": {
+ "headerImage":
"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKis9ECId8eIwn_p0SVMBt3a1vfvKOcOZXy6zK0fWoyzXnzQTguKc2CV__6oI1Pwg22NjWsErpDKqjwQdzjilvmqwWkXPj2ncglphh6mAhpoZ_QXQiDwxnwo-GjKEP0fEOb3uBlNlh9sc/s1600/tensorflow2objectdetection.png"
+ },
+ },
+ "repositoryUrl": "https://github.com/tensorflow/tensorflow",
+ },
+ {
+ "metadata": {
+ "type": "repository",
+ "title": "PyTorch Repository",
+ "slug": "pytorch-repository",
+ "description": "PyTorch is an open-source machine learning framework
that accelerates the path from research prototyping to production deployment.",
+ "author": {
+ "name": "Facebook",
+ "avatar": "https://avatars.githubusercontent.com/u/69631?v=4",
+ "role": "contributor"
+ },
+ "tags": ["pytorch", "machine learning", "repository"],
+ "date": "2023-03-10",
+ "images": {
+ "headerImage":
"https://pytorch.org/assets/images/community-events-recap/fg1.jpg"
+ },
+ },
+ "repositoryUrl": "https://github.com/pytorch/pytorch"
+ }
+];
+
+export const MOCK_DATASETS = [
+ {
+ "metadata": {
+ "type": "dataset",
+ "title": "Dataset A",
+ "slug": "dataset-a",
+ "description": "Dataset A is the best dataset for machine learning
projects. It contains a wide variety of data points and features that can be
used to train and test machine learning models.",
+ "author": {
+ "name": "Facebook",
+ "avatar": "https://avatars.githubusercontent.com/u/69631?v=4",
+ "role": "contributor"
+ },
+ "tags": ["pytorch", "machine learning", "repository"],
+ "date": "2023-03-10",
+ "images": {
+ "headerImage":
"https://pytorch.org/assets/images/community-events-recap/fg1.jpg"
+ },
+ },
+ "datasetUrl":
"https://www.kaggle.com/datasets/muhammadtahir194/movies-dataset-tmdb-top-rated",
+ "private": true,
+ },
+ {
+ "metadata": {
+ "type": "dataset",
+ "title": "Dataset B",
+ "slug": "dataset-b",
+ "description": "Dataset B is the best dataset for machine learning
projects. It contains a wide variety of data points and features that can be
used to train and test machine learning models.",
+ "author": {
+ "name": "Facebook",
+ "avatar": "https://avatars.githubusercontent.com/u/69631?v=4",
+ "role": "contributor"
+ },
+ "tags": ["pytorch", "machine learning", "repository"],
+ "date": "2023-03-10",
+ "images": {
+ "headerImage":
"https://pytorch.org/assets/images/community-events-recap/fg1.jpg"
+ },
+ },
+ "datasetUrl":
"https://www.kaggle.com/datasets/muhammadtahir194/movies-dataset-tmdb-top-rated",
+ "private": false,
+ },
+ {
+ "metadata": {
+ "type": "dataset",
+ "title": "Dataset C",
+ "slug": "dataset-c",
+ "description": "Dataset C is the best dataset for machine learning
projects. It contains a wide variety of data points and features that can be
used to train and test machine learning models.",
+ "author": {
+ "name": "Facebook",
+ "avatar": "https://avatars.githubusercontent.com/u/69631?v=4",
+ "role": "contributor"
+ },
+ "tags": ["pytorch", "machine learning", "repository"],
+ "date": "2023-03-10",
+ "images": {
+ "headerImage":
"https://pytorch.org/assets/images/community-events-recap/fg1.jpg"
+ },
+ },
+ "datasetUrl":
"https://www.kaggle.com/datasets/muhammadtahir194/movies-dataset-tmdb-top-rated",
+ "private": true,
+ },
+];
+
+export const MOCK_SESSIONS = [
+ {
+ "sessionId": "1",
+ "title": "Jupyter",
+ "started": "4 hours ago",
+ "models": ["v1", "v2"],
+ "datasets": ["A", "B", "C"],
+ "nodes": 1,
+ "ram": "8GB",
+ "storage": "40GB",
+ "status": "running"
+ },
+ {
+ "sessionId": "2",
+ "title": "Jupyter",
+ "started": "2 hours ago",
+ "models": ["v1", "v2"],
+ "datasets": ["A", "B", "C"],
+ "nodes": 1,
+ "ram": "8GB",
+ "storage": "40GB",
+ "status": "running"
+ },
+ {
+ "sessionId": "3",
+ "title": "VSCode",
+ "started": "1 hours ago",
+ "models": ["v1", "v2"],
+ "datasets": ["A", "B", "C"],
+ "nodes": 1,
+ "ram": "8GB",
+ "storage": "40GB",
+ "status": "stopped"
+ },
+ {
+ "sessionId": "4",
+ "title": "Jupyter",
+ "started": "1 hours ago",
+ "models": ["v1", "v2"],
+ "datasets": ["A", "B", "C"],
+ "nodes": 1,
+ "ram": "8GB",
+ "storage": "40GB",
+ "status": "stopped"
+ },
+ {
+ "sessionId": "5",
+ "title": "VSCode",
+ "started": "1 hours ago",
+ "models": ["v1", "v2"],
+ "datasets": ["A", "B", "C"],
+ "nodes": 1,
+ "ram": "8GB",
+ "storage": "40GB",
+ "status": "stopped"
+ },
+ {
+ "sessionId": "6",
+ "title": "VSCode",
+ "started": "1 hours ago",
+ "models": ["v1", "v2"],
+ "datasets": ["A", "B", "C"],
+ "nodes": 1,
+ "ram": "8GB",
+ "storage": "40GB",
+ "status": "stopped"
+ },
+ {
+ "sessionId": "7",
+ "title": "Jupyter",
+ "started": "1 hours ago",
+ "models": ["v1", "v2"],
+ "datasets": ["A", "B", "C"],
+ "nodes": 1,
+ "ram": "8GB",
+ "storage": "40GB",
+ "status": "running"
+ },
+];
+
+
+
+
+export const MOCK_NOTEBOOKS = [
+ {
+ "type": "notebook",
+ "title": "A new framework for molecular sciences",
+ "slug": "a-new-framework-for-molecular-sciences",
+ "description": "This study presents a new framework for molecular sciences
that can be used to study the molecular structure of molecules and their
interactions with other molecules. The framework is based on the concept of
molecular structure and is designed to be used in conjunction with other
molecular sciences tools.",
+ "author": {
+ "name": "Dr. John Doe",
+ "avatar":
"https://media.istockphoto.com/id/1356638196/photo/teenage-student-using-the-microscope-in-the-laboratory.jpg?s=612x612&w=0&k=20&c=v4ZITw5Wm3QmlcNRssQjShgE5l61E9TW62kK3k-G3jY=",
+ "role": "contributor"
+ },
+ "tags": ["science", "molecular", "framework"],
+ "date": "2021-01-01",
+ "images": {
+ "headerImage":
"https://scitechdaily.com/images/Artistic-Molecular-Structure.jpg"
+ },
+ "notebookViewer": "https://jupyter.org/try-jupyter/lab/"
+ },
+ {
+ "type": "notebook",
+ "title": "The role of machine learning in molecular sciences",
+ "slug": "the-role-of-machine-learning-in-molecular-sciences",
+ "description": "This study presents the role of machine learning in
molecular sciences and how it can be used to study the molecular structure of
molecules and their interactions with other molecules. The study is based on
the concept of molecular structure and is designed to be used in conjunction
with other molecular sciences tools.",
+ "author": {
+ "name": "Dr. Jane Doe",
+ "avatar":
"https://media.istockphoto.com/id/1356638196/photo/teenage-student-using-the-microscope-in-the-laboratory.jpg?s=612x612&w=0&k=20&c=v4ZITw5Wm3QmlcNRssQjShgE5l61E9TW62kK3k-G3jY=",
+ "role": "contributor"
+ },
+ "tags": ["science", "molecular", "machine learning"],
+ "date": "2021-01-02",
+ "images": {
+ "headerImage":
"https://scitechdaily.com/images/Artistic-Molecular-Structure.jpg"
+ },
+ "notebookViewer": "https://jupyter.org/try-jupyter/lab/"
+ },
+ {
+ "type": "notebook",
+ "title": "The future of molecular sciences",
+ "slug": "the-future-of-molecular-sciences",
+ "description": "This study presents the future of molecular sciences and
how it can be used to study the molecular structure of molecules and their
interactions with other molecules. The study is based on the concept of
molecular structure and is designed to be used in conjunction with other
molecular sciences tools.",
+ "author": {
+ "name": "Dr. James Doe",
+ "avatar":
"https://media.istockphoto.com/id/1356638196/photo/teenage-student-using-the-microscope-in-the-laboratory.jpg?s=612x612&w=0&k=20&c=v4ZITw5Wm3QmlcNRssQjShgE5l61E9TW62kK3k-G3jY=",
+ "role": "contributor"
+ },
+ "tags": ["science", "molecular", "future"],
+ "date": "2021-01-03",
+ "images": {
+ "headerImage":
"https://scitechdaily.com/images/Artistic-Molecular-Structure.jpg"
+ },
+ "notebookViewer": "https://jupyter.org/try-jupyter/lab/"
+ },
+ {
+ "type": "repository",
+ "title": "Apache Airavata Repository",
+ "slug": "apache-airavata-repository",
+ "description": "Apache Airavata is a software framework for executing and
managing computational workflows on distributed computing resources including
local clusters, supercomputers, national grids, academic and commercial
clouds.",
+ "author": {
+ "name": "Apache Software Foundation",
+ "avatar": "https://avatars.githubusercontent.com/u/47359?v=4",
+ "role": "contributor"
+ },
+ "tags": ["apache", "airavata", "repository"],
+ "date": "2021-01-01",
+ "images": {
+ "headerImage":
"https://www.gatech.edu/sites/default/files/styles/hero_16_9_extra_extra_large_1800x1013_/public/2022-04/Tech-tower-wreck.jpg?h=fb7e085a&itok=MGAERbjU"
+ },
+ "repositoryUrl": "https://github.com/apache/airavata"
+ },
+ {
+ "type": "repository",
+ "title": "TensorFlow Repository",
+ "slug": "tensorflow-repository",
+ "description": "TensorFlow is an end-to-end open-source platform for
machine learning, providing a comprehensive, flexible ecosystem of tools,
libraries, and community resources.",
+ "author": {
+ "name": "Google",
+ "avatar": "https://avatars.githubusercontent.com/u/1342004?v=4",
+ "role": "contributor"
+ },
+ "tags": ["tensorflow", "machine learning", "repository"],
+ "date": "2022-05-15",
+ "images": {
+ "headerImage":
"https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEhKis9ECId8eIwn_p0SVMBt3a1vfvKOcOZXy6zK0fWoyzXnzQTguKc2CV__6oI1Pwg22NjWsErpDKqjwQdzjilvmqwWkXPj2ncglphh6mAhpoZ_QXQiDwxnwo-GjKEP0fEOb3uBlNlh9sc/s1600/tensorflow2objectdetection.png"
+ },
+ "repositoryUrl": "https://github.com/tensorflow/tensorflow",
+ },
+ {
+ "type": "repository",
+ "title": "PyTorch Repository",
+ "slug": "pytorch-repository",
+ "description": "PyTorch is an open-source machine learning framework that
accelerates the path from research prototyping to production deployment.",
+ "author": {
+ "name": "Facebook",
+ "avatar": "https://avatars.githubusercontent.com/u/69631?v=4",
+ "role": "contributor"
+ },
+ "tags": ["pytorch", "machine learning", "repository"],
+ "date": "2023-03-10",
+ "images": {
+ "headerImage":
"https://pytorch.org/assets/images/community-events-recap/fg1.jpg"
+ },
+ "repositoryUrl": "https://github.com/pytorch/pytorch"
+ }
+];
+
+
+export const MOCK_REPOSITORIES = [
+ {
+ "githubUrl": "https://github.com/apache/airavata",
+ "title": "Apache Airavata Dataset",
+ "description": "Apache Airavata is a software framework for executing and
managing computational workflows on distributed computing resources including
local clusters, supercomputers, national grids, academic and commercial
clouds.",
+ "date": "2021-01-01",
+ },
+ {
+ "githubUrl": "https://github.com/tensorflow/tensorflow",
+ "title": "TensorFlow",
+ "description": "TensorFlow is an end-to-end open-source platform for
machine learning, providing a comprehensive, flexible ecosystem of tools,
libraries, and community resources.",
+ "date": "2022-05-15",
+ },
+ {
+ "githubUrl": "https://github.com/pytorch/pytorch",
+ "title": "PyTorch",
+ "description": "PyTorch is an open-source machine learning framework that
accelerates the path from research prototyping to production deployment.",
+ "date": "2023-03-10",
+ },
+ {
+ "githubUrl": "https://github.com/apache/spark",
+ "title": "Apache Spark",
+ "description": "Apache Spark is a unified analytics engine for large-scale
data processing, with built-in modules for SQL, streaming, machine learning,
and graph processing.",
+ "date": "2020-11-20",
+ },
+ {
+ "githubUrl": "https://github.com/microsoft/vscode",
+ "title": "Visual Studio Code",
+ "description": "Visual Studio Code is a lightweight but powerful source
code editor that runs on your desktop and is available for Windows, macOS, and
Linux.",
+ "date": "2021-09-30",
+ },
+ {
+ "githubUrl": "https://github.com/nodejs/node",
+ "title": "Node.js",
+ "description": "Node.js is a JavaScript runtime built on Chrome's V8
JavaScript engine, designed for building scalable network applications.",
+ "date": "2022-07-18",
+ },
+ {
+ "githubUrl": "https://github.com/facebook/react",
+ "title": "React",
+ "description": "React is a declarative, efficient, and flexible JavaScript
library for building user interfaces, maintained by Facebook.",
+ "date": "2023-01-05",
+ },
+ {
+ "githubUrl": "https://github.com/django/django",
+ "title": "Django",
+ "description": "Django is a high-level Python web framework that
encourages rapid development and clean, pragmatic design.",
+ "date": "2021-12-10",
+ },
+ {
+ "githubUrl": "https://github.com/rust-lang/rust",
+ "title": "Rust",
+ "description": "Rust is a multi-paradigm programming language designed for
performance and safety, especially safe concurrency.",
+ "date": "2020-06-25",
+ },
+ {
+ "githubUrl": "https://github.com/kubernetes/kubernetes",
+ "title": "Kubernetes",
+ "description": "Kubernetes is an open-source system for automating
deployment, scaling, and management of containerized applications.",
+ "date": "2022-10-14",
+ }
+];
+
+
+export const MOCK_MODELS = [
+ {
+ "appModuleId": "AiravataAgent_6a584041-c8ee-4f89-b1cc-6905093b504c",
+ "appModuleName": "AiravataAgent",
+ "appModuleVersion": "1.0.0",
+ "appModuleDescription": "Airavata Agent Application",
+ "url":
"https://cybershuttle.org/api/applications/AiravataAgent_6a584041-c8ee-4f89-b1cc-6905093b504c/",
+ "applicationInterface":
"https://cybershuttle.org/api/applications/AiravataAgent_6a584041-c8ee-4f89-b1cc-6905093b504c/application_interface/",
+ "applicationDeployments":
"https://cybershuttle.org/api/applications/AiravataAgent_6a584041-c8ee-4f89-b1cc-6905093b504c/application_deployments/",
+ "userHasWriteAccess": false
+ },
+ {
+ "appModuleId": "AlphaFold2_c177803a-f1d2-4559-a306-2e1b9cef6496",
+ "appModuleName": "AlphaFold2",
+ "appModuleVersion": "2.2.0",
+ "appModuleDescription": "AlphaFold is an AI system developed by DeepMind
that makes state-of-the-art accurate predictions of a protein’s structure from
its amino-acid sequence.",
+ "url":
"https://cybershuttle.org/api/applications/AlphaFold2_c177803a-f1d2-4559-a306-2e1b9cef6496/",
+ "applicationInterface":
"https://cybershuttle.org/api/applications/AlphaFold2_c177803a-f1d2-4559-a306-2e1b9cef6496/application_interface/",
+ "applicationDeployments":
"https://cybershuttle.org/api/applications/AlphaFold2_c177803a-f1d2-4559-a306-2e1b9cef6496/application_deployments/",
+ "userHasWriteAccess": false
+ },
+ {
+ "appModuleId": "Amber_pmemd_7fe7fb4b-38a7-45c4-a4e5-2ca9f4f49f3b",
+ "appModuleName": "Amber_pmemd",
+ "appModuleVersion": null,
+ "appModuleDescription": "Assisted Model Building with Energy Refinement MD
Package with Particle Mesh Ewald summation for electrostatic interactions",
+ "url":
"https://cybershuttle.org/api/applications/Amber_pmemd_7fe7fb4b-38a7-45c4-a4e5-2ca9f4f49f3b/",
+ "applicationInterface":
"https://cybershuttle.org/api/applications/Amber_pmemd_7fe7fb4b-38a7-45c4-a4e5-2ca9f4f49f3b/application_interface/",
+ "applicationDeployments":
"https://cybershuttle.org/api/applications/Amber_pmemd_7fe7fb4b-38a7-45c4-a4e5-2ca9f4f49f3b/application_deployments/",
+ "userHasWriteAccess": false
+ },
+ {
+ "appModuleId": "Amber_pmemd_CUDA_25c95122-ac00-4ba6-a834-33f129ad6eb2",
+ "appModuleName": "Amber_pmemd_CUDA",
+ "appModuleVersion": "Amber.22-CUDA",
+ "appModuleDescription": "GPU openmpi version of AMBER pmemd",
+ "url":
"https://cybershuttle.org/api/applications/Amber_pmemd_CUDA_25c95122-ac00-4ba6-a834-33f129ad6eb2/",
+ "applicationInterface":
"https://cybershuttle.org/api/applications/Amber_pmemd_CUDA_25c95122-ac00-4ba6-a834-33f129ad6eb2/application_interface/",
+ "applicationDeployments":
"https://cybershuttle.org/api/applications/Amber_pmemd_CUDA_25c95122-ac00-4ba6-a834-33f129ad6eb2/application_deployments/",
+ "userHasWriteAccess": false
+ },
+ {
+ "appModuleId": "CyberFaCESAgent_74734794-25db-413b-972b-dffbfab346ca",
+ "appModuleName": "CyberFaCESAgent",
+ "appModuleVersion": "1.0.0",
+ "appModuleDescription": "CyberFaCES Agent",
+ "url":
"https://cybershuttle.org/api/applications/CyberFaCESAgent_74734794-25db-413b-972b-dffbfab346ca/",
+ "applicationInterface":
"https://cybershuttle.org/api/applications/CyberFaCESAgent_74734794-25db-413b-972b-dffbfab346ca/application_interface/",
+ "applicationDeployments":
"https://cybershuttle.org/api/applications/CyberFaCESAgent_74734794-25db-413b-972b-dffbfab346ca/application_deployments/",
+ "userHasWriteAccess": false
+ },
+ {
+ "appModuleId": "Echo_88202f35-beef-414a-84a4-6ee9dd4f5a84",
+ "appModuleName": "Echo",
+ "appModuleVersion": "",
+ "appModuleDescription": "",
+ "url":
"https://cybershuttle.org/api/applications/Echo_88202f35-beef-414a-84a4-6ee9dd4f5a84/",
+ "applicationInterface":
"https://cybershuttle.org/api/applications/Echo_88202f35-beef-414a-84a4-6ee9dd4f5a84/application_interface/",
+ "applicationDeployments":
"https://cybershuttle.org/api/applications/Echo_88202f35-beef-414a-84a4-6ee9dd4f5a84/application_deployments/",
+ "userHasWriteAccess": false
+ },
+ {
+ "appModuleId": "Gaussian16_43c4b549-f8c2-4024-accc-d5e965a0025a",
+ "appModuleName": "Gaussian16",
+ "appModuleVersion": "Gaussian16/c.02",
+ "appModuleDescription": "Quantum chemistry application",
+ "url":
"https://cybershuttle.org/api/applications/Gaussian16_43c4b549-f8c2-4024-accc-d5e965a0025a/",
+ "applicationInterface":
"https://cybershuttle.org/api/applications/Gaussian16_43c4b549-f8c2-4024-accc-d5e965a0025a/application_interface/",
+ "applicationDeployments":
"https://cybershuttle.org/api/applications/Gaussian16_43c4b549-f8c2-4024-accc-d5e965a0025a/application_deployments/",
+ "userHasWriteAccess": false
+ },
+ {
+ "appModuleId":
"GEM_Magnetic_Reconnection_Challenge_Problem_43026dd1-5229-4d80-8be3-32c7b876e157",
+ "appModuleName": "GEM_Magnetic_Reconnection_Challenge_Problem",
+ "appModuleVersion": "1.0",
+ "appModuleDescription": "\"Magnetic reconnection is studied in a simple
Harris sheet configuration with a specified set of initial conditions,
including a finite amplitude, magnetic island perturbation to trigger the
dynamics.\"
[https://agupubs.onlinelibrary.wiley.com/doi/abs/10.1029/1999JA900449]",
+ "url":
"https://cybershuttle.org/api/applications/GEM_Magnetic_Reconnection_Challenge_Problem_43026dd1-5229-4d80-8be3-32c7b876e157/",
+ "applicationInterface":
"https://cybershuttle.org/api/applications/GEM_Magnetic_Reconnection_Challenge_Problem_43026dd1-5229-4d80-8be3-32c7b876e157/application_interface/",
+ "applicationDeployments":
"https://cybershuttle.org/api/applications/GEM_Magnetic_Reconnection_Challenge_Problem_43026dd1-5229-4d80-8be3-32c7b876e157/application_deployments/",
+ "userHasWriteAccess": false
+ },
+ {
+ "appModuleId": "GkeyllAgent_c884a86e-5c5e-416d-93e9-59361ac87898",
+ "appModuleName": "GkeyllAgent",
+ "appModuleVersion": "1.0.0",
+ "appModuleDescription": "Gkeyll Agent",
+ "url":
"https://cybershuttle.org/api/applications/GkeyllAgent_c884a86e-5c5e-416d-93e9-59361ac87898/",
+ "applicationInterface":
"https://cybershuttle.org/api/applications/GkeyllAgent_c884a86e-5c5e-416d-93e9-59361ac87898/application_interface/",
+ "applicationDeployments":
"https://cybershuttle.org/api/applications/GkeyllAgent_c884a86e-5c5e-416d-93e9-59361ac87898/application_deployments/",
+ "userHasWriteAccess": false
+ },
+ {
+ "appModuleId":
"Groamcs_with_OptionalRestart_cd3e614c-c66c-4b67-b944-42f0adbaba16",
+ "appModuleName": "Gromacs_with_OptionalRestart",
+ "appModuleVersion": "2016.3",
+ "appModuleDescription": "Gromacs with optional restart from cpt file
staged from a previous job ID provided in a restart text file",
+ "url":
"https://cybershuttle.org/api/applications/Groamcs_with_OptionalRestart_cd3e614c-c66c-4b67-b944-42f0adbaba16/",
+ "applicationInterface":
"https://cybershuttle.org/api/applications/Groamcs_with_OptionalRestart_cd3e614c-c66c-4b67-b944-42f0adbaba16/application_interface/",
+ "applicationDeployments":
"https://cybershuttle.org/api/applications/Groamcs_with_OptionalRestart_cd3e614c-c66c-4b67-b944-42f0adbaba16/application_deployments/",
+ "userHasWriteAccess": false
+ },
+ {
+ "appModuleId": "NAMD_76b9b262-de09-4ab7-b4d4-e89502405af7",
+ "appModuleName": "NAMD",
+ "appModuleVersion": "2.14/3.2022.07",
+ "appModuleDescription": "NAMD is a parallel, object-oriented molecular
dynamics code designed for high-performance simulation of large biomolecular
systems. Simulation preparation and analysis is integrated into the
visualization package VMD. Visit the NAMD website for complete information.",
+ "url":
"https://cybershuttle.org/api/applications/NAMD_76b9b262-de09-4ab7-b4d4-e89502405af7/",
+ "applicationInterface":
"https://cybershuttle.org/api/applications/NAMD_76b9b262-de09-4ab7-b4d4-e89502405af7/application_interface/",
+ "applicationDeployments":
"https://cybershuttle.org/api/applications/NAMD_76b9b262-de09-4ab7-b4d4-e89502405af7/application_deployments/",
+ "userHasWriteAccess": false
+ },
+ {
+ "appModuleId": "VsCode_0545a9d1-2ab1-4312-b0e9-dfa8f470cb15",
+ "appModuleName": "VsCode",
+ "appModuleVersion": "V1.0",
+ "appModuleDescription": null,
+ "url":
"https://cybershuttle.org/api/applications/VsCode_0545a9d1-2ab1-4312-b0e9-dfa8f470cb15/",
+ "applicationInterface":
"https://cybershuttle.org/api/applications/VsCode_0545a9d1-2ab1-4312-b0e9-dfa8f470cb15/application_interface/",
+ "applicationDeployments":
"https://cybershuttle.org/api/applications/VsCode_0545a9d1-2ab1-4312-b0e9-dfa8f470cb15/application_deployments/",
+ "userHasWriteAccess": false
+ }
+];
\ No newline at end of file
diff --git a/modules/research-framework/portal/src/interfaces/AuthorType.tsx
b/modules/research-framework/portal/src/interfaces/AuthorType.tsx
new file mode 100644
index 0000000000..7beaed29a3
--- /dev/null
+++ b/modules/research-framework/portal/src/interfaces/AuthorType.tsx
@@ -0,0 +1,5 @@
+export interface AuthorType {
+ name: string;
+ avatar: string;
+ role: string;
+}
diff --git a/modules/research-framework/portal/src/interfaces/DatasetType.tsx
b/modules/research-framework/portal/src/interfaces/DatasetType.tsx
new file mode 100644
index 0000000000..c331b864d8
--- /dev/null
+++ b/modules/research-framework/portal/src/interfaces/DatasetType.tsx
@@ -0,0 +1,7 @@
+import { MetadataType } from "./MetadataType";
+
+export interface DatasetType {
+ metadata: MetadataType;
+ datasetUrl: string;
+ private: true | false;
+}
diff --git a/modules/research-framework/portal/src/interfaces/MetadataType.tsx
b/modules/research-framework/portal/src/interfaces/MetadataType.tsx
new file mode 100644
index 0000000000..ba183d2185
--- /dev/null
+++ b/modules/research-framework/portal/src/interfaces/MetadataType.tsx
@@ -0,0 +1,14 @@
+import { AuthorType } from "./AuthorType";
+
+export interface MetadataType {
+ type: string;
+ title: string;
+ slug: string;
+ description: string;
+ author: AuthorType;
+ tags: string[];
+ date: string;
+ images: {
+ headerImage: string;
+ };
+}
diff --git a/modules/research-framework/portal/src/interfaces/ModelType.tsx
b/modules/research-framework/portal/src/interfaces/ModelType.tsx
new file mode 100644
index 0000000000..7d1f933a11
--- /dev/null
+++ b/modules/research-framework/portal/src/interfaces/ModelType.tsx
@@ -0,0 +1,10 @@
+export interface ModelType {
+ appModuleId: string;
+ appModuleName: string;
+ appModuleVersion: string;
+ appModuleDescription: string;
+ url: string;
+ applicationInterface: string;
+ applicationDeployments: string;
+ userHasWriteAccess: boolean;
+}
diff --git a/modules/research-framework/portal/src/interfaces/ProjectType.tsx
b/modules/research-framework/portal/src/interfaces/ProjectType.tsx
new file mode 100644
index 0000000000..b09bb66ced
--- /dev/null
+++ b/modules/research-framework/portal/src/interfaces/ProjectType.tsx
@@ -0,0 +1,7 @@
+import { MetadataType } from "./MetadataType";
+
+export interface ProjectType {
+ metadata: MetadataType;
+ notebookViewer?: string;
+ repositoryUrl?: string;
+}
diff --git a/modules/research-framework/portal/src/interfaces/SessionType.tsx
b/modules/research-framework/portal/src/interfaces/SessionType.tsx
new file mode 100644
index 0000000000..fb483cc421
--- /dev/null
+++ b/modules/research-framework/portal/src/interfaces/SessionType.tsx
@@ -0,0 +1,11 @@
+export interface SessionType {
+ sessionId: string;
+ title: string;
+ started: string;
+ models: string[];
+ datasets: string[];
+ nodes: number;
+ ram: number;
+ storage: number;
+ status: "running" | "stopped";
+}
diff --git a/modules/research-framework/portal/src/main.tsx
b/modules/research-framework/portal/src/main.tsx
new file mode 100644
index 0000000000..f12fb02d01
--- /dev/null
+++ b/modules/research-framework/portal/src/main.tsx
@@ -0,0 +1,12 @@
+import { Provider } from "@/components/ui/provider";
+import React from "react";
+import ReactDOM from "react-dom/client";
+import App from "./App";
+
+ReactDOM.createRoot(document.getElementById("root")!).render(
+ <React.StrictMode>
+ <Provider>
+ <App />
+ </Provider>
+ </React.StrictMode>
+);
diff --git a/modules/research-framework/portal/src/vite-env.d.ts
b/modules/research-framework/portal/src/vite-env.d.ts
new file mode 100644
index 0000000000..11f02fe2a0
--- /dev/null
+++ b/modules/research-framework/portal/src/vite-env.d.ts
@@ -0,0 +1 @@
+/// <reference types="vite/client" />