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

yasith pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/airavata.git


The following commit(s) were added to refs/heads/master by this push:
     new fe8d4e54f4 gateway switching for local agent (#469)
fe8d4e54f4 is described below

commit fe8d4e54f4d3a941dd1d245993a046c66d19e6e5
Author: Ganning Xu <[email protected]>
AuthorDate: Sat Apr 26 17:52:18 2025 -0400

    gateway switching for local agent (#469)
    
    * gateway switching for local agent
    
    * add build.sh to gitignore
---
 .gitignore                                         |   1 +
 airavata-local-agent/main/background.js            |  71 +++++++++++++-
 airavata-local-agent/package.json                  |   2 +-
 .../renderer/components/HeaderBox.jsx              |   4 +-
 airavata-local-agent/renderer/lib/Contexts.js      | 107 +++++++++++----------
 airavata-local-agent/renderer/pages/_app.jsx       |   6 +-
 .../renderer/pages/docker-home.jsx                 |   4 +-
 airavata-local-agent/renderer/pages/login.jsx      |  55 ++++++++++-
 8 files changed, 188 insertions(+), 62 deletions(-)

diff --git a/.gitignore b/.gitignore
index db30446109..e4bae615e4 100644
--- a/.gitignore
+++ b/.gitignore
@@ -47,5 +47,6 @@ airavata-local-agent/proxy/config.txt
 airavata-local-agent/proxy/websockify
 airavata-local-agent/renderer/.next
 airavata-local-agent/dist
+airavata-local-agent/build.sh
 
 logs
diff --git a/airavata-local-agent/main/background.js 
b/airavata-local-agent/main/background.js
index 9ca6241c5a..67623d5874 100644
--- a/airavata-local-agent/main/background.js
+++ b/airavata-local-agent/main/background.js
@@ -26,6 +26,8 @@ import serve from 'electron-serve';
 import { createWindow } from './helpers';
 const fs = require('fs');
 import log from 'electron-log/main';
+import Store from 'electron-store';
+
 
 const isProd = process.env.NODE_ENV === 'production';
 
@@ -38,7 +40,7 @@ if (isProd) {
 
 let mainWindow;
 const TOKEN_FILE = '~/csagent/token/keys.json';
-const BASE_URL = 'https://cybershuttle.org';
+let BASE_URL = 'this value will be replaced';
 
 // ----- OUR CUSTOM FUNCTIONS -----
 if (process.defaultApp) {
@@ -795,4 +797,69 @@ const writeFile = async (event, userPath, data) => {
 ipcMain.on('write-file', writeFile);
 
 /*
-*/
\ No newline at end of file
+GATEWAY PINGING LOGIC
+*/
+
+const store = new Store();
+
+const randomString = (length) => {
+  const chars = 
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ';
+
+  let result = '';
+  for (let i = length; i > 0; --i) {
+    result += chars[Math.floor(Math.random() * chars.length)];
+  }
+
+  return result;
+};
+
+const gateways = [
+  {
+    "id": "mdcyber",
+    "name": "MD Cybershuttle",
+    "gateway": "https://cybershuttle.org";,
+    "loginUrl": 
`https://iam.scigap.org/auth/realms/testdrive/protocol/openid-connect/auth?response_type=code&client_id=pga&redirect_uri=csagent%3A%2F%2Flogin-callback&scope=openid&state=${randomString(15)}&kc_idp_hint=cilogon&idp_alias=cilogon`
+  },
+  {
+    "id": "aicyber",
+    "name": "AI Cybershuttle",
+    "gateway": "https://ai.cybershuttleadfasfd.org";,
+    "loginUrl": `https://google.com`
+  }
+];
+
+log.info("Gateways: ", gateways);
+
+let CURRENT_GATEWAY = store.get('stored-gateway');
+
+log.info("CURRENT_GATEWAY (before): ", CURRENT_GATEWAY);
+
+if (typeof CURRENT_GATEWAY === "undefined" || CURRENT_GATEWAY === null) {
+  CURRENT_GATEWAY = "mdcyber";
+}
+
+log.info("CURRENT_GATEWAY (after): ", CURRENT_GATEWAY);
+
+
+BASE_URL = gateways.find(g => g.id === CURRENT_GATEWAY)?.gateway;
+
+log.info("BASE_URL: ", BASE_URL);
+
+ipcMain.on('get-all-gateways', (event) => {
+  log.info("Getting all gateways");
+  event.sender.send('got-gateways', gateways);
+});
+
+ipcMain.on('get-gateway', (event) => {
+  log.info("Getting the gateway");
+  event.sender.send('gateway-got', CURRENT_GATEWAY);
+});
+
+ipcMain.on('set-gateway', (event, gateway) => {
+  log.info("Setting the gateway: ", gateway);
+  CURRENT_GATEWAY = gateway;
+  BASE_URL = gateways.find(g => g.id === CURRENT_GATEWAY).gateway;
+  store.set('stored-gateway', gateway);
+  event.sender.send('gateway-set', gateway);
+});
+
diff --git a/airavata-local-agent/package.json 
b/airavata-local-agent/package.json
index ae275c2b55..63fde5fdce 100644
--- a/airavata-local-agent/package.json
+++ b/airavata-local-agent/package.json
@@ -2,7 +2,7 @@
   "private": true,
   "name": "cybershuttle-local-agent",
   "description": "User interface for Apache Airavata.",
-  "version": "2.0.6",
+  "version": "2.1.0",
   "author": "Ganning Xu",
   "main": "app/background.js",
   "scripts": {
diff --git a/airavata-local-agent/renderer/components/HeaderBox.jsx 
b/airavata-local-agent/renderer/components/HeaderBox.jsx
index d4915feef9..54d7a36565 100644
--- a/airavata-local-agent/renderer/components/HeaderBox.jsx
+++ b/airavata-local-agent/renderer/components/HeaderBox.jsx
@@ -23,7 +23,7 @@ import { Box, Flex, Spacer, Text, Modal, ModalOverlay, 
ModalCloseButton, ModalHe
 import { UserModal } from './UserModal';
 import { useEffect, useState } from "react";
 
-export const HeaderBox = ({ name, email }) => {
+export const HeaderBox = ({ name, email, gatewayName }) => {
   const { isOpen, onOpen, onClose } = useDisclosure();
   const [version, setVersion] = useState('');
   const [userObj, setUserObj] = useState({
@@ -100,7 +100,7 @@ export const HeaderBox = ({ name, email }) => {
                     onOpen();
                   }}
                 >
-                  {name} ({email}),
+                  {name} ({email}, {gatewayName})
                 </Text>
               </Tooltip>
               {" "}
diff --git a/airavata-local-agent/renderer/lib/Contexts.js 
b/airavata-local-agent/renderer/lib/Contexts.js
index 12ceb52008..268f275329 100644
--- a/airavata-local-agent/renderer/lib/Contexts.js
+++ b/airavata-local-agent/renderer/lib/Contexts.js
@@ -49,53 +49,62 @@ export const useAuth = () => {
   return state;
 };
 
+// Create context
+const BackendUrlContext = createContext();
 
-// import { useContext, createContext } from "react";
-
-// const TemplateContext = createContext({});
-
-// // Template Provider
-// const TemplateProvider = ({ children }) => {
-//   const [accessToken, setAccessToken] = React.useState('');
-
-//   // Context values passed to consumer
-//   const value = {
-//     accessToken,    // <------ Expose Value to Consumer
-//     setAccessToken  // <------ Expose Setter to Consumer
-//   };
-
-//   return (
-//     <TemplateContext.Provider value={value}>
-//       {children}
-//     </TemplateContext.Provider>
-//   );
-// };
-
-// // Template Consumer
-// const TemplateConsumer = ({ children }) => {
-//   return (
-//     <TemplateContext.Consumer>
-//       {(context) => {
-//         if (context === undefined) {
-//           throw new Error('TemplateConsumer must be used within 
TemplateProvider');
-//         }
-//         return children(context);
-//       }}
-//     </TemplateContext.Consumer>
-//   );
-// };
-
-// // useTemplate Hook
-// const useAuth = () => {
-//   const context = useContext(AuthContext);
-//   if (context === undefined) {
-//     throw new Error('useTemplate must be used within TemplateProvider');
-//   }
-//   return context;
-// };
-
-// export {
-//   TemplateProvider,
-//   TemplateConsumer,
-//   useAuth
-// };
+export const BackendUrlProvider = ({ children }) => {
+  const [gateway, setGateway] = useState('mdcyber');
+  const [allGateways, setAllGateways] = useState([]);
+
+  const [loading, setLoading] = useState(true);
+
+  useEffect(() => {
+    // Fetch gateways
+    window.ipc.send('get-all-gateways');
+    window.ipc.send('get-gateway');
+
+    window.ipc.on('got-gateways', (all) => {
+      setAllGateways(all);
+    });
+
+    window.ipc.on('gateway-got', (gateway) => {
+      setGateway(gateway);
+      setLoading(false);
+    });
+
+    return () => {
+      window.ipc.removeAllListeners('got-gateways');
+      window.ipc.removeAllListeners('gateway-got');
+    };
+  }, []);
+
+
+
+  // the user might want to change their gateway in the app too
+  const setGatewayId = (gateway) => {
+    setGateway(gateway);
+    window.ipc.send('set-gateway', gateway);
+  };
+
+
+  if (loading) {
+    return <div>Loading...</div>;
+  }
+  const ourGateway = allGateways?.find(g => g.id === gateway);
+
+  return (
+    <BackendUrlContext.Provider value={{
+      apiUrl: ourGateway?.gateway + '/api',
+      authUrl: ourGateway?.gateway + '/auth',
+      loginUrl: ourGateway?.loginUrl,
+      gatewayName: ourGateway?.name,
+      gateway,
+      allGateways,
+      setGatewayId
+    }}>
+      {children}
+    </BackendUrlContext.Provider>
+  );
+};
+
+export const useBackendUrls = () => useContext(BackendUrlContext);
diff --git a/airavata-local-agent/renderer/pages/_app.jsx 
b/airavata-local-agent/renderer/pages/_app.jsx
index 55d8766b2e..8bfaf580fe 100644
--- a/airavata-local-agent/renderer/pages/_app.jsx
+++ b/airavata-local-agent/renderer/pages/_app.jsx
@@ -22,13 +22,15 @@
 // pages/_app.jsx
 import { useState } from 'react';
 import { ChakraProvider } from '@chakra-ui/react';
-import { AuthContext, AuthProvider } from '../lib/Contexts';
+import { AuthContext, AuthProvider, BackendUrlProvider } from 
'../lib/Contexts';
 
 function MyApp({ Component, pageProps }) {
   return (
     <ChakraProvider>
       <AuthProvider>
-        <Component {...pageProps} />
+        <BackendUrlProvider>
+          <Component {...pageProps} />
+        </BackendUrlProvider>
       </AuthProvider>
     </ChakraProvider>
   );
diff --git a/airavata-local-agent/renderer/pages/docker-home.jsx 
b/airavata-local-agent/renderer/pages/docker-home.jsx
index 256f3a737f..fd86aeec33 100644
--- a/airavata-local-agent/renderer/pages/docker-home.jsx
+++ b/airavata-local-agent/renderer/pages/docker-home.jsx
@@ -29,6 +29,7 @@ import { AiOutlineCode } from "react-icons/ai";
 import { useInterval } from "usehooks-ts";
 import { DEBUG_DOCKER_MODE, API_BASE_URL, AUTH_BASE_URL, TOKEN_FILE } from 
"../lib/constants";
 import { motion } from 'framer-motion';
+import { useBackendUrls } from "../lib/Contexts";
 
 const PING_DOCKER_INTERVAL = 10000;
 
@@ -73,6 +74,7 @@ const DockerHome = () => {
   const [error, setError] = useState(null);
   const [tabIndex, setTabIndex] = useState(1);
   const toast = useToast();
+  const { gatewayName } = useBackendUrls();
 
   useEffect(() => {
     pingDocker();
@@ -142,7 +144,7 @@ const DockerHome = () => {
 
   return (
     <Box h='100vh' overflow='hidden' bg='gray.100'>
-      <HeaderBox />
+      <HeaderBox gatewayName={gatewayName} />
 
       <Tabs h='100%' index={tabIndex} onChange={(index) => setTabIndex(index)} 
isLazy>
         <Grid templateColumns='repeat(20, 1fr)' h='inherit'>
diff --git a/airavata-local-agent/renderer/pages/login.jsx 
b/airavata-local-agent/renderer/pages/login.jsx
index c699674db2..2a3e4721ac 100644
--- a/airavata-local-agent/renderer/pages/login.jsx
+++ b/airavata-local-agent/renderer/pages/login.jsx
@@ -19,10 +19,10 @@
 *                                                              
 *
 *****************************************************************/
-import { Box, Center, Flex, FormControl, FormLabel, Input, Img, Text, VStack, 
Button, Alert, AlertIcon, Link, Heading, Spinner } from "@chakra-ui/react";
-import { useContext, useEffect, useState } from "react";
+import { Box, Center, Flex, Select, Img, Text, Button, Alert, AlertIcon, Link, 
Heading, Spinner } from "@chakra-ui/react";
+import { useEffect, useState } from "react";
 import { HeaderBox } from "../components/HeaderBox";
-import { AuthContext, useAuth } from "../lib/Contexts";
+import { useAuth, useBackendUrls } from "../lib/Contexts";
 import { useRouter } from "next/router";
 import { TOKEN_FILE } from "../lib/constants";
 
@@ -33,8 +33,11 @@ const Login = () => {
   const [loading, setLoading] = useState(false);
   const [verifyURL, setVerifyURL] = useState("");
   const [isProd, setIsProd] = useState(false);
+  const [gatewayOptions, setGatewayOptions] = useState([]);
+  const [selectedGateway, setSelectedGateway] = useState("");
   const [authInfo, setAuthInfo] = useAuth();
   const router = useRouter();
+  const { apiUrl, authUrl, loginUrl, setGatewayId } = useBackendUrls();
 
   // general a random string
   const randomString = (length) => {
@@ -72,6 +75,8 @@ const Login = () => {
   useEffect(() => {
 
     window.ipc.send('is-prod');
+    window.ipc.send('get-all-gateways');
+    window.ipc.send('get-gateway');
 
     if (localStorage.getItem("ciLoginAuto") === "true") {
       setLoading(true);
@@ -126,10 +131,23 @@ const Login = () => {
       setIsProd(isProdResult);
     });
 
+
+    window.ipc.on('got-gateways', (data) => {
+      setGatewayOptions(data);
+    });
+
+    window.ipc.on('gateway-got', (gateway) => {
+      setSelectedGateway(gateway);
+    });
+
+    // TODO: ask background process for what gateway we are currently 
configured on
+
     return () => {
       window.ipc.removeAllListeners('file-written');
       window.ipc.removeAllListeners('file-read');
       window.ipc.removeAllListeners('is-prod-reply');
+      window.ipc.removeAllListeners('got-gateways');
+      window.ipc.removeAllListeners('gateway-got');
     };
   }, []);
 
@@ -165,6 +183,34 @@ const Login = () => {
             )
           }
 
+          <Box shadow='md' rounded='md' p={4} mt={4}>
+            <Heading size='md' textAlign="left" color='blue.500'>
+              Gateway
+            </Heading>
+            <Select mt={4}
+              onChange={(e) => {
+                const gateway = e.target.value;
+
+                if (gateway) {
+                  window.ipc.send('set-gateway', gateway);
+                  setSelectedGateway(gateway);
+                  setGatewayId(gateway);
+                }
+              }}
+              value={selectedGateway}
+            >
+              {
+                gatewayOptions.map((item) => {
+                  return (
+                    <option key={item.gateway} value={item.id}>
+                      {item.name} ({item.gateway})
+                    </option>
+                  );
+                })
+              }
+            </Select>
+          </Box>
+
           <Box shadow='md' rounded='md' p={4} mt={4}>
             <Heading size='md' textAlign="left" color='blue.500'>
 
@@ -173,10 +219,9 @@ const Login = () => {
             {
               isProd ? (
                 <Button colorScheme='blue' w='full' mt={4}
-
                   onClick={() => {
                     setLoading(true);
-                    
window.open(`https://iam.scigap.org/auth/realms/testdrive/protocol/openid-connect/auth?response_type=code&client_id=pga&redirect_uri=csagent%3A%2F%2Flogin-callback&scope=openid&state=${randomString(15)}&kc_idp_hint=cilogon&idp_alias=cilogon`,
 '_blank');
+                    window.open(loginUrl, '_blank');
                   }}
                   isDisabled={loading}
                 > {

Reply via email to