mirror of
https://github.com/open-metadata/OpenMetadata.git
synced 2025-08-28 02:46:09 +00:00
Adding welcome first user modal and auto-silent-renew for token (#184)
* Adding welcome first user modal and auto-silent-renew for token * Minor fix
This commit is contained in:
parent
b5ed3feac2
commit
d170cfe1c6
19
catalog-rest-service/src/main/resources/ui/src/@types/jpeg.d.ts
vendored
Normal file
19
catalog-rest-service/src/main/resources/ui/src/@types/jpeg.d.ts
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Licensed to the Apache Software Foundation (ASF) under one or more
|
||||
* contributor license agreements. See the NOTICE file distributed with
|
||||
* this work for additional information regarding copyright ownership.
|
||||
* The ASF licenses this file to You under the Apache License, Version 2.0
|
||||
* (the "License"); you may not use this file except in compliance with
|
||||
* the License. You may obtain a copy of the License at
|
||||
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
// Module declaration to allow importing JPEG files
|
||||
declare module '*.jpeg';
|
Binary file not shown.
After Width: | Height: | Size: 103 KiB |
@ -19,7 +19,7 @@ import { AxiosResponse } from 'axios';
|
||||
import { CookieStorage } from 'cookie-storage';
|
||||
import { isEmpty, isNil } from 'lodash';
|
||||
import { observer } from 'mobx-react';
|
||||
import { User } from 'Models';
|
||||
import { NewUser, User } from 'Models';
|
||||
import { UserManager, WebStorageStateStore } from 'oidc-client';
|
||||
import React, {
|
||||
ComponentType,
|
||||
@ -28,7 +28,13 @@ import React, {
|
||||
useState,
|
||||
} from 'react';
|
||||
import { Callback, makeAuthenticator, makeUserManager } from 'react-oidc';
|
||||
import { Redirect, Route, Switch, useHistory } from 'react-router-dom';
|
||||
import {
|
||||
Redirect,
|
||||
Route,
|
||||
Switch,
|
||||
useHistory,
|
||||
useLocation,
|
||||
} from 'react-router-dom';
|
||||
import appState from '../AppState';
|
||||
import axiosClient from '../axiosAPIs';
|
||||
import { fetchAuthorizerConfig } from '../axiosAPIs/miscAPI';
|
||||
@ -38,6 +44,7 @@ import {
|
||||
getUserByName,
|
||||
getUsers,
|
||||
} from '../axiosAPIs/userAPI';
|
||||
import { FirstTimeUserModal } from '../components/Modals/FirstTimeUserModal/FirstTimeUserModal';
|
||||
import {
|
||||
API_RES_MAX_SIZE,
|
||||
oidcTokenKey,
|
||||
@ -72,9 +79,16 @@ const AuthProvider: FunctionComponent<AuthProviderProps> = ({
|
||||
childComponentType,
|
||||
children,
|
||||
}: AuthProviderProps) => {
|
||||
const location = useLocation();
|
||||
const history = useHistory();
|
||||
const showToast = useToastContext();
|
||||
const { isSignedIn, isSigningIn, isSignedOut } = useAuth();
|
||||
const {
|
||||
isAuthenticatedRoute,
|
||||
isFirstTimeUser,
|
||||
isSignedIn,
|
||||
isSigningIn,
|
||||
isSignedOut,
|
||||
} = useAuth(location.pathname);
|
||||
|
||||
const oidcUserToken = cookieStorage.getItem(oidcTokenKey);
|
||||
const [loading, setLoading] = useState(true);
|
||||
@ -82,11 +96,11 @@ const AuthProvider: FunctionComponent<AuthProviderProps> = ({
|
||||
{} as UserManager
|
||||
);
|
||||
const [userManagerConfig, setUserManagerConfig] = useState<
|
||||
Record<string, string | WebStorageStateStore>
|
||||
Record<string, string | boolean | WebStorageStateStore>
|
||||
>({});
|
||||
|
||||
const clearOidcUserData = (
|
||||
userConfig: Record<string, string | WebStorageStateStore>
|
||||
userConfig: Record<string, string | boolean | WebStorageStateStore>
|
||||
): void => {
|
||||
cookieStorage.removeItem(
|
||||
`oidc.user:${userConfig.authority}:${userConfig.client_id}`
|
||||
@ -198,6 +212,15 @@ const AuthProvider: FunctionComponent<AuthProviderProps> = ({
|
||||
});
|
||||
};
|
||||
|
||||
const handleFirstTourModal = (skip: boolean) => {
|
||||
appState.newUser = {} as NewUser;
|
||||
if (skip) {
|
||||
history.push(ROUTES.HOME);
|
||||
} else {
|
||||
// TODO: Route to tour page
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
fetchAuthConfig();
|
||||
|
||||
@ -241,39 +264,47 @@ const AuthProvider: FunctionComponent<AuthProviderProps> = ({
|
||||
return (
|
||||
<>
|
||||
{!loading ? (
|
||||
<Switch>
|
||||
<Route exact path={ROUTES.HOME}>
|
||||
{!isSignedIn && !isSigningIn ? (
|
||||
<Redirect to={ROUTES.SIGNIN} />
|
||||
<>
|
||||
<Switch>
|
||||
<Route exact path={ROUTES.HOME}>
|
||||
{!isSignedIn && !isSigningIn ? (
|
||||
<Redirect to={ROUTES.SIGNIN} />
|
||||
) : (
|
||||
<Redirect to={ROUTES.MY_DATA} />
|
||||
)}
|
||||
</Route>
|
||||
<Route exact component={PageNotFound} path={ROUTES.NOT_FOUND} />
|
||||
{!isSigningIn ? (
|
||||
<Route exact component={SigninPage} path={ROUTES.SIGNIN} />
|
||||
) : null}
|
||||
<Route
|
||||
path={ROUTES.CALLBACK}
|
||||
render={() => (
|
||||
<Callback
|
||||
userManager={userManager}
|
||||
onSuccess={(user) => {
|
||||
cookieStorage.setItem(oidcTokenKey, user.id_token, {
|
||||
expires: getOidcExpiry(),
|
||||
});
|
||||
fetchUserByEmail(user as OidcUser);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{isSignedOut ? <Redirect to={ROUTES.SIGNIN} /> : null}
|
||||
{oidcUserToken || !userManagerConfig?.client_id ? (
|
||||
children
|
||||
) : (
|
||||
<Redirect to={ROUTES.MY_DATA} />
|
||||
<AppWithAuth />
|
||||
)}
|
||||
</Route>
|
||||
<Route exact component={PageNotFound} path={ROUTES.NOT_FOUND} />
|
||||
{!isSigningIn ? (
|
||||
<Route exact component={SigninPage} path={ROUTES.SIGNIN} />
|
||||
</Switch>
|
||||
{isAuthenticatedRoute && isFirstTimeUser ? (
|
||||
<FirstTimeUserModal
|
||||
onCancel={() => handleFirstTourModal(true)}
|
||||
onSave={() => handleFirstTourModal(false)}
|
||||
/>
|
||||
) : null}
|
||||
<Route
|
||||
path={ROUTES.CALLBACK}
|
||||
render={() => (
|
||||
<Callback
|
||||
userManager={userManager}
|
||||
onSuccess={(user) => {
|
||||
cookieStorage.setItem(oidcTokenKey, user.id_token, {
|
||||
expires: getOidcExpiry(),
|
||||
});
|
||||
fetchUserByEmail(user as OidcUser);
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
{isSignedOut ? <Redirect to={ROUTES.SIGNIN} /> : null}
|
||||
{oidcUserToken || !userManagerConfig?.client_id ? (
|
||||
children
|
||||
) : (
|
||||
<AppWithAuth />
|
||||
)}
|
||||
</Switch>
|
||||
</>
|
||||
) : null}
|
||||
</>
|
||||
);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import classNames from 'classnames';
|
||||
import React, { FunctionComponent, useState } from 'react';
|
||||
import BGConfetti from '../../../assets/img/confetti-bg.jpeg';
|
||||
import { Button } from '../../buttons/Button/Button';
|
||||
|
||||
type Props = {
|
||||
@ -15,48 +16,44 @@ const description = [
|
||||
|
||||
export const FirstTimeUserModal: FunctionComponent<Props> = ({
|
||||
onCancel,
|
||||
onSave,
|
||||
}: Props) => {
|
||||
const [active, setActive] = useState<number>(0);
|
||||
const [lastSlide, setLastSlide] = useState<boolean>(false);
|
||||
|
||||
const previousClick = () => {
|
||||
if (lastSlide) {
|
||||
// to somthing
|
||||
} else {
|
||||
setActive((pre) => pre - 1);
|
||||
}
|
||||
setActive((pre) => pre - 1);
|
||||
setLastSlide(false);
|
||||
};
|
||||
|
||||
const nextClick = () => {
|
||||
if (lastSlide) {
|
||||
onCancel();
|
||||
} else {
|
||||
setActive((pre) => {
|
||||
const newVal = pre + 1;
|
||||
description.length - 1 === newVal && setLastSlide(true);
|
||||
setActive((pre) => {
|
||||
const newVal = pre + 1;
|
||||
setLastSlide(description.length - 1 === newVal);
|
||||
|
||||
return newVal;
|
||||
});
|
||||
}
|
||||
return newVal;
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<dialog className="tw-modal">
|
||||
<div className="tw-modal-backdrop tw-opacity-80" />
|
||||
<div className="tw-modal-container tw-max-w-xl tw-max-h-90vh tw-bg-gradient-to-bl tw-to-primary-lite tw-from-secondary-lite">
|
||||
<div className="tw-modal-header tw-border-0 tw-justify-center tw-pt-8">
|
||||
<p className="tw-modal-title tw-text-h4 tw-font-semibold tw-text-primary-active">
|
||||
<div
|
||||
className="tw-modal-container tw-modal-confetti tw-max-w-xl tw-max-h-90vh"
|
||||
style={{ backgroundImage: `url(${BGConfetti})` }}>
|
||||
<div className="tw-modal-header tw-border-0 tw-justify-center tw-pt-8 tw-pb-0">
|
||||
<p className="tw-modal-title tw-text-h4 tw-font-semibold tw-text-primary-active tw-mt-32">
|
||||
Welcome to OpenMetadata
|
||||
</p>
|
||||
</div>
|
||||
<div className="tw-modal-body tw-relative tw-h-64 tw-justify-center tw-items-center">
|
||||
<div className="tw-modal-body tw-relative tw-h-40 tw-justify-start tw-items-center">
|
||||
{description.map((d, i) => (
|
||||
<p
|
||||
className={classNames(
|
||||
i === active
|
||||
? 'tw-opacity-100 tw-relative tw-transition-opacity tw-delay-200'
|
||||
: 'tw-opacity-0 tw-absolute',
|
||||
'tw-text-xl tw-font-medium tw-text-center'
|
||||
'tw-text-xl tw-font-medium tw-text-center tw-bg-white tw-mx-7'
|
||||
)}
|
||||
key={i}>
|
||||
{d}
|
||||
@ -66,38 +63,48 @@ export const FirstTimeUserModal: FunctionComponent<Props> = ({
|
||||
<div className="tw-modal-footer tw-border-0 tw-justify-between">
|
||||
<Button
|
||||
className={classNames(
|
||||
'tw-bg-primary-active tw-text-white',
|
||||
'tw-text-primary-active',
|
||||
active === 0 ? 'tw-invisible' : null
|
||||
)}
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="contained"
|
||||
variant="text"
|
||||
onClick={previousClick}>
|
||||
{lastSlide ? (
|
||||
'Take a Tour'
|
||||
) : (
|
||||
<>
|
||||
<i className="fas fa-arrow-left tw-text-sm tw-align-middle tw-pr-1.5" />{' '}
|
||||
<span>Previous</span>
|
||||
</>
|
||||
)}
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
className="tw-bg-primary-active tw-text-white"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="contained"
|
||||
onClick={nextClick}>
|
||||
{lastSlide ? (
|
||||
'Skip and go to landing page'
|
||||
) : (
|
||||
<>
|
||||
<span>Next</span>
|
||||
<i className="fas fa-arrow-right tw-text-sm tw-align-middle tw-pl-1.5" />
|
||||
</>
|
||||
)}
|
||||
<i className="fas fa-arrow-left tw-text-sm tw-align-middle tw-pr-1.5" />{' '}
|
||||
<span>Previous</span>
|
||||
</Button>
|
||||
{lastSlide ? (
|
||||
<span>
|
||||
<Button
|
||||
className="tw-text-primary-active tw-hidden"
|
||||
size="regular"
|
||||
theme="default"
|
||||
variant="text"
|
||||
onClick={onCancel}>
|
||||
<span>Skip</span>
|
||||
<i className="fas fa-angle-double-right tw-text-sm tw-align-middle tw-pl-1.5" />
|
||||
</Button>
|
||||
<Button
|
||||
className="tw-bg-primary-active tw-text-white"
|
||||
id="take-tour"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="contained"
|
||||
onClick={onSave}>
|
||||
Explore OpenMetadata
|
||||
</Button>
|
||||
</span>
|
||||
) : (
|
||||
<Button
|
||||
className="tw-text-primary-active"
|
||||
size="regular"
|
||||
theme="primary"
|
||||
variant="text"
|
||||
onClick={nextClick}>
|
||||
<span>Next</span>
|
||||
<i className="fas fa-arrow-right tw-text-sm tw-align-middle tw-pl-1.5" />
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</dialog>
|
||||
|
@ -38,5 +38,6 @@ export const useAuth = (pathname = '') => {
|
||||
isAuthenticatedRoute: isAuthenticatedRoute,
|
||||
isAuthDisabled: authDisabled,
|
||||
isAdminUser: userDetails?.isAdmin,
|
||||
isFirstTimeUser: !isEmpty(userDetails) && !isEmpty(newUser),
|
||||
};
|
||||
};
|
||||
|
@ -40,7 +40,7 @@ const SigninPage = () => {
|
||||
<PageContainer>
|
||||
<div className="tw-w-screen tw-h-screen tw-flex tw-justify-center">
|
||||
<div className="tw-flex tw-flex-col tw-items-center signin-box">
|
||||
<div className="tw-flex tw-justify-center tw-items-center tw-my-7">
|
||||
<div className="tw-flex tw-justify-center tw-items-center tw-mb-7 tw-mt-20">
|
||||
<SVGIcons
|
||||
alt="OpenMetadata Logo"
|
||||
icon={Icons.LOGO_SMALL}
|
||||
@ -56,7 +56,7 @@ const SigninPage = () => {
|
||||
<h6 className="tw-mb-px">Centralized Metadata Store, Discover,</h6>
|
||||
<h6 className="tw-mb-px">Collaborate and get your Data Right</h6>
|
||||
</div>
|
||||
<div className="tw-mt-16" onClick={handleSignIn}>
|
||||
<div className="tw-mt-4" onClick={handleSignIn}>
|
||||
{appState.authProvider.provider === AuthTypes.GOOGLE && (
|
||||
<button className="tw-signin-button">
|
||||
<SVGIcons
|
||||
|
@ -110,11 +110,17 @@
|
||||
.tw-modal {
|
||||
@apply tw-z-9999 tw-flex tw-fixed tw-inset-0 tw-bg-transparent tw-justify-center tw-h-screen tw-w-screen tw-items-center tw-antialiased;
|
||||
}
|
||||
.tw-modal-confetti {
|
||||
background-size: cover;
|
||||
background-position-y: -75px;
|
||||
background-repeat: no-repeat;
|
||||
background-position-x: center;
|
||||
}
|
||||
.tw-modal-backdrop {
|
||||
@apply tw-opacity-60 tw-bg-body-hover tw-absolute tw-inset-0;
|
||||
}
|
||||
.tw-modal-container {
|
||||
@apply tw-flex tw-flex-col tw-absolute tw-py-5 tw-bg-white tw-w-11/12 tw-max-w-screen-lg tw-max-h-screen tw-mx-auto tw-rounded-lg tw-border tw-border-main tw-shadow-xl;
|
||||
@apply tw-flex tw-flex-col tw-absolute tw-py-5 tw-bg-white tw-w-11/12 tw-max-w-screen-lg tw-max-h-screen tw-mx-auto tw-rounded-lg tw-border tw-border-main tw-shadow-modal;
|
||||
}
|
||||
.tw-modal-header {
|
||||
@apply tw-flex tw-flex-row tw-justify-between tw-px-6 tw-pb-5 tw-bg-transparent tw-border-b tw-border-separator;
|
||||
@ -280,13 +286,11 @@
|
||||
}
|
||||
|
||||
.signin-box {
|
||||
@apply tw-m-auto tw-h-100 tw-w-120;
|
||||
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2);
|
||||
@apply tw-m-auto tw-h-100 tw-w-120 tw-bg-white tw-shadow-modal;
|
||||
}
|
||||
|
||||
.signup-box {
|
||||
@apply tw-m-auto tw-w-120 tw-bg-white;
|
||||
box-shadow: 1px 1px 5px rgba(0, 0, 0, 0.2);
|
||||
@apply tw-m-auto tw-w-120 tw-bg-white tw-shadow-modal;
|
||||
}
|
||||
|
||||
.disable-cta * {
|
||||
|
@ -11,11 +11,12 @@ export const getOidcExpiry = () => {
|
||||
|
||||
export const getUserManagerConfig = (
|
||||
authClient: Record<string, string> = {}
|
||||
): Record<string, string | WebStorageStateStore> => {
|
||||
): Record<string, string | boolean | WebStorageStateStore> => {
|
||||
const { authority, clientId, callbackUrl } = authClient;
|
||||
|
||||
return {
|
||||
authority,
|
||||
automaticSilentRenew: true,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
client_id: clientId,
|
||||
// eslint-disable-next-line @typescript-eslint/camelcase
|
||||
|
@ -22,6 +22,8 @@ const primary = '#7147E8';
|
||||
const primaryHover = '#5523E0';
|
||||
const primaryActive = '#450DE2';
|
||||
const primaryHoverLite = '#DBD1F9';
|
||||
const secondary = '#B02AAC';
|
||||
const secondaryBG = '#B02AAC40';
|
||||
|
||||
// state colors
|
||||
const success = '#51C41A';
|
||||
@ -66,6 +68,9 @@ module.exports = {
|
||||
focus: primary,
|
||||
search: '#D5D6D9',
|
||||
},
|
||||
boxShadow: {
|
||||
modal: '1px 1px 5px rgba(0, 0, 0, 0.2)',
|
||||
},
|
||||
colors: {
|
||||
'grey-body': textBody,
|
||||
'grey-muted': textMuted,
|
||||
@ -76,6 +81,8 @@ module.exports = {
|
||||
'primary-hover': primaryHover,
|
||||
'primary-active': primaryActive,
|
||||
'primary-hover-lite': primaryHoverLite,
|
||||
secondary: secondary,
|
||||
'secondary-lite': secondaryBG,
|
||||
'body-main': bodyBG,
|
||||
'body-hover': bodyHoverBG,
|
||||
tag: tagBG,
|
||||
|
@ -80,7 +80,7 @@ module.exports = {
|
||||
},
|
||||
//
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg|ico)$/i,
|
||||
test: /\.(png|jpg|jpeg|gif|svg|ico)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
|
@ -81,7 +81,7 @@ module.exports = {
|
||||
},
|
||||
//
|
||||
{
|
||||
test: /\.(png|jpg|gif|svg|ico)$/i,
|
||||
test: /\.(png|jpg|jpeg|gif|svg|ico)$/i,
|
||||
use: [
|
||||
{
|
||||
loader: 'url-loader',
|
||||
|
Loading…
x
Reference in New Issue
Block a user