You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airavata.apache.org by di...@apache.org on 2025/04/07 15:48:51 UTC

(airavata) branch cybershuttle-staging updated (fcb0c50670 -> b212d69258)

This is an automated email from the ASF dual-hosted git repository.

dimuthuupe pushed a change to branch cybershuttle-staging
in repository https://gitbox.apache.org/repos/asf/airavata.git


    from fcb0c50670 make remote cell execution responsive. bump version to 2.0.6
     new ab35f65796 refactoring UI and responsive changes
     new 6ea5fe1329 Add auth to swagger
     new ab6eaae418 Support logout
     new 646f5f9e0b Homepage + redirecting logins
     new 8d7e4c439b Condense to single resource page + allow unauthenticated routes
     new b212d69258 Cybershuttle.org homepage copy

The 6 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../research-framework/portal/package-lock.json    |  92 ++++++-
 modules/research-framework/portal/package.json     |   4 +-
 modules/research-framework/portal/src/App.tsx      | 115 +++++---
 .../portal/src/assets/AllenLogo.png                | Bin 0 -> 11565 bytes
 .../portal/src/assets/GTLogo.png                   | Bin 0 -> 53417 bytes
 .../portal/src/assets/Hero.original.png            | Bin 0 -> 3208051 bytes
 .../portal/src/assets/IUFullLogo.png               | Bin 0 -> 10163 bytes
 .../portal/src/assets/IULogo.png                   | Bin 0 -> 2333 bytes
 .../portal/src/assets/MolecularDynamics.png        | Bin 0 -> 37240 bytes
 .../portal/src/assets/NSFLogo.png                  | Bin 0 -> 42375 bytes
 .../portal/src/assets/NeuroScience.png             | Bin 0 -> 37427 bytes
 .../portal/src/assets/PoweredByApache.png          | Bin 0 -> 12850 bytes
 .../portal/src/assets/UCSDLogo.png                 | Bin 0 -> 52605 bytes
 .../portal/src/assets/UIUCLogo.png                 | Bin 0 -> 3490 bytes
 .../portal/src/assets/XSEDELogo.png                | Bin 0 -> 14094 bytes
 .../portal/src/assets/extensibleIcon.png           | Bin 0 -> 7467 bytes
 .../portal/src/assets/reproIcon.png                | Bin 0 -> 4275 bytes
 .../portal/src/assets/thirdPartyIcon.png           | Bin 0 -> 9044 bytes
 .../portal/src/assets/userClockIcon.png            | Bin 0 -> 6939 bytes
 .../portal/src/components/NavBar.tsx               |  90 -------
 .../portal/src/components/auth/Login.tsx           |  21 +-
 .../src/components/auth/ProtectedComponent.tsx     |  20 +-
 .../portal/src/components/auth/UserMenu.tsx        |  73 +++++
 .../portal/src/components/datasets/DatasetCard.tsx |  70 -----
 .../src/components/datasets/DatasetDetails.tsx     |  54 ----
 .../portal/src/components/datasets/index.tsx       |   3 -
 .../src/components/home/CybershuttleLanding.tsx    | 300 +++++++++++++++++++++
 .../home/NotebooksAndRepositoriesSection.tsx       |  49 ----
 .../portal/src/components/home/ResourceCard.tsx    |  19 +-
 .../portal/src/components/home/index.tsx           |   3 -
 .../portal/src/components/models/ModelCard.tsx     |  34 ---
 .../portal/src/components/models/ModelDetails.tsx  |  96 -------
 .../portal/src/components/models/index.tsx         |   3 -
 .../src/components/notebooks/NotebookCard.tsx      |  64 -----
 .../portal/src/components/notebooks/index.tsx      |  19 +-
 .../repositories/RepositorySpecificDetails.tsx     | 152 ++++++-----
 .../portal/src/components/repositories/index.tsx   |   3 -
 .../src/components/resources/ResourceDetails.tsx   |   4 +-
 .../portal/src/components/resources/TagInput.css   | 103 +++++++
 .../portal/src/components/resources/index.tsx      | 226 ++++++++++++++++
 .../src/components/typography/SectionHeading.tsx   |  21 ++
 .../portal/src/layouts/NavBar.tsx                  | 134 +++++++++
 .../portal/src/layouts/NavBarFooterLayout.tsx      |  14 +
 modules/research-framework/portal/src/main.tsx     |  62 +----
 .../research-framework/research-service/pom.xml    |   4 +-
 .../research/service/config/AuthzTokenFilter.java  |  13 +
 .../service/config/DevDataInitializer.java         |  29 +-
 .../research/service/config/OpenApiConfig.java     |  54 ++++
 .../service/controller/ResourceController.java     |  15 +-
 .../research/service/handlers/ResourceHandler.java |  12 +-
 .../service/model/repo/ResourceRepository.java     |  16 ++
 .../src/main/resources/application.yml             |   9 +
 52 files changed, 1325 insertions(+), 675 deletions(-)
 create mode 100644 modules/research-framework/portal/src/assets/AllenLogo.png
 create mode 100644 modules/research-framework/portal/src/assets/GTLogo.png
 create mode 100644 modules/research-framework/portal/src/assets/Hero.original.png
 create mode 100644 modules/research-framework/portal/src/assets/IUFullLogo.png
 create mode 100644 modules/research-framework/portal/src/assets/IULogo.png
 create mode 100644 modules/research-framework/portal/src/assets/MolecularDynamics.png
 create mode 100644 modules/research-framework/portal/src/assets/NSFLogo.png
 create mode 100644 modules/research-framework/portal/src/assets/NeuroScience.png
 create mode 100644 modules/research-framework/portal/src/assets/PoweredByApache.png
 create mode 100644 modules/research-framework/portal/src/assets/UCSDLogo.png
 create mode 100644 modules/research-framework/portal/src/assets/UIUCLogo.png
 create mode 100644 modules/research-framework/portal/src/assets/XSEDELogo.png
 create mode 100644 modules/research-framework/portal/src/assets/extensibleIcon.png
 create mode 100644 modules/research-framework/portal/src/assets/reproIcon.png
 create mode 100644 modules/research-framework/portal/src/assets/thirdPartyIcon.png
 create mode 100644 modules/research-framework/portal/src/assets/userClockIcon.png
 delete mode 100644 modules/research-framework/portal/src/components/NavBar.tsx
 create mode 100644 modules/research-framework/portal/src/components/auth/UserMenu.tsx
 delete mode 100644 modules/research-framework/portal/src/components/datasets/DatasetCard.tsx
 delete mode 100644 modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx
 create mode 100644 modules/research-framework/portal/src/components/home/CybershuttleLanding.tsx
 delete mode 100644 modules/research-framework/portal/src/components/home/NotebooksAndRepositoriesSection.tsx
 delete mode 100644 modules/research-framework/portal/src/components/models/ModelCard.tsx
 delete mode 100644 modules/research-framework/portal/src/components/models/ModelDetails.tsx
 delete mode 100644 modules/research-framework/portal/src/components/notebooks/NotebookCard.tsx
 create mode 100644 modules/research-framework/portal/src/components/resources/TagInput.css
 create mode 100644 modules/research-framework/portal/src/components/resources/index.tsx
 create mode 100644 modules/research-framework/portal/src/components/typography/SectionHeading.tsx
 create mode 100644 modules/research-framework/portal/src/layouts/NavBar.tsx
 create mode 100644 modules/research-framework/portal/src/layouts/NavBarFooterLayout.tsx
 create mode 100644 modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/OpenApiConfig.java


(airavata) 01/06: refactoring UI and responsive changes

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dimuthuupe pushed a commit to branch cybershuttle-staging
in repository https://gitbox.apache.org/repos/asf/airavata.git

commit ab35f657967129881b94e569d29ff481609fc0f0
Author: ganning127 <ga...@gmail.com>
AuthorDate: Fri Apr 4 13:01:54 2025 -0400

    refactoring UI and responsive changes
---
 modules/research-framework/portal/src/App.tsx      |  21 ++-
 .../portal/src/components/NavBar.tsx               |  90 ------------
 .../src/components/datasets/DatasetDetails.tsx     |   2 +-
 .../portal/src/components/datasets/index.tsx       |   3 -
 .../home/NotebooksAndRepositoriesSection.tsx       |  49 -------
 .../portal/src/components/home/ResourceCard.tsx    |   1 -
 .../portal/src/components/home/index.tsx           |   3 -
 .../portal/src/components/models/ModelCard.tsx     |  34 -----
 .../portal/src/components/models/ModelDetails.tsx  |  96 -------------
 .../portal/src/components/models/index.tsx         |   3 -
 .../src/components/notebooks/NotebookCard.tsx      |  64 ---------
 .../portal/src/components/notebooks/index.tsx      |   3 -
 .../repositories/RepositorySpecificDetails.tsx     | 152 +++++++++++----------
 .../portal/src/components/repositories/index.tsx   |   3 -
 .../src/components/resources/ResourceDetails.tsx   |   4 +-
 .../portal/src/layouts/NavBar.tsx                  | 142 +++++++++++++++++++
 .../portal/src/layouts/NavBarFooterLayout.tsx      |  14 ++
 17 files changed, 259 insertions(+), 425 deletions(-)

diff --git a/modules/research-framework/portal/src/App.tsx b/modules/research-framework/portal/src/App.tsx
index 42206ac062..ebeb14af3b 100644
--- a/modules/research-framework/portal/src/App.tsx
+++ b/modules/research-framework/portal/src/App.tsx
@@ -11,6 +11,7 @@ import ProtectedComponent from "./components/auth/ProtectedComponent";
 import { useAuth } from "react-oidc-context";
 import { useEffect } from "react";
 import { setUserProvider } from "./lib/api";
+import NavBarFooterLayout from "./layouts/NavBarFooterLayout";
 function App() {
   const colorMode = useColorMode();
   if (colorMode.colorMode === "dark") {
@@ -28,7 +29,7 @@ function App() {
   return (
     <>
       <BrowserRouter>
-        <Routes>
+        {/* <Routes>
           <Route path="/" element={<Login />} />
 
           <Route
@@ -53,12 +54,28 @@ function App() {
               element={<ProtectedComponent Component={Models} />}
             />
 
-            {/* Dynamic Route for specific resource details */}
             <Route
               path=":type/:id"
               element={<ProtectedComponent Component={ResourceDetails} />}
             />
           </Route>
+        </Routes> */}
+
+        <Routes>
+          {/* Public Route */}
+          <Route path="/" element={<Login />} />
+
+          {/* Protected Routes with Layout */}
+          <Route
+            element={<ProtectedComponent Component={NavBarFooterLayout} />}
+          >
+            <Route path="/projects" element={<Home />} />
+            <Route path="/resources/notebooks" element={<Notebooks />} />
+            <Route path="/resources/datasets" element={<Datasets />} />
+            <Route path="/resources/repositories" element={<Repositories />} />
+            <Route path="/resources/models" element={<Models />} />
+            <Route path="/resources/:type/:id" element={<ResourceDetails />} />
+          </Route>
         </Routes>
       </BrowserRouter>
     </>
diff --git a/modules/research-framework/portal/src/components/NavBar.tsx b/modules/research-framework/portal/src/components/NavBar.tsx
deleted file mode 100644
index 6cc402f985..0000000000
--- a/modules/research-framework/portal/src/components/NavBar.tsx
+++ /dev/null
@@ -1,90 +0,0 @@
-import {
-  Text,
-  Flex,
-  Spacer,
-  Image,
-  HStack,
-  Avatar,
-  Box,
-} from "@chakra-ui/react";
-import ApacheAiravataLogo from "../assets/airavata-logo.png";
-import { Link } from "react-router";
-import { useAuth } from "react-oidc-context";
-
-const NAV_CONTENT = [
-  {
-    title: "Projects",
-    url: "/projects",
-  },
-  {
-    title: "Datasets",
-    url: "/resources/datasets",
-  },
-  {
-    title: "Repositories",
-    url: "/resources/repositories",
-  },
-  {
-    title: "Notebooks",
-    url: "/resources/notebooks",
-  },
-  {
-    title: "Models",
-    url: "/resources/models",
-  },
-];
-
-const NavBar = () => {
-  const auth = useAuth();
-
-  console.log(auth);
-  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="/projects">
-        <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">
-            <Text
-              _hover={{
-                color: "blue.400",
-                textDecoration: "underline",
-              }}
-            >
-              {item.title}
-            </Text>
-          </Link>
-        ))}
-      </Flex>
-
-      <Spacer />
-
-      {/* Search Bar */}
-      <HStack>
-        <Avatar.Root variant="subtle">
-          <Avatar.Fallback name={auth.user?.profile.name} />
-        </Avatar.Root>
-        <Box>
-          <Text>{auth.user?.profile.name}</Text>
-          <Text fontSize="sm" color="gray.500">
-            {auth.user?.profile.email}
-          </Text>
-        </Box>
-      </HStack>
-    </Flex>
-  );
-};
-export default NavBar;
diff --git a/modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx b/modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx
index 79719752aa..975639fcd6 100644
--- a/modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx
+++ b/modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx
@@ -1,5 +1,5 @@
 import { Metadata } from "../Metadata";
-import NavBar from "../NavBar";
+import NavBar from "../../layouts/NavBar";
 // @ts-expect-error This is fine
 import { MOCK_DATASETS } from "../../data/MOCK_DATA";
 import { DatasetType } from "@/interfaces/DatasetType";
diff --git a/modules/research-framework/portal/src/components/datasets/index.tsx b/modules/research-framework/portal/src/components/datasets/index.tsx
index 690dd61152..d2b84b5b1b 100644
--- a/modules/research-framework/portal/src/components/datasets/index.tsx
+++ b/modules/research-framework/portal/src/components/datasets/index.tsx
@@ -1,4 +1,3 @@
-import NavBar from "../NavBar";
 import { Container, HStack, Input, SimpleGrid } from "@chakra-ui/react";
 import { PageHeader } from "../PageHeader";
 import { LuSearch } from "react-icons/lu";
@@ -43,8 +42,6 @@ export const Datasets = () => {
 
   return (
     <>
-      <NavBar />
-
       <Container maxW="container.lg" mt={8}>
         <HStack alignItems="flex-end" justify="space-between">
           <PageHeader
diff --git a/modules/research-framework/portal/src/components/home/NotebooksAndRepositoriesSection.tsx b/modules/research-framework/portal/src/components/home/NotebooksAndRepositoriesSection.tsx
deleted file mode 100644
index 584d3e6b6e..0000000000
--- a/modules/research-framework/portal/src/components/home/NotebooksAndRepositoriesSection.tsx
+++ /dev/null
@@ -1,49 +0,0 @@
-import { Input } from "@chakra-ui/react";
-import { LuSearch } from "react-icons/lu";
-import { ResourceCard } from "./ResourceCard";
-import { InputGroup } from "../ui/input-group";
-import { GridContainer } from "../GridContainer";
-import { useEffect, useState } from "react";
-import api from "@/lib/api";
-import { Resource } from "@/interfaces/ResourceType";
-
-const fetchNotebooksAndRepositories = async () => {
-  try {
-    const response = await api.get(
-      "/project-management/resources?type=NOTEBOOK&type=REPOSITORY"
-    );
-    const data = response.data;
-    return data;
-  } catch (error) {
-    console.error("Error fetching:", error);
-  }
-};
-
-export const NotebooksAndRepositoriesSection = () => {
-  const [resources, setResources] = useState<Resource[]>([]);
-
-  useEffect(() => {
-    async function init() {
-      const projects = await fetchNotebooksAndRepositories();
-      setResources(projects.content);
-    }
-
-    init();
-  }, []);
-
-  console.log("Resources:", resources);
-
-  return (
-    <>
-      <InputGroup mt={2} endElement={<LuSearch />} w="100%">
-        <Input placeholder="Search" rounded="md" />
-      </InputGroup>
-
-      <GridContainer>
-        {resources.map((resource: Resource) => {
-          return <ResourceCard key={resource.id} resource={resource} />;
-        })}
-      </GridContainer>
-    </>
-  );
-};
diff --git a/modules/research-framework/portal/src/components/home/ResourceCard.tsx b/modules/research-framework/portal/src/components/home/ResourceCard.tsx
index bceb4bdd92..a5da829aac 100644
--- a/modules/research-framework/portal/src/components/home/ResourceCard.tsx
+++ b/modules/research-framework/portal/src/components/home/ResourceCard.tsx
@@ -17,7 +17,6 @@ import { ModelCardButton } from "../models/ModelCardButton";
 
 export const ResourceCard = ({ resource }: { resource: Resource }) => {
   const author = resource.authors[0];
-  console.log("author" + author);
 
   const isValidImage = isValidImaage(resource.headerImage);
 
diff --git a/modules/research-framework/portal/src/components/home/index.tsx b/modules/research-framework/portal/src/components/home/index.tsx
index 3a932951f7..4d81bf39ed 100644
--- a/modules/research-framework/portal/src/components/home/index.tsx
+++ b/modules/research-framework/portal/src/components/home/index.tsx
@@ -1,6 +1,5 @@
 import { Box, HStack, Container } from "@chakra-ui/react";
 
-import NavBar from "../NavBar";
 import { PageHeader } from "../PageHeader";
 import { AddRepositoryButton } from "./AddRepositoryButton";
 import { AddZipButton } from "./AddZipButton";
@@ -12,8 +11,6 @@ import { ProjectsSection } from "./ProjectsSection";
 const Home = () => {
   return (
     <Box>
-      <NavBar />
-
       <Container maxW="container.xl" mt={8}>
         <HStack alignItems="flex-end" justify="space-between">
           <PageHeader
diff --git a/modules/research-framework/portal/src/components/models/ModelCard.tsx b/modules/research-framework/portal/src/components/models/ModelCard.tsx
deleted file mode 100644
index 9fd0898e54..0000000000
--- a/modules/research-framework/portal/src/components/models/ModelCard.tsx
+++ /dev/null
@@ -1,34 +0,0 @@
-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
deleted file mode 100644
index db3eff60ff..0000000000
--- a/modules/research-framework/portal/src/components/models/ModelDetails.tsx
+++ /dev/null
@@ -1,96 +0,0 @@
-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
index 2ae3cd27b2..dc1cdce827 100644
--- a/modules/research-framework/portal/src/components/models/index.tsx
+++ b/modules/research-framework/portal/src/components/models/index.tsx
@@ -1,4 +1,3 @@
-import NavBar from "../NavBar";
 import { Container, HStack, Input, SimpleGrid } from "@chakra-ui/react";
 import { PageHeader } from "../PageHeader";
 import { LuSearch } from "react-icons/lu";
@@ -43,8 +42,6 @@ export const Models = () => {
 
   return (
     <>
-      <NavBar />
-
       <Container maxW="container.lg" mt={8}>
         <HStack alignItems="flex-end" justify="space-between">
           <PageHeader
diff --git a/modules/research-framework/portal/src/components/notebooks/NotebookCard.tsx b/modules/research-framework/portal/src/components/notebooks/NotebookCard.tsx
deleted file mode 100644
index d2bda42287..0000000000
--- a/modules/research-framework/portal/src/components/notebooks/NotebookCard.tsx
+++ /dev/null
@@ -1,64 +0,0 @@
-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/index.tsx b/modules/research-framework/portal/src/components/notebooks/index.tsx
index 7baa79aee7..ac0dd44507 100644
--- a/modules/research-framework/portal/src/components/notebooks/index.tsx
+++ b/modules/research-framework/portal/src/components/notebooks/index.tsx
@@ -1,5 +1,4 @@
 import { Container, Input, SimpleGrid } from "@chakra-ui/react";
-import NavBar from "../NavBar";
 import { PageHeader } from "../PageHeader";
 import { InputGroup } from "../ui/input-group";
 import { LuSearch } from "react-icons/lu";
@@ -40,8 +39,6 @@ const Notebooks = () => {
 
   return (
     <>
-      <NavBar />
-
       <Container maxW="container.lg" mt={8}>
         <PageHeader
           title="Notebooks"
diff --git a/modules/research-framework/portal/src/components/repositories/RepositorySpecificDetails.tsx b/modules/research-framework/portal/src/components/repositories/RepositorySpecificDetails.tsx
index ba1f3f08a3..1bd94b9a53 100644
--- a/modules/research-framework/portal/src/components/repositories/RepositorySpecificDetails.tsx
+++ b/modules/research-framework/portal/src/components/repositories/RepositorySpecificDetails.tsx
@@ -11,6 +11,7 @@ import {
   Breadcrumb,
 } from "@chakra-ui/react";
 import { Fragment, useEffect, useState } from "react";
+import { FaGithub } from "react-icons/fa";
 import { FiFolder, FiFile } from "react-icons/fi";
 
 interface FileTreeItem {
@@ -22,11 +23,12 @@ interface FileTreeItem {
 }
 
 export const RepositorySpecificDetails = ({
-  dataset,
+  repository,
 }: {
-  dataset: RepositoryResource;
+  repository: RepositoryResource;
 }) => {
-  const githubUrl = dataset.repositoryUrl;
+  console.log(repository);
+  const githubUrl = repository.repositoryUrl;
   const [fileTree, setFileTree] = useState<FileTreeItem[]>([]);
   const [fileTreeLoading, setFileTreeLoading] = useState(false);
   const [currentPath, setCurrentPath] = useState<string>("");
@@ -122,75 +124,85 @@ export const RepositorySpecificDetails = ({
   if (error !== null) return null;
 
   return (
-    <Box
-      bg="white"
-      p={4}
-      borderRadius="md"
-      shadow="md"
-      overflow="auto"
-      height="full"
-    >
-      <Button onClick={handleGoBack} mb={4}>
-        Back
+    <>
+      {/* @ts-expect-error This is fine */}
+      <Button size="sm" as="a" target="_blank" href={repository.repositoryUrl}>
+        <FaGithub />
+        Open {repository.name} on GitHub
       </Button>
-      <Breadcrumb.Root mt={2}>
-        <Breadcrumb.List>
-          <Breadcrumb.Item>
-            <Breadcrumb.Link href="#">root</Breadcrumb.Link>
-          </Breadcrumb.Item>
 
-          {history.length > 0 &&
-            currentPath
-              .split("/")
-              .filter(Boolean) // Remove empty strings
-              .map((path, index) => (
-                <Fragment key={index}>
-                  <Breadcrumb.Separator />
-                  <Breadcrumb.Item>
-                    <Breadcrumb.Link href="#">{path}</Breadcrumb.Link>
-                  </Breadcrumb.Item>
-                </Fragment>
+      <Box
+        mt={4}
+        bg="white"
+        p={4}
+        borderRadius="md"
+        shadow="md"
+        overflow="auto"
+        height="full"
+      >
+        {/* Open in GitHub button */}
+        <Button onClick={handleGoBack} mb={4}>
+          Back
+        </Button>
+        <Breadcrumb.Root mt={2}>
+          <Breadcrumb.List>
+            <Breadcrumb.Item>
+              <Breadcrumb.Link href="#">root</Breadcrumb.Link>
+            </Breadcrumb.Item>
+
+            {history.length > 0 &&
+              currentPath
+                .split("/")
+                .filter(Boolean) // Remove empty strings
+                .map((path, index) => (
+                  <Fragment key={index}>
+                    <Breadcrumb.Separator />
+                    <Breadcrumb.Item>
+                      <Breadcrumb.Link href="#">{path}</Breadcrumb.Link>
+                    </Breadcrumb.Item>
+                  </Fragment>
+                ))}
+          </Breadcrumb.List>
+        </Breadcrumb.Root>{" "}
+        {fileContent ? (
+          <Box p={4} bg="gray.100" borderRadius="md">
+            <Text whiteSpace="pre-wrap" fontSize="sm" fontFamily="monospace">
+              {fileContent}
+            </Text>
+          </Box>
+        ) : (
+          <ListRoot>
+            {Array.isArray(fileTree) &&
+              fileTree.map((file) => (
+                <ListItem
+                  key={file.sha}
+                  display="flex"
+                  alignItems="center"
+                  p={2}
+                  borderRadius="md"
+                  _hover={{ bg: "gray.100", cursor: "pointer" }}
+                  onClick={() =>
+                    file.type === "dir"
+                      ? handleFolderClick(file.path)
+                      : handleFileClick(file.path)
+                  }
+                >
+                  <Icon
+                    as={file.type === "dir" ? FiFolder : FiFile}
+                    color={file.type === "dir" ? "blue.500" : "gray.500"}
+                    mr={2}
+                  />
+                  <p>{file.name}</p>
+                  {file.size !== undefined && file.size > 0 && (
+                    <Text fontSize="xs" color="gray.500" ml={2}>
+                      ({file.size} bytes)
+                    </Text>
+                  )}
+                </ListItem>
               ))}
-        </Breadcrumb.List>
-      </Breadcrumb.Root>{" "}
-      {fileContent ? (
-        <Box p={4} bg="gray.100" borderRadius="md">
-          <Text whiteSpace="pre-wrap" fontSize="sm" fontFamily="monospace">
-            {fileContent}
-          </Text>
-        </Box>
-      ) : (
-        <ListRoot>
-          {Array.isArray(fileTree) &&
-            fileTree.map((file) => (
-              <ListItem
-                key={file.sha}
-                display="flex"
-                alignItems="center"
-                p={2}
-                borderRadius="md"
-                _hover={{ bg: "gray.100", cursor: "pointer" }}
-                onClick={() =>
-                  file.type === "dir"
-                    ? handleFolderClick(file.path)
-                    : handleFileClick(file.path)
-                }
-              >
-                <Icon
-                  as={file.type === "dir" ? FiFolder : FiFile}
-                  color={file.type === "dir" ? "blue.500" : "gray.500"}
-                  mr={2}
-                />
-                <p>{file.name}</p>
-                {file.size !== undefined && file.size > 0 && (
-                  <Text fontSize="xs" color="gray.500" ml={2}>
-                    ({file.size} bytes)
-                  </Text>
-                )}
-              </ListItem>
-            ))}
-        </ListRoot>
-      )}
-    </Box>
+          </ListRoot>
+        )}
+      </Box>
+    </>
   );
 };
diff --git a/modules/research-framework/portal/src/components/repositories/index.tsx b/modules/research-framework/portal/src/components/repositories/index.tsx
index 665c6ab724..bf593ad0c2 100644
--- a/modules/research-framework/portal/src/components/repositories/index.tsx
+++ b/modules/research-framework/portal/src/components/repositories/index.tsx
@@ -1,5 +1,4 @@
 import { Container, Input, SimpleGrid } from "@chakra-ui/react";
-import NavBar from "../NavBar";
 import { PageHeader } from "../PageHeader";
 import api from "@/lib/api";
 import { useEffect, useState } from "react";
@@ -40,8 +39,6 @@ const Repositories = () => {
 
   return (
     <>
-      <NavBar />
-
       <Container maxW="container.lg" mt={8}>
         <PageHeader
           title="Repositories"
diff --git a/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx b/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx
index a475cf302d..73aab50862 100644
--- a/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx
+++ b/modules/research-framework/portal/src/components/resources/ResourceDetails.tsx
@@ -1,5 +1,4 @@
 import { useNavigate, useParams } from "react-router";
-import NavBar from "../NavBar";
 import {
   Container,
   Spinner,
@@ -60,7 +59,6 @@ const ResourceDetails = () => {
 
   return (
     <>
-      <NavBar />
       <Container maxW="breakpoint-lg" mx="auto" p={4} mt={16}>
         <Box>
           <Button variant="plain" p={0} onClick={() => navigate(-1)}>
@@ -150,7 +148,7 @@ const ResourceDetails = () => {
           {(resource.type as ResourceTypeEnum) ===
             ResourceTypeEnum.REPOSITORY && (
             <RepositorySpecificDetails
-              dataset={resource as RepositoryResource}
+              repository={resource as RepositoryResource}
             />
           )}
 
diff --git a/modules/research-framework/portal/src/layouts/NavBar.tsx b/modules/research-framework/portal/src/layouts/NavBar.tsx
new file mode 100644
index 0000000000..be483f7f41
--- /dev/null
+++ b/modules/research-framework/portal/src/layouts/NavBar.tsx
@@ -0,0 +1,142 @@
+import {
+  Text,
+  Flex,
+  Spacer,
+  Image,
+  HStack,
+  Avatar,
+  Box,
+  IconButton,
+  useDisclosure,
+  Collapsible,
+  Button,
+  Stack,
+  ButtonProps,
+} from "@chakra-ui/react";
+import ApacheAiravataLogo from "../assets/airavata-logo.png";
+import { Link, useNavigate } from "react-router";
+import { useAuth } from "react-oidc-context";
+import { RxHamburgerMenu } from "react-icons/rx";
+import { IoClose } from "react-icons/io5";
+
+const NAV_CONTENT = [
+  {
+    title: "Projects",
+    url: "/projects",
+  },
+  {
+    title: "Datasets",
+    url: "/resources/datasets",
+  },
+  {
+    title: "Repositories",
+    url: "/resources/repositories",
+  },
+  {
+    title: "Notebooks",
+    url: "/resources/notebooks",
+  },
+  {
+    title: "Models",
+    url: "/resources/models",
+  },
+];
+
+interface NavLinkProps extends ButtonProps {
+  title: string;
+  url: string;
+}
+
+const NavBar = () => {
+  const auth = useAuth();
+  const { open, onToggle } = useDisclosure();
+  const navigate = useNavigate();
+
+  const NavLink = ({ title, url, ...props }: NavLinkProps) => (
+    <Button
+      variant="plain"
+      px={2}
+      _hover={{ bg: "gray.200" }}
+      onClick={() => {
+        navigate(url);
+        onToggle();
+      }}
+      {...props}
+    >
+      <Text color="gray.700" fontSize="md" textAlign="left">
+        {title}
+      </Text>
+    </Button>
+  );
+
+  return (
+    <Box position="sticky" top="0" zIndex="1000" bg="white" boxShadow="sm">
+      <Flex align="center" p={4}>
+        {/* Hamburger Menu (Mobile Only) */}
+        <IconButton
+          aria-label="Toggle Navigation"
+          display={{ base: "inline-flex", md: "none" }}
+          onClick={onToggle}
+          variant="ghost"
+          mr={2}
+        >
+          {open ? <IoClose size={24} /> : <RxHamburgerMenu size={24} />}
+        </IconButton>
+
+        {/* Logo */}
+        <Link to="/projects">
+          <Image src={ApacheAiravataLogo} alt="Logo" boxSize="30px" />
+        </Link>
+
+        {/* Desktop Nav Links */}
+        <HStack ml={4} display={{ base: "none", md: "flex" }}>
+          {NAV_CONTENT.map((item) => (
+            <NavLink key={item.title} title={item.title} url={item.url} />
+          ))}
+        </HStack>
+
+        <Spacer />
+
+        {/* User Profile */}
+        <HStack gap={3}>
+          <Avatar.Root variant="subtle">
+            <Avatar.Fallback name={auth.user?.profile.name} />
+          </Avatar.Root>
+          <Box textAlign="left">
+            <Text fontSize="sm">{auth.user?.profile.name}</Text>
+            <Text fontSize="xs" color="gray.500">
+              {auth.user?.profile.email}
+            </Text>
+          </Box>
+        </HStack>
+      </Flex>
+
+      {/* Mobile Nav Links (Collapse) */}
+      <Collapsible.Root open={open}>
+        <Collapsible.Content>
+          <Stack
+            direction="column"
+            bg="white"
+            px={4}
+            pb={4}
+            spaceY={2}
+            display={{ md: "none" }}
+          >
+            {NAV_CONTENT.map((item) => (
+              <Box key={item.title} w="100%">
+                <NavLink
+                  key={item.title}
+                  title={item.title}
+                  url={item.url}
+                  width="100%"
+                />
+              </Box>
+            ))}
+          </Stack>
+        </Collapsible.Content>
+      </Collapsible.Root>
+    </Box>
+  );
+};
+
+export default NavBar;
diff --git a/modules/research-framework/portal/src/layouts/NavBarFooterLayout.tsx b/modules/research-framework/portal/src/layouts/NavBarFooterLayout.tsx
new file mode 100644
index 0000000000..04d80846a6
--- /dev/null
+++ b/modules/research-framework/portal/src/layouts/NavBarFooterLayout.tsx
@@ -0,0 +1,14 @@
+// layout.tsx
+import NavBar from "@/layouts/NavBar";
+import { Outlet } from "react-router";
+
+const NavBarFooterLayout = () => {
+  return (
+    <>
+      <NavBar />
+      <Outlet />
+    </>
+  );
+};
+
+export default NavBarFooterLayout;


(airavata) 06/06: Cybershuttle.org homepage copy

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dimuthuupe pushed a commit to branch cybershuttle-staging
in repository https://gitbox.apache.org/repos/asf/airavata.git

commit b212d692589ed4273859c47ca755913cebffe52c
Author: ganning127 <ga...@gmail.com>
AuthorDate: Sun Apr 6 23:42:14 2025 -0400

    Cybershuttle.org homepage copy
---
 .../portal/src/assets/AllenLogo.png                | Bin 0 -> 11565 bytes
 .../portal/src/assets/GTLogo.png                   | Bin 0 -> 53417 bytes
 .../portal/src/assets/IUFullLogo.png               | Bin 0 -> 10163 bytes
 .../portal/src/assets/IULogo.png                   | Bin 0 -> 2333 bytes
 .../portal/src/assets/MolecularDynamics.png        | Bin 0 -> 37240 bytes
 .../portal/src/assets/NSFLogo.png                  | Bin 0 -> 42375 bytes
 .../portal/src/assets/NeuroScience.png             | Bin 0 -> 37427 bytes
 .../portal/src/assets/PoweredByApache.png          | Bin 0 -> 12850 bytes
 .../portal/src/assets/UCSDLogo.png                 | Bin 0 -> 52605 bytes
 .../portal/src/assets/UIUCLogo.png                 | Bin 0 -> 3490 bytes
 .../portal/src/assets/XSEDELogo.png                | Bin 0 -> 14094 bytes
 .../portal/src/assets/extensibleIcon.png           | Bin 0 -> 7467 bytes
 .../portal/src/assets/reproIcon.png                | Bin 0 -> 4275 bytes
 .../portal/src/assets/thirdPartyIcon.png           | Bin 0 -> 9044 bytes
 .../portal/src/assets/userClockIcon.png            | Bin 0 -> 6939 bytes
 .../src/components/home/CybershuttleLanding.tsx    | 246 ++++++++++++++++++++-
 .../portal/src/components/resources/index.tsx      |   2 +-
 17 files changed, 244 insertions(+), 4 deletions(-)

diff --git a/modules/research-framework/portal/src/assets/AllenLogo.png b/modules/research-framework/portal/src/assets/AllenLogo.png
new file mode 100644
index 0000000000..a6db788b8b
Binary files /dev/null and b/modules/research-framework/portal/src/assets/AllenLogo.png differ
diff --git a/modules/research-framework/portal/src/assets/GTLogo.png b/modules/research-framework/portal/src/assets/GTLogo.png
new file mode 100644
index 0000000000..5e445f5868
Binary files /dev/null and b/modules/research-framework/portal/src/assets/GTLogo.png differ
diff --git a/modules/research-framework/portal/src/assets/IUFullLogo.png b/modules/research-framework/portal/src/assets/IUFullLogo.png
new file mode 100644
index 0000000000..7e4482b007
Binary files /dev/null and b/modules/research-framework/portal/src/assets/IUFullLogo.png differ
diff --git a/modules/research-framework/portal/src/assets/IULogo.png b/modules/research-framework/portal/src/assets/IULogo.png
new file mode 100644
index 0000000000..9719ad5ea3
Binary files /dev/null and b/modules/research-framework/portal/src/assets/IULogo.png differ
diff --git a/modules/research-framework/portal/src/assets/MolecularDynamics.png b/modules/research-framework/portal/src/assets/MolecularDynamics.png
new file mode 100644
index 0000000000..3eaed8d8c3
Binary files /dev/null and b/modules/research-framework/portal/src/assets/MolecularDynamics.png differ
diff --git a/modules/research-framework/portal/src/assets/NSFLogo.png b/modules/research-framework/portal/src/assets/NSFLogo.png
new file mode 100644
index 0000000000..6d54707ff0
Binary files /dev/null and b/modules/research-framework/portal/src/assets/NSFLogo.png differ
diff --git a/modules/research-framework/portal/src/assets/NeuroScience.png b/modules/research-framework/portal/src/assets/NeuroScience.png
new file mode 100644
index 0000000000..9ed69c80a6
Binary files /dev/null and b/modules/research-framework/portal/src/assets/NeuroScience.png differ
diff --git a/modules/research-framework/portal/src/assets/PoweredByApache.png b/modules/research-framework/portal/src/assets/PoweredByApache.png
new file mode 100644
index 0000000000..f488fcd324
Binary files /dev/null and b/modules/research-framework/portal/src/assets/PoweredByApache.png differ
diff --git a/modules/research-framework/portal/src/assets/UCSDLogo.png b/modules/research-framework/portal/src/assets/UCSDLogo.png
new file mode 100644
index 0000000000..b9108fcbda
Binary files /dev/null and b/modules/research-framework/portal/src/assets/UCSDLogo.png differ
diff --git a/modules/research-framework/portal/src/assets/UIUCLogo.png b/modules/research-framework/portal/src/assets/UIUCLogo.png
new file mode 100644
index 0000000000..d6b1c78625
Binary files /dev/null and b/modules/research-framework/portal/src/assets/UIUCLogo.png differ
diff --git a/modules/research-framework/portal/src/assets/XSEDELogo.png b/modules/research-framework/portal/src/assets/XSEDELogo.png
new file mode 100644
index 0000000000..89c5847c40
Binary files /dev/null and b/modules/research-framework/portal/src/assets/XSEDELogo.png differ
diff --git a/modules/research-framework/portal/src/assets/extensibleIcon.png b/modules/research-framework/portal/src/assets/extensibleIcon.png
new file mode 100644
index 0000000000..4797911c2b
Binary files /dev/null and b/modules/research-framework/portal/src/assets/extensibleIcon.png differ
diff --git a/modules/research-framework/portal/src/assets/reproIcon.png b/modules/research-framework/portal/src/assets/reproIcon.png
new file mode 100644
index 0000000000..27845d5389
Binary files /dev/null and b/modules/research-framework/portal/src/assets/reproIcon.png differ
diff --git a/modules/research-framework/portal/src/assets/thirdPartyIcon.png b/modules/research-framework/portal/src/assets/thirdPartyIcon.png
new file mode 100644
index 0000000000..e88e192178
Binary files /dev/null and b/modules/research-framework/portal/src/assets/thirdPartyIcon.png differ
diff --git a/modules/research-framework/portal/src/assets/userClockIcon.png b/modules/research-framework/portal/src/assets/userClockIcon.png
new file mode 100644
index 0000000000..bebe1056b7
Binary files /dev/null and b/modules/research-framework/portal/src/assets/userClockIcon.png differ
diff --git a/modules/research-framework/portal/src/components/home/CybershuttleLanding.tsx b/modules/research-framework/portal/src/components/home/CybershuttleLanding.tsx
index 0195545767..bd9c750462 100644
--- a/modules/research-framework/portal/src/components/home/CybershuttleLanding.tsx
+++ b/modules/research-framework/portal/src/components/home/CybershuttleLanding.tsx
@@ -1,6 +1,33 @@
-import { Container, Heading, Image, Text, VStack, Box } from "@chakra-ui/react";
+import {
+  Container,
+  Heading,
+  Image,
+  Text,
+  VStack,
+  Box,
+  SimpleGrid,
+  Link,
+  HStack,
+  Separator,
+} from "@chakra-ui/react";
 import HeroOriginal from "../../assets/Hero.original.png";
 import { SectionHeading } from "../typography/SectionHeading";
+import ExtensibleIcon from "../../assets/extensibleIcon.png";
+import ReproIcon from "../../assets/reproIcon.png";
+import ThirdPartyIcon from "../../assets/thirdPartyIcon.png";
+import UserClockIcon from "../../assets/userClockIcon.png";
+import MolecularDyanmics from "../../assets/MolecularDynamics.png";
+import NeuroScience from "../../assets/NeuroScience.png";
+import GeorgiaTech from "../../assets/GTLogo.png";
+import IULogo from "../../assets/IULogo.png";
+import UIUCLogo from "../../assets/UIUCLogo.png";
+import UCSDLogo from "../../assets/UCSDLogo.png";
+import AllenLogo from "../../assets/AllenLogo.png";
+import NSFLogo from "../../assets/NSFLogo.png";
+import { FaCheckCircle } from "react-icons/fa";
+import PoweredByApache from "../../assets/PoweredByApache.png";
+import XSEDELogo from "../../assets/XSEDELogo.png";
+import IUFullLogo from "../../assets/IUFullLogo.png";
 
 export const CybershuttleLanding = () => {
   return (
@@ -38,23 +65,236 @@ export const CybershuttleLanding = () => {
         <VStack mt={18} alignItems="flex-start" gap={16}>
           <Box>
             <SectionHeading>Main Features</SectionHeading>
+
+            <SimpleGrid columns={{ base: 1, md: 2, lg: 4 }} gap={8} mt={2}>
+              <FeatureCard
+                image={UserClockIcon}
+                title="User Productivity"
+                description="Enable individual researchers to provide an individual view into research computing resources at all scales."
+              />
+              <FeatureCard
+                image={ExtensibleIcon}
+                title="Extensibility & Collaborations"
+                description="Instead of learning from scratch, modify an existing experiment and run it through CyberShuttle."
+              />
+              <FeatureCard
+                image={ThirdPartyIcon}
+                title="Third-Party App Integrations"
+                description="Access to synchronized cloud tools to gain deeper insights from the data."
+              />
+              <FeatureCard
+                image={ReproIcon}
+                title="Reproducible Computations"
+                description="Shared associated data and experiments captured in the system generating quick results."
+              />
+            </SimpleGrid>
           </Box>
 
           <Box>
             <SectionHeading>Science-Centrist Extensions</SectionHeading>
+            <Text fontSize="lg" color="gray.500" mt={4}>
+              We create and integrate tailored science extensions to optimize
+              workflows and meet domain-specific requirements through
+              collaboration with experts.
+            </Text>
+
+            <VStack mt={8} gap={4} alignItems="flex-start">
+              <ExtensionCard
+                image={MolecularDyanmics}
+                title="Molecular Dynamics"
+                description="Integrate with molecular dynamics simulations for enhanced analysis."
+                link="https://md.cybershuttle.org/"
+              />
+              <ExtensionCard
+                image={NeuroScience}
+                title="Neuroscience"
+                description="Integrate with neuroscience data analysis tools for advanced research."
+                link="https://neuroscience.cybershuttle.org/"
+              />
+            </VStack>
           </Box>
 
           <Box>
             <SectionHeading>Collaboration</SectionHeading>
+            <Text fontSize="lg" color="gray.500" mt={4}>
+              We collaborate with institutions to enhance research computing
+              capabilities and foster innovation.
+            </Text>
+
+            <SimpleGrid
+              columns={{ base: 3, md: 5 }}
+              gap={8}
+              mt={8}
+              alignItems="flex-start"
+            >
+              <CollaborationCard image={GeorgiaTech} title="Georgia Tech" />
+              <CollaborationCard image={IULogo} title="Indiana University" />
+              <CollaborationCard
+                image={UIUCLogo}
+                title="University of Illinois"
+              />
+              <CollaborationCard
+                image={UCSDLogo}
+                title="University of California San Diego"
+              />
+              <CollaborationCard
+                image={AllenLogo}
+                title="Allen Institute for Brain Sciences"
+              />
+            </SimpleGrid>
           </Box>
 
           <Box>
             <SectionHeading>Thanks to our Sponsors</SectionHeading>
+
+            <SimpleGrid
+              columns={{ base: 1, md: 2 }}
+              gap={8}
+              mt={8}
+              alignItems="center"
+            >
+              <Box>
+                <Text fontSize="xl" mb={4}>
+                  Cybershuttle is supported by the{" "}
+                  <Text as="span" fontWeight="bold">
+                    National Science Foundation (NSF)
+                  </Text>{" "}
+                  under the following grants.
+                </Text>
+
+                <SimpleGrid columns={2} gap={2}>
+                  <GrantBox
+                    title="CSSI-2209874"
+                    href="https://nsf.gov/awardsearch/showAward?AWD_ID=2209872&HistoricalAwards=false"
+                  />
+                  <GrantBox
+                    title="CSSI-2209872"
+                    href="https://nsf.gov/awardsearch/showAward?AWD_ID=2209874&HistoricalAwards=false"
+                  />
+                  <GrantBox
+                    title="CSSI-2209875"
+                    href="https://nsf.gov/awardsearch/showAward?AWD_ID=2209873&HistoricalAwards=false"
+                  />
+                  <GrantBox
+                    title="CSSI-2209873"
+                    href="https://nsf.gov/awardsearch/showAward?AWD_ID=2209875&HistoricalAwards=false"
+                  />
+                </SimpleGrid>
+              </Box>
+              <Image src={NSFLogo} alt="NSF Logo" maxH="180px" mx="auto" />
+            </SimpleGrid>
           </Box>
         </VStack>
-
-        <Box my={8} />
       </Container>
+
+      <Separator my={16} borderColor="red.700" borderWidth="4px" />
+
+      <SimpleGrid
+        columns={{ base: 2, md: 4 }}
+        gap={8}
+        maxW="breakpoint-lg"
+        mx="auto"
+        alignItems="center"
+        justifyContent="center"
+        mb={8}
+      >
+        <Image
+          src={PoweredByApache}
+          alt="Powered by Apache"
+          maxH="180px"
+          mx="auto"
+        />
+        <Image src={XSEDELogo} alt="XSEDE Logo" maxH="180px" mx="auto" />
+        <Image src={NSFLogo} alt="XSEDE Logo" maxH="180px" mx="auto" />
+        <Image
+          src={IUFullLogo}
+          alt="Indiana University Logo"
+          maxH="180px"
+          mx="auto"
+        />
+      </SimpleGrid>
     </>
   );
 };
+
+const FeatureCard = ({
+  image,
+  title,
+  description,
+}: {
+  image: string;
+  title: string;
+  description: string;
+}) => {
+  return (
+    <VStack
+      alignItems="flex-start"
+      gap={0}
+      borderWidth={1}
+      borderRadius="md"
+      padding={4}
+    >
+      <Image src={image} alt={title} maxH="90px" />
+      <Text fontSize="2xl" fontWeight="bold">
+        {title}
+      </Text>
+      <Text>{description}</Text>
+    </VStack>
+  );
+};
+
+const ExtensionCard = ({
+  image,
+  title,
+  description,
+  link,
+}: {
+  image: string;
+  title: string;
+  description: string;
+  link: string;
+}) => {
+  return (
+    <HStack columns={{ base: 1, md: 2 }} gap={4} mt={2} alignItems="center">
+      <Image src={image} alt={title} borderWidth={1} borderRadius="md" />
+
+      <VStack alignItems="flex-start" gap={0}>
+        <Text fontSize="2xl" fontWeight="bold">
+          {title}
+        </Text>
+        <Text>{description}</Text>
+        <Link href={link} target="_blank">
+          <Text color="blue.500" textDecoration="underline" fontWeight="bold">
+            View Details
+          </Text>
+        </Link>
+      </VStack>
+    </HStack>
+  );
+};
+
+const CollaborationCard = ({
+  image,
+  title,
+}: {
+  image: string;
+  title: string;
+}) => {
+  return (
+    <VStack gap={2} borderRadius="md" textAlign="center">
+      <Image src={image} alt={title} maxH="90px" mx="auto" />
+      <Text fontSize="lg">{title}</Text>
+    </VStack>
+  );
+};
+
+const GrantBox = ({ title, href }: { title: string; href: string }) => {
+  return (
+    <HStack>
+      <FaCheckCircle color="green" />
+      <Link href={href} target="_blank">
+        <Text>{title}</Text>
+      </Link>
+    </HStack>
+  );
+};
diff --git a/modules/research-framework/portal/src/components/resources/index.tsx b/modules/research-framework/portal/src/components/resources/index.tsx
index 504ed2899e..9bd58cf05a 100644
--- a/modules/research-framework/portal/src/components/resources/index.tsx
+++ b/modules/research-framework/portal/src/components/resources/index.tsx
@@ -61,7 +61,7 @@ export const Resources = () => {
   const [resources, setResources] = useState<Resource[]>([]);
 
   const handleDelete = (i: number) => {
-    setTags(tags.filter((tag, index) => index !== i));
+    setTags(tags.filter((_, index) => index !== i));
   };
 
   const handleAddition = (tag: Tag) => {


(airavata) 03/06: Support logout

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dimuthuupe pushed a commit to branch cybershuttle-staging
in repository https://gitbox.apache.org/repos/asf/airavata.git

commit ab6eaae418b31884bf055cd45b25c12e89f99a67
Author: ganning127 <ga...@gmail.com>
AuthorDate: Sat Apr 5 02:44:01 2025 -0400

    Support logout
---
 modules/research-framework/portal/src/App.tsx      |  4 +-
 .../portal/src/components/auth/Login.tsx           | 17 ++++++-
 .../src/components/auth/ProtectedComponent.tsx     |  2 +-
 .../portal/src/components/auth/UserMenu.tsx        | 55 ++++++++++++++++++++++
 .../portal/src/layouts/NavBar.tsx                  | 13 +----
 5 files changed, 77 insertions(+), 14 deletions(-)

diff --git a/modules/research-framework/portal/src/App.tsx b/modules/research-framework/portal/src/App.tsx
index ebeb14af3b..23694017ce 100644
--- a/modules/research-framework/portal/src/App.tsx
+++ b/modules/research-framework/portal/src/App.tsx
@@ -63,7 +63,9 @@ function App() {
 
         <Routes>
           {/* Public Route */}
-          <Route path="/" element={<Login />} />
+          <Route element={<NavBarFooterLayout />}>
+            <Route path="/login" element={<Login />} />
+          </Route>
 
           {/* Protected Routes with Layout */}
           <Route
diff --git a/modules/research-framework/portal/src/components/auth/Login.tsx b/modules/research-framework/portal/src/components/auth/Login.tsx
index 545fb3eeff..b338bacb01 100644
--- a/modules/research-framework/portal/src/components/auth/Login.tsx
+++ b/modules/research-framework/portal/src/components/auth/Login.tsx
@@ -1,9 +1,18 @@
 import { Center, Text, Button, Image, VStack, Stack } from "@chakra-ui/react";
 import { useAuth } from "react-oidc-context";
 import AirvataLogo from "../../assets/airavata-logo.png";
+import { useEffect } from "react";
 
 export const Login = () => {
   const auth = useAuth();
+
+  // Automatically login the user if they are authenticated
+  useEffect(() => {
+    if (auth.isAuthenticated && !auth.isLoading) {
+      auth.signinRedirect();
+    }
+  }, [auth]);
+
   return (
     <Center height="100vh">
       <Stack
@@ -28,7 +37,13 @@ export const Login = () => {
             w="300px"
             onClick={() => {
               console.log("Sign in clicked");
-              auth.signinRedirect();
+              auth.signinRedirect({
+                extraQueryParams: {
+                  // This is the prompt that will be shown to the user
+                  prompt: "login",
+                  kc_idp_hint: "oidc",
+                },
+              });
             }}
           >
             Institution Login
diff --git a/modules/research-framework/portal/src/components/auth/ProtectedComponent.tsx b/modules/research-framework/portal/src/components/auth/ProtectedComponent.tsx
index e4b9160992..ecbf1feb66 100644
--- a/modules/research-framework/portal/src/components/auth/ProtectedComponent.tsx
+++ b/modules/research-framework/portal/src/components/auth/ProtectedComponent.tsx
@@ -10,7 +10,7 @@ function ProtectedComponent({ Component }: { Component: React.FC }) {
   }
 
   if (!auth.isAuthenticated) {
-    navigate("/");
+    navigate("/login");
     return;
   }
 
diff --git a/modules/research-framework/portal/src/components/auth/UserMenu.tsx b/modules/research-framework/portal/src/components/auth/UserMenu.tsx
new file mode 100644
index 0000000000..b929659908
--- /dev/null
+++ b/modules/research-framework/portal/src/components/auth/UserMenu.tsx
@@ -0,0 +1,55 @@
+import { Avatar, Box, HStack, Text, Menu, Portal } from "@chakra-ui/react";
+import { useAuth } from "react-oidc-context";
+
+export const UserMenu = () => {
+  const auth = useAuth();
+
+  if (auth.isLoading || !auth.user) return null;
+
+  const handleLogout = async () => {
+    await auth.signoutRedirect();
+  };
+
+  return (
+    <Menu.Root>
+      <Menu.Trigger asChild>
+        <HStack
+          gap={3}
+          p={1}
+          rounded="md"
+          as="button"
+          cursor="pointer"
+          transition="all .2s"
+          _hover={{ bg: "gray.200" }}
+        >
+          <Avatar.Root variant="subtle">
+            <Avatar.Fallback name={auth.user?.profile.name} />
+          </Avatar.Root>
+          <Box textAlign="left">
+            <Text fontSize="sm">{auth.user?.profile.name}</Text>
+            <Text fontSize="xs" color="gray.500">
+              {auth.user?.profile.email}
+            </Text>
+          </Box>
+        </HStack>
+      </Menu.Trigger>
+
+      <Portal>
+        <Menu.Positioner>
+          <Menu.Content>
+            <Menu.Item
+              value="logout"
+              onClick={handleLogout}
+              _hover={{
+                cursor: "pointer",
+              }}
+            >
+              Logout
+            </Menu.Item>
+          </Menu.Content>
+          <Menu.Arrow />
+        </Menu.Positioner>
+      </Portal>
+    </Menu.Root>
+  );
+};
diff --git a/modules/research-framework/portal/src/layouts/NavBar.tsx b/modules/research-framework/portal/src/layouts/NavBar.tsx
index be483f7f41..43090c1841 100644
--- a/modules/research-framework/portal/src/layouts/NavBar.tsx
+++ b/modules/research-framework/portal/src/layouts/NavBar.tsx
@@ -18,6 +18,7 @@ import { Link, useNavigate } from "react-router";
 import { useAuth } from "react-oidc-context";
 import { RxHamburgerMenu } from "react-icons/rx";
 import { IoClose } from "react-icons/io5";
+import { UserMenu } from "@/components/auth/UserMenu";
 
 const NAV_CONTENT = [
   {
@@ -98,17 +99,7 @@ const NavBar = () => {
         <Spacer />
 
         {/* User Profile */}
-        <HStack gap={3}>
-          <Avatar.Root variant="subtle">
-            <Avatar.Fallback name={auth.user?.profile.name} />
-          </Avatar.Root>
-          <Box textAlign="left">
-            <Text fontSize="sm">{auth.user?.profile.name}</Text>
-            <Text fontSize="xs" color="gray.500">
-              {auth.user?.profile.email}
-            </Text>
-          </Box>
-        </HStack>
+        <UserMenu />
       </Flex>
 
       {/* Mobile Nav Links (Collapse) */}


(airavata) 02/06: Add auth to swagger

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dimuthuupe pushed a commit to branch cybershuttle-staging
in repository https://gitbox.apache.org/repos/asf/airavata.git

commit 6ea5fe13291aac0e6bb51eb8d972329938d72b14
Author: ganning127 <ga...@gmail.com>
AuthorDate: Fri Apr 4 14:57:00 2025 -0400

    Add auth to swagger
---
 .../research/service/config/AuthzTokenFilter.java  | 11 +++++
 .../research/service/config/OpenApiConfig.java     | 54 ++++++++++++++++++++++
 .../service/controller/ResourceController.java     |  3 ++
 .../src/main/resources/application.yml             |  9 ++++
 4 files changed, 77 insertions(+)

diff --git a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/AuthzTokenFilter.java b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/AuthzTokenFilter.java
index 82c3b4e9ba..d31cc28c37 100644
--- a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/AuthzTokenFilter.java
+++ b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/AuthzTokenFilter.java
@@ -53,6 +53,17 @@ public class AuthzTokenFilter extends OncePerRequestFilter {
         this.airavataService = airavataService;
     }
 
+    @Override
+    protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
+        String path = request.getRequestURI();
+        return path.startsWith("/swagger") ||
+                path.startsWith("/v2/api-docs") ||
+                path.startsWith("/v3/api-docs") ||
+                path.startsWith("/swagger-ui") ||
+                path.startsWith("/swagger-resources") ||
+                path.startsWith("/webjars/");
+    }
+
     @Override
     protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
 
diff --git a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/OpenApiConfig.java b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/OpenApiConfig.java
new file mode 100644
index 0000000000..0cd5392040
--- /dev/null
+++ b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/OpenApiConfig.java
@@ -0,0 +1,54 @@
+package org.apache.airavata.research.service.config;
+
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import io.swagger.v3.oas.models.media.StringSchema;
+import io.swagger.v3.oas.models.parameters.Parameter;
+import io.swagger.v3.oas.models.security.*;
+import io.swagger.v3.oas.models.servers.Server;
+import org.springdoc.core.models.GroupedOpenApi;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+import java.util.List;
+
+@Configuration
+public class OpenApiConfig {
+
+    @Value("${airavata.openid.url}")
+    private String openIdConfigURL;
+
+    @Bean
+    public GroupedOpenApi publicApi() {
+        return GroupedOpenApi.builder()
+                .group("public")
+                .pathsToMatch("/api/**")
+                .build();
+    }
+
+    @Bean
+    public OpenAPI researchServiceOpenAPI() {
+        return new OpenAPI()
+                .info(new Info()
+                        .title("Research Service API")
+                        .version("1.0.0"))
+                .addSecurityItem(new SecurityRequirement().addList("oauth2-pkce"))
+                .components(new io.swagger.v3.oas.models.Components()
+                        .addSecuritySchemes("oauth2-pkce",
+                                new SecurityScheme()
+                                        .type(SecurityScheme.Type.OAUTH2)
+                                        .flows(new OAuthFlows()
+                                                .authorizationCode(new OAuthFlow()
+                                                        .authorizationUrl("https://auth.dev.cybershuttle.org/realms/default/protocol/openid-connect/auth")
+                                                        .tokenUrl("https://auth.dev.cybershuttle.org/realms/default/protocol/openid-connect/token")
+                                                        .scopes(new Scopes()
+                                                                .addString("openid", "openid")
+                                                                .addString("email", "email"))))));
+
+
+    }
+
+}
diff --git a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java
index d61c4e198e..9f4228b554 100644
--- a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java
+++ b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java
@@ -18,9 +18,11 @@
  */
 package org.apache.airavata.research.service.controller;
 
+import io.swagger.v3.oas.annotations.enums.ParameterIn;
 import org.apache.airavata.research.service.ResponseTypes.ResourceResponse;
 import org.apache.airavata.research.service.enums.ResourceTypeEnum;
 import org.apache.airavata.research.service.model.entity.*;
+import org.junit.runner.Request;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
 import org.springframework.data.domain.Page;
@@ -84,6 +86,7 @@ public class ResourceController {
     )
     @GetMapping("/")
     public ResponseEntity<Page<Resource>> getAllResources(
+            @RequestHeader(name="X-Claims", required=true) String claims,
             @RequestParam(value="pageNumber", defaultValue = "0") int pageNumber,
             @RequestParam(value="pageSize", defaultValue = "10") int pageSize,
             @RequestParam(value="type") ResourceTypeEnum[] types
diff --git a/modules/research-framework/research-service/src/main/resources/application.yml b/modules/research-framework/research-service/src/main/resources/application.yml
index 42b99ebf12..ac21eb7ba2 100644
--- a/modules/research-framework/research-service/src/main/resources/application.yml
+++ b/modules/research-framework/research-service/src/main/resources/application.yml
@@ -13,6 +13,9 @@ airavata:
   research-portal:
     url: http://localhost:5173
 
+  openid:
+    url: "https://auth.dev.cybershuttle.org/realms/default/.well-known/openid-configuration"
+
   user-profile:
     server:
       url: api.dev.cybershuttle.org
@@ -44,3 +47,9 @@ springdoc:
     operationsSorter: alpha
     tagsSorter: alpha
     doc-expansion: none
+    oauth:
+      use-pkce-with-authorization-code-grant: true
+      client-id: data-catalog-portal
+
+
+


(airavata) 05/06: Condense to single resource page + allow unauthenticated routes

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dimuthuupe pushed a commit to branch cybershuttle-staging
in repository https://gitbox.apache.org/repos/asf/airavata.git

commit 8d7e4c439b051b8514202117299f884a9a5a613b
Author: ganning127 <ga...@gmail.com>
AuthorDate: Sun Apr 6 22:46:55 2025 -0400

    Condense to single resource page + allow unauthenticated routes
---
 .../research-framework/portal/package-lock.json    |  92 ++++++++-
 modules/research-framework/portal/package.json     |   4 +-
 modules/research-framework/portal/src/App.tsx      |  16 +-
 .../src/components/auth/ProtectedComponent.tsx     |  10 +-
 .../portal/src/components/auth/UserMenu.tsx        |   1 +
 .../portal/src/components/datasets/DatasetCard.tsx |  70 -------
 .../src/components/datasets/DatasetDetails.tsx     |  54 -----
 .../portal/src/components/home/ResourceCard.tsx    |  18 +-
 .../portal/src/components/notebooks/index.tsx      |  16 +-
 .../portal/src/components/resources/TagInput.css   | 103 ++++++++++
 .../portal/src/components/resources/index.tsx      | 226 +++++++++++++++++++++
 .../portal/src/layouts/NavBar.tsx                  |  38 ++--
 .../research-framework/research-service/pom.xml    |   4 +-
 .../research/service/config/AuthzTokenFilter.java  |   4 +-
 .../service/config/DevDataInitializer.java         |  29 ++-
 .../service/controller/ResourceController.java     |  14 +-
 .../research/service/handlers/ResourceHandler.java |  12 +-
 .../service/model/repo/ResourceRepository.java     |  16 ++
 18 files changed, 545 insertions(+), 182 deletions(-)

diff --git a/modules/research-framework/portal/package-lock.json b/modules/research-framework/portal/package-lock.json
index 1184b19f57..27f3a8f938 100644
--- a/modules/research-framework/portal/package-lock.json
+++ b/modules/research-framework/portal/package-lock.json
@@ -960,6 +960,21 @@
       "dev": true,
       "optional": true
     },
+    "@react-dnd/asap": {
+      "version": "5.0.2",
+      "resolved": "https://registry.npmjs.org/@react-dnd/asap/-/asap-5.0.2.tgz",
+      "integrity": "sha512-WLyfoHvxhs0V9U+GTsGilGgf2QsPl6ZZ44fnv0/b8T3nQyvzxidxsg/ZltbWssbsRDlYW8UKSQMTGotuTotZ6A=="
+    },
+    "@react-dnd/invariant": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/@react-dnd/invariant/-/invariant-4.0.2.tgz",
+      "integrity": "sha512-xKCTqAK/FFauOM9Ta2pswIyT3D8AQlfrYdOi/toTPEhqCuAs1v5tcJ3Y08Izh1cJ5Jchwy9SeAXmMg6zrKs2iw=="
+    },
+    "@react-dnd/shallowequal": {
+      "version": "4.0.2",
+      "resolved": "https://registry.npmjs.org/@react-dnd/shallowequal/-/shallowequal-4.0.2.tgz",
+      "integrity": "sha512-/RVXdLvJxLg4QKvMoM5WlwNR9ViO9z8B/qPcc+C0Sa/teJY7QG7kJ441DwzOjMYEY7GmU4dj5EcGHIkKZiQZCA=="
+    },
     "@rollup/rollup-android-arm-eabi": {
       "version": "4.35.0",
       "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.35.0.tgz",
@@ -1180,6 +1195,19 @@
       "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
       "dev": true
     },
+    "@types/lodash": {
+      "version": "4.17.16",
+      "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.16.tgz",
+      "integrity": "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g=="
+    },
+    "@types/lodash-es": {
+      "version": "4.17.12",
+      "resolved": "https://registry.npmjs.org/@types/lodash-es/-/lodash-es-4.17.12.tgz",
+      "integrity": "sha512-0NgftHUcV4v34VhXm8QBSftKVXtbkBG3ViCjs6+eJ5a6y6Mi/jiFGPc1sC7QK+9BFhWrURE3EOggmWaSxL9OzQ==",
+      "requires": {
+        "@types/lodash": "*"
+      }
+    },
     "@types/ms": {
       "version": "2.1.0",
       "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz",
@@ -2450,6 +2478,11 @@
         }
       }
     },
+    "classnames": {
+      "version": "2.3.3",
+      "resolved": "https://registry.npmjs.org/classnames/-/classnames-2.3.3.tgz",
+      "integrity": "sha512-1inzZmicIFcmUya7PGtUQeXtcF7zZpPnxtQoYOrz0uiOBGlLFa4ik4361seYL2JCcRDIyfdFHiwQolESFlw+Og=="
+    },
     "cli-table": {
       "version": "0.3.11",
       "resolved": "https://registry.npmjs.org/cli-table/-/cli-table-0.3.11.tgz",
@@ -2564,6 +2597,16 @@
       "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
       "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ=="
     },
+    "dnd-core": {
+      "version": "16.0.1",
+      "resolved": "https://registry.npmjs.org/dnd-core/-/dnd-core-16.0.1.tgz",
+      "integrity": "sha512-HK294sl7tbw6F6IeuK16YSBUoorvHpY8RHO+9yFfaJyCDVb6n7PRcezrOEOa2SBCqiYpemh5Jx20ZcjKdFAVng==",
+      "requires": {
+        "@react-dnd/asap": "^5.0.1",
+        "@react-dnd/invariant": "^4.0.1",
+        "redux": "^4.2.0"
+      }
+    },
     "dunder-proto": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz",
@@ -2788,8 +2831,7 @@
     "fast-deep-equal": {
       "version": "3.1.3",
       "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
-      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
-      "dev": true
+      "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
     },
     "fast-glob": {
       "version": "3.3.3",
@@ -3294,6 +3336,11 @@
         "p-locate": "^5.0.0"
       }
     },
+    "lodash-es": {
+      "version": "4.17.21",
+      "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz",
+      "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw=="
+    },
     "lodash.merge": {
       "version": "4.6.2",
       "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
@@ -3651,6 +3698,26 @@
       "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz",
       "integrity": "sha512-V8AVnmPIICiWpGfm6GLzCR/W5FXLchHop40W4nXBmdlEceh16rCN8O8LNWm5bh5XUX91fh7KpA+W0TgMKmgTpQ=="
     },
+    "react-dnd": {
+      "version": "16.0.1",
+      "resolved": "https://registry.npmjs.org/react-dnd/-/react-dnd-16.0.1.tgz",
+      "integrity": "sha512-QeoM/i73HHu2XF9aKksIUuamHPDvRglEwdHL4jsp784BgUuWcg6mzfxT0QDdQz8Wj0qyRKx2eMg8iZtWvU4E2Q==",
+      "requires": {
+        "@react-dnd/invariant": "^4.0.1",
+        "@react-dnd/shallowequal": "^4.0.1",
+        "dnd-core": "^16.0.1",
+        "fast-deep-equal": "^3.1.3",
+        "hoist-non-react-statics": "^3.3.2"
+      }
+    },
+    "react-dnd-html5-backend": {
+      "version": "16.0.1",
+      "resolved": "https://registry.npmjs.org/react-dnd-html5-backend/-/react-dnd-html5-backend-16.0.1.tgz",
+      "integrity": "sha512-Wu3dw5aDJmOGw8WjH1I1/yTH+vlXEL4vmjk5p+MHxP8HuHJS1lAGeIdG/hze1AvNeXWo/JgULV87LyQOr+r5jw==",
+      "requires": {
+        "dnd-core": "^16.0.1"
+      }
+    },
     "react-dom": {
       "version": "19.0.0",
       "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.0.0.tgz",
@@ -3691,10 +3758,15 @@
         "turbo-stream": "2.4.0"
       }
     },
-    "react-tag-input-component": {
-      "version": "2.0.2",
-      "resolved": "https://registry.npmjs.org/react-tag-input-component/-/react-tag-input-component-2.0.2.tgz",
-      "integrity": "sha512-dydI9luVwwv9vrjE5u1TTnkcOVkOVL6mhFti8r6hLi78V2F2EKWQOLptURz79UYbDHLSk6tnbvGl8FE+sMpADg=="
+    "react-tag-input": {
+      "version": "6.10.6",
+      "resolved": "https://registry.npmjs.org/react-tag-input/-/react-tag-input-6.10.6.tgz",
+      "integrity": "sha512-oXopRSs2ZSuMqsygcZhjal869MZpAUX0YVNQ/WGaYXPJmbuZIviqeTocs3g/BHs9ReIG9vu563JOZsHGikiMgw==",
+      "requires": {
+        "@types/lodash-es": "^4.17.12",
+        "classnames": "~2.3.1",
+        "lodash-es": "^4.17.21"
+      }
     },
     "readdirp": {
       "version": "3.6.0",
@@ -3705,6 +3777,14 @@
         "picomatch": "^2.2.1"
       }
     },
+    "redux": {
+      "version": "4.2.1",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
+      "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
+      "requires": {
+        "@babel/runtime": "^7.9.2"
+      }
+    },
     "regenerator-runtime": {
       "version": "0.14.1",
       "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.14.1.tgz",
diff --git a/modules/research-framework/portal/package.json b/modules/research-framework/portal/package.json
index da96f656f9..65121a7cf7 100644
--- a/modules/research-framework/portal/package.json
+++ b/modules/research-framework/portal/package.json
@@ -19,11 +19,13 @@
     "next-themes": "^0.4.6",
     "oidc-client-ts": "^3.2.0",
     "react": "^19.0.0",
+    "react-dnd": "^16.0.1",
+    "react-dnd-html5-backend": "^16.0.1",
     "react-dom": "^19.0.0",
     "react-icons": "^5.5.0",
     "react-oidc-context": "^3.3.0",
     "react-router": "^7.3.0",
-    "react-tag-input-component": "^2.0.2"
+    "react-tag-input": "^6.10.6"
   },
   "devDependencies": {
     "@chakra-ui/cli": "^3.12.0",
diff --git a/modules/research-framework/portal/src/App.tsx b/modules/research-framework/portal/src/App.tsx
index 32b403aacc..c59ac8ad91 100644
--- a/modules/research-framework/portal/src/App.tsx
+++ b/modules/research-framework/portal/src/App.tsx
@@ -19,6 +19,7 @@ import {
   OPENID_CONFIG_URL,
 } from "./lib/constants";
 import { WebStorageStateStore } from "oidc-client-ts";
+import { Resources } from "./components/resources";
 function App() {
   const colorMode = useColorMode();
   const navigate = useNavigate();
@@ -73,10 +74,6 @@ function App() {
       <AuthProvider
         {...oidcConfig}
         onSigninCallback={() => {
-          // const from = location.state?.from || "/"; // fallback to homepage
-          // console.log("Redirecting to:", from);
-          // navigate(from, { replace: true });
-          // clear state from url
           navigate(location.pathname, { replace: true });
         }}
       >
@@ -85,6 +82,12 @@ function App() {
           <Route element={<NavBarFooterLayout />}>
             <Route path="/" element={<CybershuttleLanding />} />
             <Route path="/login" element={<Login />} />
+            <Route path="/resources" element={<Resources />} />
+            <Route path="/resources/datasets" element={<Datasets />} />
+            <Route path="/resources/notebooks" element={<Notebooks />} />
+            <Route path="/resources/repositories" element={<Repositories />} />
+            <Route path="/resources/models" element={<Models />} />
+            <Route path="/resources/:type/:id" element={<ResourceDetails />} />
           </Route>
 
           {/* Protected Routes with Layout */}
@@ -92,11 +95,6 @@ function App() {
             element={<ProtectedComponent Component={NavBarFooterLayout} />}
           >
             <Route path="/projects" element={<Home />} />
-            <Route path="/resources/notebooks" element={<Notebooks />} />
-            <Route path="/resources/datasets" element={<Datasets />} />
-            <Route path="/resources/repositories" element={<Repositories />} />
-            <Route path="/resources/models" element={<Models />} />
-            <Route path="/resources/:type/:id" element={<ResourceDetails />} />
           </Route>
         </Routes>
       </AuthProvider>
diff --git a/modules/research-framework/portal/src/components/auth/ProtectedComponent.tsx b/modules/research-framework/portal/src/components/auth/ProtectedComponent.tsx
index d9a27a7c44..ed2685ab9f 100644
--- a/modules/research-framework/portal/src/components/auth/ProtectedComponent.tsx
+++ b/modules/research-framework/portal/src/components/auth/ProtectedComponent.tsx
@@ -1,25 +1,25 @@
 import { setUserProvider } from "@/lib/api";
-import { useEffect } from "react";
+import { useEffect, useRef } from "react";
 import { useAuth } from "react-oidc-context";
 import { useNavigate } from "react-router";
 
 function ProtectedComponent({ Component }: { Component: React.FC }) {
   const auth = useAuth();
   const navigate = useNavigate();
-  const path = window.location.pathname;
+  const initialPathRef = useRef(window.location.pathname); // store once
 
   useEffect(() => {
     if (!auth.isLoading && !auth.isAuthenticated) {
-      navigate(`/login?redirect=${path}`, { replace: true });
+      navigate(`/login?redirect=${initialPathRef.current}`, { replace: true });
     }
 
     if (auth.isAuthenticated) {
       setUserProvider(() => Promise.resolve(auth.user ?? null));
     }
-  }, [auth]);
+  }, [auth, navigate]);
 
   if (auth.isLoading || !auth.isAuthenticated) {
-    return;
+    return null;
   }
 
   return <Component />;
diff --git a/modules/research-framework/portal/src/components/auth/UserMenu.tsx b/modules/research-framework/portal/src/components/auth/UserMenu.tsx
index cba709720f..a92ff98eac 100644
--- a/modules/research-framework/portal/src/components/auth/UserMenu.tsx
+++ b/modules/research-framework/portal/src/components/auth/UserMenu.tsx
@@ -24,6 +24,7 @@ export const UserMenu = () => {
       </Link>
     );
   const handleLogout = async () => {
+    // Clear the user provider
     await auth.signoutRedirect();
   };
 
diff --git a/modules/research-framework/portal/src/components/datasets/DatasetCard.tsx b/modules/research-framework/portal/src/components/datasets/DatasetCard.tsx
deleted file mode 100644
index 95d3a84ddf..0000000000
--- a/modules/research-framework/portal/src/components/datasets/DatasetCard.tsx
+++ /dev/null
@@ -1,70 +0,0 @@
-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
deleted file mode 100644
index 975639fcd6..0000000000
--- a/modules/research-framework/portal/src/components/datasets/DatasetDetails.tsx
+++ /dev/null
@@ -1,54 +0,0 @@
-import { Metadata } from "../Metadata";
-import NavBar from "../../layouts/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/home/ResourceCard.tsx b/modules/research-framework/portal/src/components/home/ResourceCard.tsx
index a5da829aac..71f937cfdd 100644
--- a/modules/research-framework/portal/src/components/home/ResourceCard.tsx
+++ b/modules/research-framework/portal/src/components/home/ResourceCard.tsx
@@ -15,15 +15,29 @@ import { ResourceTypeBadge } from "../resources/ResourceTypeBadge";
 import { ResourceTypeEnum } from "@/interfaces/ResourceTypeEnum";
 import { ModelCardButton } from "../models/ModelCardButton";
 
-export const ResourceCard = ({ resource }: { resource: Resource }) => {
+export const ResourceCard = ({
+  resource,
+  appendTypeToUrl = false,
+}: {
+  resource: Resource;
+  appendTypeToUrl?: boolean;
+}) => {
   const author = resource.authors[0];
 
   const isValidImage = isValidImaage(resource.headerImage);
 
+  resource.tags.sort((a, b) => a.value.localeCompare(b.value));
+
+  const linkTo = resource.id;
+  const linkToWithType = `${resource.type}/${resource.id}`;
+
+  // Determine the link based on appendTypeToUrl
+  const link = appendTypeToUrl ? linkToWithType : linkTo;
+
   return (
     <Box>
       <Card.Root overflow="hidden" size="md">
-        <Link to={`${resource.id}`}>
+        <Link to={link}>
           {/* Image Container with Badge */}
           {isValidImage && (
             <Box position="relative" width="full">
diff --git a/modules/research-framework/portal/src/components/notebooks/index.tsx b/modules/research-framework/portal/src/components/notebooks/index.tsx
index ac0dd44507..cb241577ea 100644
--- a/modules/research-framework/portal/src/components/notebooks/index.tsx
+++ b/modules/research-framework/portal/src/components/notebooks/index.tsx
@@ -47,13 +47,7 @@ const Notebooks = () => {
         <InputGroup endElement={<LuSearch />} w="100%" mt={4}>
           <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}
@@ -61,7 +55,13 @@ const Notebooks = () => {
           justifyContent="space-around"
         >
           {notebooks.map((notebook: NotebookResource) => {
-            return <ResourceCard resource={notebook} key={notebook.id} />;
+            return (
+              <ResourceCard
+                resource={notebook}
+                key={notebook.id}
+                appendTypeToUrl={false}
+              />
+            );
           })}
         </SimpleGrid>
       </Container>
diff --git a/modules/research-framework/portal/src/components/resources/TagInput.css b/modules/research-framework/portal/src/components/resources/TagInput.css
new file mode 100644
index 0000000000..9caa43e09c
--- /dev/null
+++ b/modules/research-framework/portal/src/components/resources/TagInput.css
@@ -0,0 +1,103 @@
+/* TagInput.css */
+
+.tag-input-wrapper {
+  padding: 1rem;
+  border-radius: 12px;
+  box-shadow: 0 4px 16px rgba(0, 0, 0, 0.05);
+}
+
+/* container holding all tags + input */
+.ReactTags__tags {
+  position: relative;
+  /* Add this line */
+  display: flex;
+  flex-wrap: wrap;
+  gap: 8px;
+  border: 2px solid #e0e0e0;
+  border-radius: 10px;
+  padding: 10px;
+  background-color: #fafafa;
+  transition: border-color 0.2s ease-in-out;
+}
+
+/* tag pill style */
+.ReactTags__tag {
+  background-color: #007bff;
+  color: #fff;
+  padding: 6px 12px;
+  border-radius: 20px;
+  font-size: 14px;
+  display: flex;
+  align-items: center;
+  gap: 6px;
+  white-space: nowrap;
+}
+
+/* remove (x) icon */
+.ReactTags__remove {
+  margin-left: 4px;
+  cursor: pointer;
+  font-weight: bold;
+  color: #fff;
+}
+
+/* input field container (flexed) */
+.ReactTags__tagInput {
+  flex: 1 1 auto;
+  /* allow it to grow and wrap */
+  min-width: 120px;
+}
+
+.ReactTags__selected {
+  width: 100%;
+  display: flex;
+  gap: 0.5rem;
+}
+
+/* actual input element */
+.ReactTags__tagInputField {
+  width: 100%;
+  border: none;
+  outline: none;
+  font-size: 14px;
+  background: transparent;
+  padding: 6px;
+  box-sizing: border-box;
+}
+
+.ReactTags__tagInputField::placeholder {
+  color: #888;
+}
+
+/* Make suggestions dropdown absolutely positioned */
+.ReactTags__suggestions {
+  position: absolute;
+  top: 100%;
+  /* Push below the input */
+  left: 0;
+  width: 100%;
+  /* Match parent width */
+  z-index: 10;
+  background-color: #fff;
+  border: 1px solid #ccc;
+  border-radius: 4px;
+  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
+  max-height: 200px;
+  overflow-y: auto;
+  list-style: none;
+  margin-top: 4px;
+  padding: 0;
+}
+
+/* Each individual suggestion */
+.ReactTags__suggestions li {
+  padding: 8px 12px;
+  cursor: pointer;
+  transition: background-color 0.2s ease;
+}
+
+/* Active (highlighted) suggestion */
+.ReactTags__activeSuggestion {
+  background-color: #007bff;
+  color: white;
+}
\ No newline at end of file
diff --git a/modules/research-framework/portal/src/components/resources/index.tsx b/modules/research-framework/portal/src/components/resources/index.tsx
new file mode 100644
index 0000000000..504ed2899e
--- /dev/null
+++ b/modules/research-framework/portal/src/components/resources/index.tsx
@@ -0,0 +1,226 @@
+import {
+  Text,
+  Container,
+  Heading,
+  Box,
+  SimpleGrid,
+  Button,
+  HStack,
+  Code,
+} from "@chakra-ui/react";
+import { useEffect, useState } from "react";
+import { SEPARATORS, WithContext as ReactTags, Tag } from "react-tag-input";
+import "./TagInput.css"; // 👈 custom styles
+import api from "@/lib/api";
+import { CONTROLLER } from "@/lib/controller";
+import { ResourceTypeEnum } from "@/interfaces/ResourceTypeEnum";
+import { Resource } from "@/interfaces/ResourceType";
+import { ResourceCard } from "../home/ResourceCard";
+import { FaCheck } from "react-icons/fa";
+import { Tag as TagEntity } from "@/interfaces/TagType";
+
+const getResources = async (
+  types: ResourceTypeEnum[],
+  stringTagsArr: string[]
+) => {
+  try {
+    const response = await api.get(`${CONTROLLER.resources}/`, {
+      params: {
+        type: types.join(","),
+        tag: stringTagsArr.join(","),
+        pageNumber: 0,
+        pageSize: 100,
+      },
+    });
+    const data = response.data;
+    return data;
+  } catch (error) {
+    console.error("Error fetching:", error);
+  }
+};
+
+const getTags = async () => {
+  try {
+    const response = await api.get(`${CONTROLLER.resources}/tags/all`);
+    const data = response.data;
+    return data;
+  } catch (error) {
+    console.error("Error fetching:", error);
+  }
+};
+
+export const Resources = () => {
+  const [tags, setTags] = useState<Tag[]>([]);
+  const [suggestions, setSuggestions] = useState<Tag[]>([]);
+  const [resourceTypes, setResourceTypes] = useState<ResourceTypeEnum[]>([
+    ResourceTypeEnum.DATASET,
+    ResourceTypeEnum.REPOSITORY,
+    ResourceTypeEnum.NOTEBOOK,
+    ResourceTypeEnum.MODEL,
+  ]);
+  const [resources, setResources] = useState<Resource[]>([]);
+
+  const handleDelete = (i: number) => {
+    setTags(tags.filter((tag, index) => index !== i));
+  };
+
+  const handleAddition = (tag: Tag) => {
+    console.log(tag);
+    setTags([...tags, tag]);
+  };
+
+  useEffect(() => {
+    async function fetchResources() {
+      const stringTagsArr = tags.map((tag) => tag.text);
+      const resources = await getResources(resourceTypes, stringTagsArr);
+      setResources(resources.content);
+    }
+
+    fetchResources();
+  }, [resourceTypes, tags]);
+
+  useEffect(() => {
+    async function fetchTags() {
+      const tags: TagEntity[] = await getTags();
+      const suggestedTags = tags.map((tag: TagEntity) => ({
+        id: tag.value,
+        text: tag.value,
+        className: "",
+      }));
+
+      setSuggestions(suggestedTags);
+    }
+
+    fetchTags();
+  }, []);
+
+  return (
+    <>
+      <Container maxW="container.lg" mt={8}>
+        <Heading
+          textAlign="center"
+          fontSize={{ base: "4xl", md: "5xl" }}
+          fontWeight="black"
+          lineHeight={1.2}
+        >
+          Browse datasets, repositories, notebooks, and models,
+          <Text as="span" color="blue.600">
+            {" "}
+            made by scientists, for scientists
+          </Text>
+          .
+        </Heading>
+
+        <Box mt={4} maxWidth="1000px" mx="auto">
+          <ReactTags
+            tags={tags}
+            handleDelete={handleDelete}
+            handleAddition={handleAddition}
+            suggestions={suggestions}
+            separators={[
+              SEPARATORS.TAB,
+              SEPARATORS.COMMA,
+              SEPARATORS.ENTER,
+              SEPARATORS.SEMICOLON,
+            ]}
+            allowDragDrop={true}
+            placeholder="Filter resources by tags"
+            renderSuggestion={(item) => {
+              return <span>{item.text}</span>;
+            }}
+          />
+
+          <HStack alignItems="center" mt={2}>
+            <Text fontSize="sm" color="gray.500" fontWeight="bold">
+              Showing
+            </Text>
+            <HStack wrap="wrap">
+              {Object.values(ResourceTypeEnum).map((type) => {
+                const isSelected = resourceTypes.includes(type);
+                return (
+                  <Button
+                    key={type}
+                    variant="outline"
+                    color={isSelected ? "blue.600" : "black"}
+                    bg={isSelected ? "blue.100" : "white"}
+                    _hover={{
+                      bg: isSelected ? "blue.200" : "gray.100",
+                      color: isSelected ? "blue.700" : "black",
+                    }}
+                    size="sm"
+                    onClick={() => {
+                      if (isSelected) {
+                        setResourceTypes(
+                          resourceTypes.filter((t) => t !== type)
+                        );
+                      } else {
+                        setResourceTypes([...resourceTypes, type]);
+                      }
+                    }}
+                  >
+                    {type}
+                    {isSelected && <FaCheck color="blue" />}
+                  </Button>
+                );
+              })}
+            </HStack>
+          </HStack>
+        </Box>
+
+        <SimpleGrid
+          columns={{ base: 1, md: 2, lg: 3 }}
+          mt={4}
+          gap={4}
+          justifyContent="space-around"
+        >
+          {resources.map((resource: Resource) => {
+            return (
+              <ResourceCard
+                resource={resource}
+                key={resource.id}
+                appendTypeToUrl={true}
+              />
+            );
+          })}
+        </SimpleGrid>
+
+        {resources.length === 0 && (
+          <Box textAlign="center" color="gray.500">
+            <Text textAlign="center" mt={8} mb={4}>
+              No resources found with the following criteria:
+            </Text>
+            <Text>
+              Tags:{" "}
+              {tags.length > 0 ? (
+                <>
+                  {tags.map((tag) => (
+                    <Code key={tag.id} colorScheme="blue" mr={1}>
+                      {tag.text}
+                    </Code>
+                  ))}
+                </>
+              ) : (
+                <Text as="span">None</Text>
+              )}
+            </Text>
+
+            <Text>
+              Resource Types:{" "}
+              {resourceTypes.length > 0 ? (
+                <>
+                  {resourceTypes.map((type) => (
+                    <Code key={type} colorScheme="blue" mr={1}>
+                      {type}
+                    </Code>
+                  ))}
+                </>
+              ) : (
+                <Text as="span">None</Text>
+              )}
+            </Text>
+          </Box>
+        )}
+      </Container>
+    </>
+  );
+};
diff --git a/modules/research-framework/portal/src/layouts/NavBar.tsx b/modules/research-framework/portal/src/layouts/NavBar.tsx
index 10b5537f49..016adb640c 100644
--- a/modules/research-framework/portal/src/layouts/NavBar.tsx
+++ b/modules/research-framework/portal/src/layouts/NavBar.tsx
@@ -20,24 +20,28 @@ import { UserMenu } from "@/components/auth/UserMenu";
 
 const NAV_CONTENT = [
   {
-    title: "Projects",
-    url: "/projects",
-  },
-  {
-    title: "Datasets",
-    url: "/resources/datasets",
-  },
-  {
-    title: "Repositories",
-    url: "/resources/repositories",
-  },
-  {
-    title: "Notebooks",
-    url: "/resources/notebooks",
+    title: "Resources",
+    url: "/resources",
   },
+  // {
+  //   title: "Datasets",
+  //   url: "/resources/datasets",
+  // },
+  // {
+  //   title: "Repositories",
+  //   url: "/resources/repositories",
+  // },
+  // {
+  //   title: "Notebooks",
+  //   url: "/resources/notebooks",
+  // },
+  // {
+  //   title: "Models",
+  //   url: "/resources/models",
+  // },
   {
-    title: "Models",
-    url: "/resources/models",
+    title: "Projects",
+    url: "/projects",
   },
 ];
 
@@ -82,7 +86,7 @@ const NavBar = () => {
         </IconButton>
 
         {/* Logo */}
-        <Link to="/projects">
+        <Link to="/">
           <Image src={ApacheAiravataLogo} alt="Logo" boxSize="30px" />
         </Link>
 
diff --git a/modules/research-framework/research-service/pom.xml b/modules/research-framework/research-service/pom.xml
index d79af13145..0cea8263b8 100644
--- a/modules/research-framework/research-service/pom.xml
+++ b/modules/research-framework/research-service/pom.xml
@@ -178,8 +178,8 @@
                 <groupId>org.apache.maven.plugins</groupId>
                 <artifactId>maven-compiler-plugin</artifactId>
                 <configuration>
-                    <source>14</source>
-                    <target>14</target>
+                    <source>15</source>
+                    <target>15</target>
                 </configuration>
             </plugin>
             <plugin>
diff --git a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/AuthzTokenFilter.java b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/AuthzTokenFilter.java
index d31cc28c37..f2c15f0ec5 100644
--- a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/AuthzTokenFilter.java
+++ b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/AuthzTokenFilter.java
@@ -56,12 +56,14 @@ public class AuthzTokenFilter extends OncePerRequestFilter {
     @Override
     protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
         String path = request.getRequestURI();
+        // TODO: ensure that only GET requests do not need auth
         return path.startsWith("/swagger") ||
                 path.startsWith("/v2/api-docs") ||
                 path.startsWith("/v3/api-docs") ||
                 path.startsWith("/swagger-ui") ||
                 path.startsWith("/swagger-resources") ||
-                path.startsWith("/webjars/");
+                path.startsWith("/webjars/") ||
+                path.startsWith("/api/v1/rf/resources");
     }
 
     @Override
diff --git a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/DevDataInitializer.java b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/DevDataInitializer.java
index 35614509c7..816fa83e72 100644
--- a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/DevDataInitializer.java
+++ b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/config/DevDataInitializer.java
@@ -18,14 +18,18 @@
 package org.apache.airavata.research.service.config;
 
 import java.util.HashSet;
+import java.util.Optional;
+import java.util.Set;
 
 import org.apache.airavata.research.service.enums.PrivacyEnum;
 import org.apache.airavata.research.service.enums.StatusEnum;
 import org.apache.airavata.research.service.model.entity.DatasetResource;
 import org.apache.airavata.research.service.model.entity.Project;
 import org.apache.airavata.research.service.model.entity.RepositoryResource;
+import org.apache.airavata.research.service.model.entity.Tag;
 import org.apache.airavata.research.service.model.repo.ProjectRepository;
 import org.apache.airavata.research.service.model.repo.ResourceRepository;
+import org.apache.airavata.research.service.model.repo.TagRepository;
 import org.springframework.beans.factory.annotation.Value;
 import org.springframework.boot.CommandLineRunner;
 import org.springframework.context.annotation.Profile;
@@ -37,16 +41,31 @@ public class DevDataInitializer implements CommandLineRunner {
 
     private final ProjectRepository projectRepository;
     private final ResourceRepository resourceRepository;
+    private final TagRepository tagRepository;
 
     @Value("${airavata.research-hub.dev-user}")
     private String devUserEmail;
 
-    public DevDataInitializer(ProjectRepository projectRepository, ResourceRepository resourceRepository) {
+    public DevDataInitializer(ProjectRepository projectRepository, ResourceRepository resourceRepository, TagRepository tagRepository) {
         this.projectRepository = projectRepository;
         this.resourceRepository = resourceRepository;
+        this.tagRepository = tagRepository;
     }
 
-    private void createProject(String name, String repoUrl, String datasetName, String datasetUrl, String user) {
+    private void createProject(String name, String repoUrl, String datasetName, String datasetUrl,  String[] tags, String user) {
+        Set<Tag> tagSet = new HashSet<>();
+        for (String tag : tags) {
+            Tag t = tagRepository.findByValue(tag);
+            if (t != null) {
+                tagSet.add(t);
+            } else {
+                Tag newTag = new Tag();
+                newTag.setValue(tag);
+                tagSet.add(newTag);
+                tagRepository.save(newTag);
+            }
+        }
+
         RepositoryResource repo = new RepositoryResource();
         repo.setName(name);
         repo.setDescription("Repository for " + name);
@@ -54,6 +73,7 @@ public class DevDataInitializer implements CommandLineRunner {
         repo.setRepositoryUrl(repoUrl);
         repo.setStatus(StatusEnum.VERIFIED);
         repo.setPrivacy(PrivacyEnum.PUBLIC);
+        repo.setTags(tagSet);
         repo = resourceRepository.save(repo);
 
         DatasetResource dataset = new DatasetResource();
@@ -63,6 +83,7 @@ public class DevDataInitializer implements CommandLineRunner {
         dataset.setDatasetUrl(datasetUrl);
         dataset.setStatus(StatusEnum.VERIFIED);
         dataset.setPrivacy(PrivacyEnum.PUBLIC);
+        dataset.setTags(tagSet);
         dataset.setAuthors(new HashSet<>() {
             {
                 add(user);
@@ -92,6 +113,7 @@ public class DevDataInitializer implements CommandLineRunner {
                 "https://github.com/yasithdev/bmtk-workshop.git",
                 "Allen / BMTK Workshop Data",
                 "allen-bmtk-workshop",
+                new String[]{"allen", "bmtk", "workshop"},
                 devUserEmail
         );
 
@@ -100,6 +122,7 @@ public class DevDataInitializer implements CommandLineRunner {
                 "https://github.com/yasithdev/allen-v1.git",
                 "Allen / V1 Data",
                 "allen-v1",
+                new String[]{"allen", "v1", "workshop"},
                 devUserEmail
         );
 
@@ -108,6 +131,7 @@ public class DevDataInitializer implements CommandLineRunner {
                 "https://github.com/yasithdev/onehot-hmmglm.git",
                 "BRAINML / OneHot HMMGLM Data",
                 "brainml-onehot-hmmglm",
+                new String[]{"brainml", "onehot", "workshop"},
                 devUserEmail
         );
 
@@ -116,6 +140,7 @@ public class DevDataInitializer implements CommandLineRunner {
                 "https://github.com/yasithdev/functional-network.git",
                 "HChoiLab / Functional Network Data",
                 "hchoilab-functional-network",
+                new String[]{"hchoilab", "functional network", "workshop"},
                 devUserEmail
         );
     }
diff --git a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java
index 9f4228b554..d7116476d2 100644
--- a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java
+++ b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/controller/ResourceController.java
@@ -73,6 +73,14 @@ public class ResourceController {
         return ResponseEntity.ok(response);
     }
 
+    @Operation(
+            summary = "Get all tags"
+    )
+    @GetMapping(value = "/tags/all")
+    public ResponseEntity<List<org.apache.airavata.research.service.model.entity.Tag>> getTags() {
+        return ResponseEntity.ok(resourceHandler.getAllTags());
+    }
+
     @Operation(
             summary = "Get dataset, notebook, or repository"
     )
@@ -86,10 +94,10 @@ public class ResourceController {
     )
     @GetMapping("/")
     public ResponseEntity<Page<Resource>> getAllResources(
-            @RequestHeader(name="X-Claims", required=true) String claims,
             @RequestParam(value="pageNumber", defaultValue = "0") int pageNumber,
             @RequestParam(value="pageSize", defaultValue = "10") int pageSize,
-            @RequestParam(value="type") ResourceTypeEnum[] types
+            @RequestParam(value="type") ResourceTypeEnum[] types,
+            @RequestParam(value="tag", required = false) String[] tags
     ) {
         List<Class<? extends Resource>> typeList = new ArrayList<>();
         for (ResourceTypeEnum resourceType : types) {
@@ -103,7 +111,7 @@ public class ResourceController {
                 typeList.add(DatasetResource.class);
             }
         }
-        Page<Resource> response = resourceHandler.getAllResources(pageNumber, pageSize, typeList);
+        Page<Resource> response = resourceHandler.getAllResources(pageNumber, pageSize, typeList, tags);
 
         return ResponseEntity.ok(response);
     }
diff --git a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResourceHandler.java b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResourceHandler.java
index 85587ea507..72c36bb19c 100644
--- a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResourceHandler.java
+++ b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/handlers/ResourceHandler.java
@@ -100,9 +100,17 @@ public class ResourceHandler {
         return opResource.get();
     }
 
-    public Page<Resource> getAllResources(int pageNumber, int pageSize, List<Class<? extends Resource>> typeList) {
+    public Page<Resource> getAllResources(int pageNumber, int pageSize, List<Class<? extends Resource>> typeList, String[] tag) {
         Pageable pageable = PageRequest.of(pageNumber, pageSize);
-        return resourceRepository.findAllByTypes(typeList, pageable);
+        if (tag == null || tag.length == 0) {
+            return resourceRepository.findAllByTypes(typeList, pageable);
+        }
+
+        return resourceRepository.findAllByTypesAndAllTags(typeList, tag, tag.length, pageable);
+    }
+
+    public List<Tag> getAllTags() {
+        return tagRepository.findAll();
     }
 
     // Helper methods
diff --git a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/ResourceRepository.java b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/ResourceRepository.java
index 72daa3c787..95c8205ce2 100644
--- a/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/ResourceRepository.java
+++ b/modules/research-framework/research-service/src/main/java/org/apache/airavata/research/service/model/repo/ResourceRepository.java
@@ -33,4 +33,20 @@ public interface ResourceRepository extends JpaRepository<Resource, String> {
 
     @Query("SELECT r FROM #{#entityName} r WHERE TYPE(r) IN :types")
     Page<Resource> findAllByTypes(@Param("types") List<Class<? extends Resource>> types, Pageable pageable);
+
+    @Query("""
+    SELECT r
+    FROM Resource r
+    JOIN r.tags t
+    WHERE r.class IN :typeList AND t.value IN :tags
+    GROUP BY r
+    HAVING COUNT(DISTINCT t.value) = :tagCount
+    """)
+    Page<Resource> findAllByTypesAndAllTags(
+            @Param("typeList") List<Class<? extends Resource>> typeList,
+            @Param("tags") String[] tags,
+            @Param("tagCount") long tagCount,
+            Pageable pageable
+    );
+
 }


(airavata) 04/06: Homepage + redirecting logins

Posted by di...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

dimuthuupe pushed a commit to branch cybershuttle-staging
in repository https://gitbox.apache.org/repos/asf/airavata.git

commit 646f5f9e0b19b83c3a3c678166f5628213c3e778
Author: ganning127 <ga...@gmail.com>
AuthorDate: Sat Apr 5 03:33:04 2025 -0400

    Homepage + redirecting logins
---
 modules/research-framework/portal/src/App.tsx      | 106 ++++++++++++---------
 .../portal/src/assets/Hero.original.png            | Bin 0 -> 3208051 bytes
 .../portal/src/components/auth/Login.tsx           |   4 +
 .../src/components/auth/ProtectedComponent.tsx     |  18 +++-
 .../portal/src/components/auth/UserMenu.tsx        |  23 ++++-
 .../src/components/home/CybershuttleLanding.tsx    |  60 ++++++++++++
 .../src/components/typography/SectionHeading.tsx   |  21 ++++
 .../portal/src/layouts/NavBar.tsx                  |   3 -
 modules/research-framework/portal/src/main.tsx     |  62 +-----------
 9 files changed, 185 insertions(+), 112 deletions(-)

diff --git a/modules/research-framework/portal/src/App.tsx b/modules/research-framework/portal/src/App.tsx
index 23694017ce..32b403aacc 100644
--- a/modules/research-framework/portal/src/App.tsx
+++ b/modules/research-framework/portal/src/App.tsx
@@ -1,5 +1,5 @@
 import { useColorMode } from "./components/ui/color-mode";
-import { BrowserRouter, Route, Routes } from "react-router";
+import { Route, Routes, useLocation, useNavigate } from "react-router";
 import Home from "./components/home";
 import { Models } from "./components/models";
 import { Datasets } from "./components/datasets";
@@ -8,62 +8,82 @@ import Notebooks from "./components/notebooks";
 import Repositories from "./components/repositories";
 import { Login } from "./components/auth/Login";
 import ProtectedComponent from "./components/auth/ProtectedComponent";
-import { useAuth } from "react-oidc-context";
-import { useEffect } from "react";
-import { setUserProvider } from "./lib/api";
+import { AuthProvider, AuthProviderProps } from "react-oidc-context";
+import { useEffect, useState } from "react";
 import NavBarFooterLayout from "./layouts/NavBarFooterLayout";
+import { CybershuttleLanding } from "./components/home/CybershuttleLanding";
+import {
+  APP_REDIRECT_URI,
+  BACKEND_URL,
+  CLIENT_ID,
+  OPENID_CONFIG_URL,
+} from "./lib/constants";
+import { WebStorageStateStore } from "oidc-client-ts";
 function App() {
   const colorMode = useColorMode();
+  const navigate = useNavigate();
+  const location = useLocation();
+  const [oidcConfig, setOidcConfig] = useState<AuthProviderProps | null>(null);
+
   if (colorMode.colorMode === "dark") {
     colorMode.toggleColorMode();
   }
 
-  const user = useAuth();
-
   useEffect(() => {
-    if (user.isAuthenticated) {
-      setUserProvider(() => Promise.resolve(user.user ?? null));
-    }
-  }, [user]);
+    const fetchOidcConfig = async () => {
+      try {
+        const response = await fetch(OPENID_CONFIG_URL);
+        const data = await response.json();
 
-  return (
-    <>
-      <BrowserRouter>
-        {/* <Routes>
-          <Route path="/" element={<Login />} />
+        const redirectUri = APP_REDIRECT_URI;
 
-          <Route
-            path="/projects"
-            element={<ProtectedComponent Component={Home} />}
-          />
-          <Route path="/resources">
-            <Route
-              path="notebooks"
-              element={<ProtectedComponent Component={Notebooks} />}
-            />
-            <Route
-              path="datasets"
-              element={<ProtectedComponent Component={Datasets} />}
-            />
-            <Route
-              path="repositories"
-              element={<ProtectedComponent Component={Repositories} />}
-            />
-            <Route
-              path="models"
-              element={<ProtectedComponent Component={Models} />}
-            />
+        const theConfig: AuthProviderProps = {
+          authority: `${BACKEND_URL}/api/v1/identity-management/`,
+          client_id: CLIENT_ID,
+          redirect_uri: redirectUri,
+          response_type: "code",
+          scope: "openid email",
+          metadata: {
+            authorization_endpoint: data.authorization_endpoint,
+            token_endpoint: data.token_endpoint,
+            revocation_endpoint: data.revocation_endpoint,
+            introspection_endpoint: data.introspection_endpoint,
+            userinfo_endpoint: data.userinfo_endpoint,
+            jwks_uri: data.jwks_uri,
+          },
+          userStore: new WebStorageStateStore({ store: window.localStorage }),
+          automaticSilentRenew: true,
+        };
 
-            <Route
-              path=":type/:id"
-              element={<ProtectedComponent Component={ResourceDetails} />}
-            />
-          </Route>
-        </Routes> */}
+        setOidcConfig(theConfig);
+      } catch (error) {
+        console.error("Error fetching OIDC config:", error);
+      }
+    };
 
+    fetchOidcConfig();
+  }, []);
+
+  if (!oidcConfig) {
+    return <div>Loading OIDC configuration...</div>; // Loading state while config is fetched
+  }
+
+  return (
+    <>
+      <AuthProvider
+        {...oidcConfig}
+        onSigninCallback={() => {
+          // const from = location.state?.from || "/"; // fallback to homepage
+          // console.log("Redirecting to:", from);
+          // navigate(from, { replace: true });
+          // clear state from url
+          navigate(location.pathname, { replace: true });
+        }}
+      >
         <Routes>
           {/* Public Route */}
           <Route element={<NavBarFooterLayout />}>
+            <Route path="/" element={<CybershuttleLanding />} />
             <Route path="/login" element={<Login />} />
           </Route>
 
@@ -79,7 +99,7 @@ function App() {
             <Route path="/resources/:type/:id" element={<ResourceDetails />} />
           </Route>
         </Routes>
-      </BrowserRouter>
+      </AuthProvider>
     </>
   );
 }
diff --git a/modules/research-framework/portal/src/assets/Hero.original.png b/modules/research-framework/portal/src/assets/Hero.original.png
new file mode 100644
index 0000000000..545042c02e
Binary files /dev/null and b/modules/research-framework/portal/src/assets/Hero.original.png differ
diff --git a/modules/research-framework/portal/src/components/auth/Login.tsx b/modules/research-framework/portal/src/components/auth/Login.tsx
index b338bacb01..7ae1adfb53 100644
--- a/modules/research-framework/portal/src/components/auth/Login.tsx
+++ b/modules/research-framework/portal/src/components/auth/Login.tsx
@@ -13,6 +13,9 @@ export const Login = () => {
     }
   }, [auth]);
 
+  const redirect =
+    new URLSearchParams(window.location.search).get("redirect") || "/";
+
   return (
     <Center height="100vh">
       <Stack
@@ -38,6 +41,7 @@ export const Login = () => {
             onClick={() => {
               console.log("Sign in clicked");
               auth.signinRedirect({
+                redirect_uri: `${window.location.origin}${redirect}`,
                 extraQueryParams: {
                   // This is the prompt that will be shown to the user
                   prompt: "login",
diff --git a/modules/research-framework/portal/src/components/auth/ProtectedComponent.tsx b/modules/research-framework/portal/src/components/auth/ProtectedComponent.tsx
index ecbf1feb66..d9a27a7c44 100644
--- a/modules/research-framework/portal/src/components/auth/ProtectedComponent.tsx
+++ b/modules/research-framework/portal/src/components/auth/ProtectedComponent.tsx
@@ -1,16 +1,24 @@
+import { setUserProvider } from "@/lib/api";
+import { useEffect } from "react";
 import { useAuth } from "react-oidc-context";
 import { useNavigate } from "react-router";
 
 function ProtectedComponent({ Component }: { Component: React.FC }) {
   const auth = useAuth();
   const navigate = useNavigate();
+  const path = window.location.pathname;
 
-  if (auth.isLoading) {
-    return;
-  }
+  useEffect(() => {
+    if (!auth.isLoading && !auth.isAuthenticated) {
+      navigate(`/login?redirect=${path}`, { replace: true });
+    }
+
+    if (auth.isAuthenticated) {
+      setUserProvider(() => Promise.resolve(auth.user ?? null));
+    }
+  }, [auth]);
 
-  if (!auth.isAuthenticated) {
-    navigate("/login");
+  if (auth.isLoading || !auth.isAuthenticated) {
     return;
   }
 
diff --git a/modules/research-framework/portal/src/components/auth/UserMenu.tsx b/modules/research-framework/portal/src/components/auth/UserMenu.tsx
index b929659908..cba709720f 100644
--- a/modules/research-framework/portal/src/components/auth/UserMenu.tsx
+++ b/modules/research-framework/portal/src/components/auth/UserMenu.tsx
@@ -1,11 +1,28 @@
-import { Avatar, Box, HStack, Text, Menu, Portal } from "@chakra-ui/react";
+import {
+  Avatar,
+  Box,
+  HStack,
+  Text,
+  Menu,
+  Portal,
+  Button,
+} from "@chakra-ui/react";
+import { FaArrowRight } from "react-icons/fa6";
 import { useAuth } from "react-oidc-context";
+import { Link } from "react-router";
 
 export const UserMenu = () => {
   const auth = useAuth();
 
-  if (auth.isLoading || !auth.user) return null;
-
+  if (auth.isLoading || !auth.user)
+    return (
+      <Link to="/login">
+        <Button colorPalette="blue">
+          Login
+          <FaArrowRight />
+        </Button>
+      </Link>
+    );
   const handleLogout = async () => {
     await auth.signoutRedirect();
   };
diff --git a/modules/research-framework/portal/src/components/home/CybershuttleLanding.tsx b/modules/research-framework/portal/src/components/home/CybershuttleLanding.tsx
new file mode 100644
index 0000000000..0195545767
--- /dev/null
+++ b/modules/research-framework/portal/src/components/home/CybershuttleLanding.tsx
@@ -0,0 +1,60 @@
+import { Container, Heading, Image, Text, VStack, Box } from "@chakra-ui/react";
+import HeroOriginal from "../../assets/Hero.original.png";
+import { SectionHeading } from "../typography/SectionHeading";
+
+export const CybershuttleLanding = () => {
+  return (
+    <>
+      <Container maxW="breakpoint-lg" mt={8}>
+        <Heading
+          textAlign="center"
+          fontSize={{ base: "3xl", md: "4xl", lg: "5xl" }}
+          fontWeight="black"
+          lineHeight={1.2}
+        >
+          Balance Local and Remote for
+          <br /> Streamlined Research with{" "}
+          <Text as="span" color="blue.600">
+            Cybershuttle
+          </Text>
+        </Heading>
+
+        <Text
+          textAlign="center"
+          fontSize={{ base: "lg", lg: "xl" }}
+          color="gray.600"
+          mt={4}
+        >
+          Cybershuttle expertly balances local and remote computing, seamlessly
+          orchestrating tasks and data between machines. Schedule
+          time-sensitive, small tasks locally, and reserve compute-intensive
+          tasks for powerful remote HPC resources, with data transparently
+          accessible everywhere. This approach significantly enhances the
+          efficiency of scientific workflows compared to fully remote or fully
+          local operations.
+        </Text>
+
+        <Image src={HeroOriginal} alt="Cybershuttle" maxW="100%" mt={8} />
+        <VStack mt={18} alignItems="flex-start" gap={16}>
+          <Box>
+            <SectionHeading>Main Features</SectionHeading>
+          </Box>
+
+          <Box>
+            <SectionHeading>Science-Centrist Extensions</SectionHeading>
+          </Box>
+
+          <Box>
+            <SectionHeading>Collaboration</SectionHeading>
+          </Box>
+
+          <Box>
+            <SectionHeading>Thanks to our Sponsors</SectionHeading>
+          </Box>
+        </VStack>
+
+        <Box my={8} />
+      </Container>
+    </>
+  );
+};
diff --git a/modules/research-framework/portal/src/components/typography/SectionHeading.tsx b/modules/research-framework/portal/src/components/typography/SectionHeading.tsx
new file mode 100644
index 0000000000..e86aea2075
--- /dev/null
+++ b/modules/research-framework/portal/src/components/typography/SectionHeading.tsx
@@ -0,0 +1,21 @@
+import { Heading, HeadingProps } from "@chakra-ui/react";
+
+interface SectionHeadingProps extends HeadingProps {
+  children: React.ReactNode;
+}
+export const SectionHeading = ({ children, ...props }: SectionHeadingProps) => {
+  return (
+    <Heading
+      fontSize={{
+        base: "2xl",
+        md: "3xl",
+        lg: "4xl",
+      }}
+      fontWeight="bold"
+      lineHeight={1.2}
+      {...props}
+    >
+      {children}
+    </Heading>
+  );
+};
diff --git a/modules/research-framework/portal/src/layouts/NavBar.tsx b/modules/research-framework/portal/src/layouts/NavBar.tsx
index 43090c1841..10b5537f49 100644
--- a/modules/research-framework/portal/src/layouts/NavBar.tsx
+++ b/modules/research-framework/portal/src/layouts/NavBar.tsx
@@ -4,7 +4,6 @@ import {
   Spacer,
   Image,
   HStack,
-  Avatar,
   Box,
   IconButton,
   useDisclosure,
@@ -15,7 +14,6 @@ import {
 } from "@chakra-ui/react";
 import ApacheAiravataLogo from "../assets/airavata-logo.png";
 import { Link, useNavigate } from "react-router";
-import { useAuth } from "react-oidc-context";
 import { RxHamburgerMenu } from "react-icons/rx";
 import { IoClose } from "react-icons/io5";
 import { UserMenu } from "@/components/auth/UserMenu";
@@ -49,7 +47,6 @@ interface NavLinkProps extends ButtonProps {
 }
 
 const NavBar = () => {
-  const auth = useAuth();
   const { open, onToggle } = useDisclosure();
   const navigate = useNavigate();
 
diff --git a/modules/research-framework/portal/src/main.tsx b/modules/research-framework/portal/src/main.tsx
index f0fc512ec3..8ec6752f04 100644
--- a/modules/research-framework/portal/src/main.tsx
+++ b/modules/research-framework/portal/src/main.tsx
@@ -1,70 +1,16 @@
 import { Provider } from "@/components/ui/provider";
-import React, { useEffect, useState } from "react";
+import React from "react";
 import ReactDOM from "react-dom/client";
 import App from "./App";
-import { AuthProvider, AuthProviderProps } from "react-oidc-context";
-import { WebStorageStateStore } from "oidc-client-ts";
-import {
-  APP_REDIRECT_URI,
-  BACKEND_URL,
-  CLIENT_ID,
-  OPENID_CONFIG_URL,
-} from "./lib/constants";
+import { BrowserRouter } from "react-router";
 
 const Index = () => {
-  const [oidcConfig, setOidcConfig] = useState<AuthProviderProps | null>(null);
-
-  useEffect(() => {
-    const fetchOidcConfig = async () => {
-      try {
-        const response = await fetch(OPENID_CONFIG_URL);
-        const data = await response.json();
-
-        const redirectUri = APP_REDIRECT_URI;
-
-        const theConfig: AuthProviderProps = {
-          authority: `${BACKEND_URL}/api/v1/identity-management/`,
-          client_id: CLIENT_ID,
-          redirect_uri: redirectUri,
-          response_type: "code",
-          scope: "openid email",
-          metadata: {
-            authorization_endpoint: data.authorization_endpoint,
-            token_endpoint: data.token_endpoint,
-            revocation_endpoint: data.revocation_endpoint,
-            introspection_endpoint: data.introspection_endpoint,
-            userinfo_endpoint: data.userinfo_endpoint,
-            jwks_uri: data.jwks_uri,
-          },
-          userStore: new WebStorageStateStore({ store: window.localStorage }),
-          automaticSilentRenew: true,
-        };
-
-        setOidcConfig(theConfig);
-      } catch (error) {
-        console.error("Error fetching OIDC config:", error);
-      }
-    };
-
-    fetchOidcConfig();
-  }, []);
-
-  if (!oidcConfig) {
-    return <div>Loading OIDC configuration...</div>; // Loading state while config is fetched
-  }
-
   return (
     <React.StrictMode>
       <Provider>
-        <AuthProvider
-          {...oidcConfig}
-          onSigninCallback={async (user) => {
-            console.log("User signed in", user);
-            window.location.href = "/projects";
-          }}
-        >
+        <BrowserRouter>
           <App />
-        </AuthProvider>
+        </BrowserRouter>
       </Provider>
     </React.StrictMode>
   );