Skip to content

Commit

Permalink
[ui] Feat/login UI (unitycatalog/unitycatalog-ui#73)
Browse files Browse the repository at this point in the history
* start of login page

* env and google auth button

* merge with main, remove params reference

* start of okta auth

* initial commit for handling auth token (unitycatalog#67)

* start of login with keycloak

* handle google sign in with token

* more google auth

* profile dropdown

* merge with main

* merge with main

* convert to axios

* start of readme instructions

* get current user endpoint (unitycatalog/unitycatalog-ui#70)

clean up some other endpoints

* commenting out UI until repositories are merged

* clean up current user (unitycatalog/unitycatalog-ui#74)

* yarn lock file

* remove keycloak for now, node version error in jwt-decode dependency

* commit yarn lock

* remove state as useEffect dependency, comment out currentUser call for now

---------

Co-authored-by: Xiang Xu <[email protected]>
  • Loading branch information
2 people authored and rtyler committed Sep 6, 2024
1 parent a1df9e5 commit cab22cf
Show file tree
Hide file tree
Showing 25 changed files with 1,772 additions and 859 deletions.
14 changes: 14 additions & 0 deletions ui/.env
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Google config
REACT_APP_GOOGLE_AUTH_ENABLED=false
REACT_APP_GOOGLE_CLIENT_ID=

# Okta config
REACT_APP_OKTA_AUTH_ENABLED=false
REACT_APP_OKTA_DOMAIN=
REACT_APP_OKTA_CLIENT_ID=

# Keycloak config
REACT_APP_KEYCLOAK_AUTH_ENABLED=false
REACT_APP_KEYCLOAK_URL=
REACT_APP_KEYCLOAK_REALM_ID=
REACT_APP_KEYCLOAK_CLIENT_ID=
7 changes: 7 additions & 0 deletions ui/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,3 +21,10 @@ Open [http://localhost:3000](http://localhost:3000) to view it in the browser.

The page will reload if you make edits.\
You will also see any lint errors in the console.


### Authenticate and Login

OSS Unity Catalog supports Sign in with Google. You can authenticate with Google by clicking the "Sign in with Google" button on the login page, once OAuth has been configured. To configure this, follow the steps to obtain a [Google API Client ID](https://developers.google.com/identity/gsi/web/guides/get-google-api-clientid) and configure your OAuth consent screen.

Once you have the client ID, add it to the `.env` file after `REACT_APP_GOOGLE_CLIENT_ID=` and change the `REACT_APP_GOOGLE_AUTH_ENABLED` flag from false to true. Restart yarn.
5 changes: 4 additions & 1 deletion ui/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,14 @@
"proxy": "http://localhost:8080",
"dependencies": {
"@ant-design/icons": "^5.3.7",
"@okta/okta-auth-js": "^7.7.0",
"@okta/okta-signin-widget": "^7.21.0",
"@tanstack/react-query": "^5.50.1",
"@testing-library/jest-dom": "^5.14.1",
"@testing-library/react": "^13.0.0",
"@testing-library/user-event": "^13.2.1",
"antd": "^5.19.1",
"axios": "^1.7.4",
"react": "^18.3.1",
"react-dom": "^18.3.1",
"react-router-dom": "^6.24.1",
Expand Down Expand Up @@ -52,4 +55,4 @@
"@types/react-dom": "^18.0.0",
"prettier": "^3.3.3"
}
}
}
10 changes: 10 additions & 0 deletions ui/public/keycloak-logo.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added ui/public/okta-logo.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
35 changes: 35 additions & 0 deletions ui/public/uc-logo-horiz-reverse.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
118 changes: 99 additions & 19 deletions ui/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import React from 'react';
import { ConfigProvider, Layout, Menu } from 'antd';
import React, { useMemo } from 'react';
import {
Avatar,
ConfigProvider,
Dropdown,
Layout,
Menu,
MenuProps,
Typography,
} from 'antd';
import {
createBrowserRouter,
Outlet,
Expand All @@ -16,6 +24,9 @@ import CatalogsList from './pages/CatalogsList';
import CatalogDetails from './pages/CatalogDetails';
import SchemaDetails from './pages/SchemaDetails';
import { NotificationProvider } from './utils/NotificationContext';
import Login from './pages/Login';
import { AuthProvider, useAuth } from './context/auth-context';
import { UserOutlined } from '@ant-design/icons';

const router = createBrowserRouter([
{
Expand Down Expand Up @@ -50,8 +61,43 @@ const router = createBrowserRouter([
]);

function AppProvider() {
const { accessToken, logout } = useAuth();
const navigate = useNavigate();
const loggedIn = accessToken !== '';

const profileMenuItems = useMemo(
(): MenuProps['items'] => [
{
key: 'userInfo',
label: (
<div
style={{
display: 'flex',
flexDirection: 'column',
cursor: 'default',
}}
>
<Typography.Text>User name here</Typography.Text>
<Typography.Text>[email protected]</Typography.Text>
</div>
),
},
{
type: 'divider',
},
{
key: 'logout',
label: 'Log out',
onClick: () => logout().then(() => navigate('/')),
},
],
[],
);

// commenting login UI for now until repositories are merged
// return !loggedIn ? (
// <Login />
// ) : (
return (
<ConfigProvider
theme={{
Expand All @@ -65,23 +111,54 @@ function AppProvider() {
>
<Layout>
{/* Header */}
<Layout.Header style={{ display: 'flex', alignItems: 'center' }}>
<div style={{ marginRight: 24 }} onClick={() => navigate('/')}>
<img src="/uc-logo-reverse.png" height={32} alt="uc-logo-reverse" />
<Layout.Header
style={{
display: 'flex',
alignItems: 'center',
width: '100%',
justifyContent: 'space-between',
}}
>
<div
style={{ display: 'flex', alignItems: 'center', flex: '1 0 auto' }}
>
<div style={{ marginRight: 24 }} onClick={() => navigate('/')}>
<img
src="/uc-logo-reverse.png"
height={32}
alt="uc-logo-reverse"
/>
</div>
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={['catalogs']}
items={[
{
key: 'catalogs',
label: 'Catalogs',
onClick: () => navigate('/'),
},
]}
style={{ flex: 1, minWidth: 0 }}
/>
</div>
<Menu
theme="dark"
mode="horizontal"
defaultSelectedKeys={['catalogs']}
items={[
{
key: 'catalogs',
label: 'Catalogs',
onClick: () => navigate('/'),
},
]}
style={{ flex: 1, minWidth: 0 }}
/>
{/*<div>*/}
{/* <Dropdown*/}
{/* menu={{ items: profileMenuItems }}*/}
{/* trigger={['click']}*/}
{/* placement={'bottomRight'}*/}
{/* >*/}
{/* <Avatar*/}
{/* icon={<UserOutlined />}*/}
{/* style={{*/}
{/* backgroundColor: 'white',*/}
{/* color: 'black',*/}
{/* cursor: 'pointer',*/}
{/* }}*/}
{/* />*/}
{/* </Dropdown>*/}
{/*</div>*/}
</Layout.Header>
{/* Content */}
<Layout.Content
Expand Down Expand Up @@ -118,6 +195,7 @@ function AppProvider() {
</Layout>
</ConfigProvider>
);
// );
}

function App() {
Expand All @@ -128,7 +206,9 @@ function App() {
return (
<NotificationProvider>
<QueryClientProvider client={queryClient}>
<RouterProvider router={router} fallbackElement={<p>Loading...</p>} />
<AuthProvider>
<RouterProvider router={router} fallbackElement={<p>Loading...</p>} />
</AuthProvider>
</QueryClientProvider>
</NotificationProvider>
);
Expand Down
68 changes: 68 additions & 0 deletions ui/src/components/login/GoogleAuthButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { useCallback, useEffect, useMemo } from 'react';
import { Button } from 'antd';

export default function GoogleAuthButton({
onGoogleSignIn,
}: {
onGoogleSignIn: (credential: string) => void;
}) {
const clientId = useMemo(() => process.env.REACT_APP_GOOGLE_CLIENT_ID, []);

const handleGoogleSignIn = useCallback(
(res: any) => {
if (!res.clientId || !res.credential) return;
onGoogleSignIn(res.credential);
},
[onGoogleSignIn],
);

useEffect(() => {
const url = 'https://accounts.google.com/gsi/client';
const scripts = document.getElementsByTagName('script');
const isGsiScriptLoaded = Array.from(scripts).some(
(script) => script.src === url,
);

if (isGsiScriptLoaded) return;

const initializeGsi = () => {
// Typescript will complain about window.google
// Add types to your `react-app-env.d.ts` or //@ts-ignore it.
if (!(window as any).google || isGsiScriptLoaded) return;

(window as any).google.accounts.id.initialize({
client_id: process.env.REACT_APP_GOOGLE_CLIENT_ID,
callback: handleGoogleSignIn,
});

(window as any).google.accounts.id.renderButton(
document.getElementById('google-client-button'),
{
text: 'continue_with', // customization attributes
width: 240,
theme: 'outline',
},
);
};

const script = document.createElement('script');
script.src = 'https://accounts.google.com/gsi/client';
script.onload = initializeGsi;
script.defer = true;
script.async = true;
script.id = 'google-client-script';
document.querySelector('body')?.appendChild(script);

return () => {
// Cleanup function that runs when component unmounts
(window as any).google?.accounts.id.cancel();
document.getElementById('google-client-script')?.remove();
};
}, [handleGoogleSignIn]);

return (
<>
{clientId && <Button style={{ width: 240 }} id="google-client-button" />}
</>
);
}
Loading

0 comments on commit cab22cf

Please sign in to comment.