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 <[email protected]> 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> );
