diff --git a/apps/web/.env b/apps/web/.env index 3748091227f84a56cbfe836fbb081e9635fc1a5c..f651adbd016aeac3956bde79d0f2c1de67081320 100644 --- a/apps/web/.env +++ b/apps/web/.env @@ -1,2 +1,2 @@ VITE_BACKEND_URL=api.graphpolaris.com -VITE_STAGING=local \ No newline at end of file +VITE_STAGING=local diff --git a/apps/web/node.d.ts b/apps/web/node.d.ts index 0156111480f412902209f239be01e460baa8192f..9c681853c7020e2fee31b6b4eb34490d72cdf5ac 100644 --- a/apps/web/node.d.ts +++ b/apps/web/node.d.ts @@ -2,5 +2,8 @@ interface ImportMeta { env: { VITE_BACKEND_URL: string; VITE_STAGING: string; + VITE_KEYCLOAK_URL: string; + VITE_KEYCLOAK_REALM: string; + VITE_KEYCLOAK_CLIENT: string; } } \ No newline at end of file diff --git a/apps/web/src/app/app.module.scss b/apps/web/src/app/app.module.scss index 328ed5e2dc9feb139a8a9b695b717be949295c39..b04f78a7d2813bc96bf2cae6a6048793725ad4a5 100644 --- a/apps/web/src/app/app.module.scss +++ b/apps/web/src/app/app.module.scss @@ -10,7 +10,7 @@ width: 100%; max-width: 33%; height: 100%; - max-height: 70%; + max-height: 100%; } .panel { display: flex; diff --git a/apps/web/src/app/app.tsx b/apps/web/src/app/app.tsx index 6972e3473f24dfd9bdb4186d81442e32f0dc31df..eb99a9a0803cb7402b9d4bfa895b54768559e58e 100644 --- a/apps/web/src/app/app.tsx +++ b/apps/web/src/app/app.tsx @@ -1,50 +1,42 @@ -import { useEffect, useRef, useState } from 'react'; -import GridLayout from 'react-grid-layout'; -import Panel from '../components/panels/panel'; -import { RawJSONVis } from '@graphpolaris/shared/lib/vis/rawjsonvis/rawjsonvis'; -import SemanticSubstrates from '@graphpolaris/shared/lib/vis/semanticsubstrates/semanticsubstrates'; -import { Schema } from '@graphpolaris/shared/lib/schema/panel'; -import { - Query2BackendQuery, - QueryBuilder, -} from '@graphpolaris/shared/lib/querybuilder'; -import { - assignNewGraphQueryResult, - useAppDispatch, -} from '@graphpolaris/shared/lib/data-access/store'; -import { Navbar } from '../components/navbar/navbar'; -import { VisualizationPanel } from './panels/Visualization'; import { readInSchemaFromBackend, - useAuthorization, + useAuth, useAuthorizationCache, useDatabaseAPI, useQueryAPI, useQuerybuilderGraph, - useQuerybuilderGraphology, useQuerybuilderHash, useSchemaAPI, - useSessionCache, + useSessionCache } from '@graphpolaris/shared/lib/data-access'; -import LoginScreen from '../components/login/loginScreen'; import { WebSocketHandler } from '@graphpolaris/shared/lib/data-access/socket'; import Broker from '@graphpolaris/shared/lib/data-access/socket/broker'; -import { SchemaFromBackend } from '@graphpolaris/shared/lib/model/backend'; -import { domain } from '../environments/variables'; -import { QueryMultiGraphExport } from '@graphpolaris/shared/lib/querybuilder/graph/graphology/utils'; import { - GraphQueryResultFromBackend, - resetGraphQueryResults, -} from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice'; -import { clearQB } from '@graphpolaris/shared/lib/data-access/store/querybuilderSlice'; + assignNewGraphQueryResult, + useAppDispatch, +} from '@graphpolaris/shared/lib/data-access/store'; +import { GraphQueryResultFromBackend, resetGraphQueryResults } from '@graphpolaris/shared/lib/data-access/store/graphQueryResultSlice'; +import { + Query2BackendQuery, + QueryBuilder, +} from '@graphpolaris/shared/lib/querybuilder'; +import { Schema } from '@graphpolaris/shared/lib/schema/panel'; +import { useEffect, useRef, useState } from 'react'; +import { Navbar } from '../components/navbar/navbar'; +import Panel from '../components/panels/panel'; +import { domain } from '../environments/variables'; +import { VisualizationPanel } from './panels/Visualization'; import styles from './app.module.scss'; +import { logout } from '@graphpolaris/shared/lib/data-access/store/authSlice'; +import { SchemaFromBackend } from '@graphpolaris/shared/lib/schema'; export interface App { styles: any; } export function App(props: App) { - const { AuthorizeFromCache, auth } = useAuthorization(domain); + const isLogin = useAuth(); + const auth = useAuthorizationCache() const api = useDatabaseAPI(domain); const api_schema = useSchemaAPI(domain); const api_query = useQueryAPI(domain); @@ -55,9 +47,13 @@ export function App(props: App) { const ws = useRef(new WebSocketHandler(domain)); const [authCheck, setAuthCheck] = useState(false); + // for testing purposes + // useEffect(() => { + // console.info('Authentification changed', auth) + // }, [auth]); + useEffect(() => { // Default - AuthorizeFromCache(); Broker.instance().subscribe( (data: SchemaFromBackend) => dispatch(readInSchemaFromBackend(data)), 'schema_result' @@ -77,7 +73,7 @@ export function App(props: App) { useEffect(() => { // New active database - if (session.currentDatabase) { + if (auth.accessToken && session.currentDatabase) { ws.current.useToken(auth.accessToken).connect(() => { api_schema.RequestSchema(session.currentDatabase); }); @@ -86,12 +82,14 @@ export function App(props: App) { useEffect(() => { // Newly (un)authorized - console.log(auth.authorized); if (auth.authorized) { + console.info("App is authorized; Getting Datatabases", isLogin); api.GetAllDatabases({ updateSessionCache: true }); setAuthCheck(true); + } else { + dispatch(logout()) } - }, [auth.authorized]); + }, [isLogin]); useEffect(() => { // New query @@ -105,7 +103,6 @@ export function App(props: App) { return ( <div className="h-screen w-screen"> - {auth.authorized === false && <LoginScreen />} <div className={ 'flex h-screen w-screen overflow-hidden ' + diff --git a/apps/web/src/components/login/loginScreen.tsx b/apps/web/src/components/login/loginScreen.tsx deleted file mode 100644 index 82f2d7fbb27f3542f2c194dec9a511bd26eefded..0000000000000000000000000000000000000000 --- a/apps/web/src/components/login/loginScreen.tsx +++ /dev/null @@ -1,114 +0,0 @@ -import { useAuthorizationCache } from '@graphpolaris/shared/lib/data-access'; -import { useAuthorization } from '@graphpolaris/shared/lib/data-access/authorization'; -import { domain } from '../../environments/variables'; -import styled from 'styled-components'; -import EmailIcon from '@mui/icons-material/Email'; - -const Wrapper = styled.div` - font-family: Arial, serif; - position: absolute; - left: 0; - top: 0; - // Cover the screen - width: 100vw; - height: 100vh; - - display: flex; - justify-content: center; - align-items: center; -`; - -const Background = styled.div` - position: absolute; - - width: 100%; - height: 100%; - - z-index: 1; - - // Blur - background: rgba( - 0, - 0, - 0, - 0.4 - ); // Make sure this color has an opacity of less than 1 - backdrop-filter: blur(8px); // This be the blur -`; - -const Content = styled.div` - background-color: white; - box-shadow: 0 3px 10px rgb(0 0 0 / 0.2); - padding: 2em; - z-index: 2; - border-radius: 8px; - - display: flex; - flex-direction: column; - gap: 1em; - align-items: center; - justify-content: center; - - // Give children 0 padding and margin - * { - display: flex; - margin: 0; - padding: 0; - - // Same width flexbox items - flex: 1 1 0; - - max-height: 3em; - - &:hover { - cursor: pointer; - } - } -`; - -const LoginScreen = () => { - const { SetAccessToken } = useAuthorization(domain); - - const openSignInWindow = (url: string) => { - window.location.assign(url); - }; - - return ( - <Wrapper> - <Background></Background> - <Content> - <h1 className="pointer-events-none">Sign In</h1> - <img - onClick={() => - openSignInWindow( - 'https://api.graphpolaris.com/user/sign-in?provider=1&callback=https://local.graphpolaris.com:4200/' - ) - } - src="assets/login-screen/google.png" - alt="sign up with google" - /> - <img - onClick={() => - openSignInWindow( - 'https://api.graphpolaris.com/user/sign-in?provider=2&callback=https://local.graphpolaris.com:4200/' - ) - } - src="assets/login-screen/github.png" - alt="sign up with github" - /> - <button className='btn bg-gray-400 rounded-md p-1'> - <EmailIcon /> - </button> - <p - onClick={() => - openSignInWindow('https://api.graphpolaris.com/user/create_free/') - } - > - Demo - </p> - </Content> - </Wrapper> - ); -}; - -export default LoginScreen; diff --git a/apps/web/src/components/login/popup.tsx b/apps/web/src/components/login/popup.tsx deleted file mode 100644 index 73231dc6da73e40763eeed26cd9ae9e2099858c8..0000000000000000000000000000000000000000 --- a/apps/web/src/components/login/popup.tsx +++ /dev/null @@ -1,21 +0,0 @@ -const LoginPopupComponent = () => { - if (window.opener) { - // Get the access token from the query params - const urlParams = new URLSearchParams(window.location.search); - const accessToken = urlParams.get('access_token'); - - // Send the access token to the parent window - const c_event = new CustomEvent('auth_message', { - detail: { token: accessToken, origin: window.location.hostname }, - }); - window.opener.dispatchEvent(c_event); - // window.opener.postMessage(accessToken, '*'); - - // Close this window - window.close(); - } - - return <h1>Loading...</h1>; -}; - -export default LoginPopupComponent; diff --git a/apps/web/src/components/navbar/AddDatabaseForm/index.tsx b/apps/web/src/components/navbar/AddDatabaseForm/index.tsx index acef980f97760382ce522bf43ea730f54c7e9d01..c13df46c392bf8507cd4997215951d9740762db9 100644 --- a/apps/web/src/components/navbar/AddDatabaseForm/index.tsx +++ b/apps/web/src/components/navbar/AddDatabaseForm/index.tsx @@ -40,14 +40,21 @@ export interface AddDatabaseFormState extends AddDatabaseRequest { /** AddDatabaseForm is the View implementation for the connect screen that will be rendered. */ export default function AddDatabaseForm(props: AddDatabaseFormProps) { const [state, setState] = useState<AddDatabaseFormState>({ - username: 'root', - password: 'DikkeDraak', - url: 'https://datastrophe.science.uu.nl/', - port: 8529, - name: 'Tweede Kamer Dataset', - internal_database_name: 'TweedeKamer', - // styles: props.styles, // FIXME - type: DatabaseType.ArangoDB, + // username: 'root', + // password: 'DikkeDraak', + // url: 'https://datastrophe.science.uu.nl/', + // port: 8529, + // name: 'Tweede Kamer Dataset', + // internal_database_name: 'TweedeKamer', + // type: DatabaseType.ArangoDB, + + username: 'neo4j', + password: 'oL3nNlebrx4le2A0zxaFVqAo3HAvodHxwEiI_7_2JxI', + url: '635176c8.databases.neo4j.io', + port: 7687, + name: 'neo4j', + internal_database_name: 'neo4j', + type: DatabaseType.Neo4j, }); const [portValidation, setPortValidation] = useState<string>('valid'); @@ -59,6 +66,15 @@ export default function AddDatabaseForm(props: AddDatabaseFormProps) { } }, [state.port]); + useEffect(() => { + if (!state) return; + if (state.type === DatabaseType.ArangoDB && state.port === 7687) { + setState({ ...state, port: 8529 }); + } else if (state.type === DatabaseType.Neo4j && state.port === 8529) { + setState({ ...state, port: 7687 }); + } + }, [state.type]); + /** * Validates if the port value is numerical. Only then will the state be updated. * @param port The new port value. diff --git a/apps/web/src/main.tsx b/apps/web/src/main.tsx index 588f0faf3e145525e8ca14311de2ffe319c92e47..483b0d21b5718072f82dc621247151aa686e74e6 100644 --- a/apps/web/src/main.tsx +++ b/apps/web/src/main.tsx @@ -8,7 +8,6 @@ import { Provider } from 'react-redux'; import { store } from '@graphpolaris/shared/lib/data-access/store'; import { GraphPolarisThemeProvider } from '@graphpolaris/shared/lib/data-access/theme'; import App from './app/app'; -import LoginPopupComponent from './components/login/popup'; import { CssBaseline } from '@mui/material'; import { createRoot } from 'react-dom/client'; import './styles.css'; @@ -25,8 +24,6 @@ if (domNode) { <CssBaseline /> <Router> <Routes> - {/* Route to auth component in popup */} - <Route path="/auth" element={<LoginPopupComponent />}></Route> {/* App */} <Route path="/" element={<App />}></Route> </Routes> diff --git a/libs/shared/.env b/libs/shared/.env new file mode 100644 index 0000000000000000000000000000000000000000..25874d424ea58a84f5965f652718f6e2b5a4a400 --- /dev/null +++ b/libs/shared/.env @@ -0,0 +1,3 @@ +VITE_KEYCLOAK_URL=https://login.graphpolaris.com/ +VITE_KEYCLOAK_REALM=graphpolaris +VITE_KEYCLOAK_CLIENT=web \ No newline at end of file diff --git a/libs/shared/lib/data-access/api/database.ts b/libs/shared/lib/data-access/api/database.ts index 5764cfe0f92194e1b7714ef179a0f6454afbce8d..0904869942c4e9443fabf18467e685b8ccecb4ef 100644 --- a/libs/shared/lib/data-access/api/database.ts +++ b/libs/shared/lib/data-access/api/database.ts @@ -69,7 +69,7 @@ export const useDatabaseAPI = (domain: string) => { async function GetAllDatabases(options: GetDatabasesOptions = {}): Promise<Array<string>> { const { updateSessionCache: updateDatabaseCache = true } = options; - console.log(accessToken); + // console.log(accessToken); const response = await fetch(`https://${domain}/user/database`, { method: 'GET', credentials: 'same-origin', @@ -116,4 +116,4 @@ export const useDatabaseAPI = (domain: string) => { } return { DatabaseType, AddDatabase, GetAllDatabases, DeleteDatabase } -}; \ No newline at end of file +}; diff --git a/libs/shared/lib/data-access/authorization/authorizationHook.tsx b/libs/shared/lib/data-access/authorization/authorizationHook.tsx deleted file mode 100644 index d607d375ab61df6c457be2ec78a8e8b74072fe1a..0000000000000000000000000000000000000000 --- a/libs/shared/lib/data-access/authorization/authorizationHook.tsx +++ /dev/null @@ -1,189 +0,0 @@ -import { useEffect, useState } from 'react'; -import { useAppDispatch, useAuthorizationCache } from '../store'; -import { dispatch } from 'd3'; -import { authorized, unauthorized, updateAccessToken } from '../store/authSlice'; - -/** - * getNewAccessToken gets a new access token using the refresh-token cookie - * @returns an authResponse containing details - */ -async function getNewAccessToken( - domain: string, - accessToken: string -): Promise<authResponse> { - // If we have an access token already, append it to the url as a query param to keep sessionID the same - let url = `https://${domain}/auth/refresh`; - if (accessToken != '') { - url += '?access_token=' + accessToken; - } - - try { - const response = await fetch(url, { - method: 'GET', - credentials: 'include', - }); - - if (!response.ok) { - // User is not authorized - const text = await response.text(); - console.error('User UNAUTHORIZED', text); - - return { success: false }; - // throw Error(response.statusText); - } - - const json = await response.json(); - return { - success: true, - accessToken: json.accessToken, - userID: json.userID, - sessionID: json.sessionID, - }; - } catch (error) { - console.error('Error authorizing user', error); - return { success: false }; - } -} - -type authResponse = { - success: boolean; - accessToken?: string; - userID?: string; - sessionID?: string; -}; - -export type useIsAuthorizedState = { - userID?: string; - sessionID?: string; - accessToken: string; - authorized?: boolean; -}; - -export function useAuthorization(domain: string) { - const dispatch = useAppDispatch(); - const auth = useAuthorizationCache(); - - /** - * refreshTokens refreshes tokens - */ - async function refreshTokens() { - console.log('refreshing tokens'); - // Get a new access + refresh token pair - const authResponse = await getNewAccessToken(domain, auth.accessToken); - - if (authResponse.success) { - // Set the new access token - dispatch(updateAccessToken(authResponse.accessToken ?? '')); - - if (auth.accessToken !== '') { - // Initialize the new refresh token - initializeRefreshToken(); - } - } - } - - // /** - // * initializeRefreshToken attempts to initialize a refresh token - // */ - // async function refreshRefreshToken() { - // fetch( - // 'https://${domain}/auth/refresh?access_token=' + - // state.accessToken, - // { - // method: 'GET', - // credentials: 'include', - // } - // ) - // .then((response) => { - // if (!response.ok) { - // throw Error(response.statusText); - // } - // }) - // .catch((error) => { - // console.error(error); - // }); - // } - - /** - * initializeRefreshToken attempts to initialize a refresh token - */ - async function initializeRefreshToken() { - fetch(`https://${domain}/auth/refresh`, { - method: 'POST', - credentials: 'include', - }) - .then((response) => { - if (!response.ok) { - console.error(response.statusText); - // throw Error(response.statusText); - } - }) - .catch((error) => { - console.error(error); - }); - } - - // MARK: Setters - /** - * SetAccessToken sets the current access token (should only be called by the sign-in component) - * @param accessToken - */ - async function SetAccessToken(accessToken: string) { - dispatch(updateAccessToken(accessToken)); - console.log('set access token', auth.accessToken); - - const result = await AuthorizeFromCache(); - if (result) { - // Activate the refresh token TODO - initializeRefreshToken(); - - // Start the automatic refreshing every 10 minutes - setInterval(() => { - refreshTokens(); - }, 10 * 60 * 1000); - } else { - console.error( - 'Failed Logging in due to refresh token initialization', - result - ); - // throw Error('Failed Logging in'); - } - } - - /** - * Authorize attempts to authorize using a refresh-token set as a cookie. If the user has been inactive for more than 7 days this cookie will be gone. - * @returns true is authorization was successful, else returns false - */ - async function AuthorizeFromCache(): Promise<boolean> { - // Attempt to log in with a refresh-token - const authResponse = await getNewAccessToken(domain, auth.accessToken); - - // If the request was a success, we have an accessToken, userID and sessionID - if (authResponse.success) { - console.log('SUCCESS authorize'); - - console.log(auth); - // Store them - await dispatch( - authorized({ - userID: authResponse.userID ?? '', - sessionID: authResponse.sessionID ?? '', - accessToken: authResponse.accessToken ?? '', - authorized: true, - }) - ); - } else { - await dispatch( - unauthorized() - ); - } - - return authResponse.success; - } - - return { - AuthorizeFromCache, - SetAccessToken, - auth, - }; -} diff --git a/libs/shared/lib/data-access/authorization/index.ts b/libs/shared/lib/data-access/authorization/index.ts index 521daf5096bc7c9567fca3db52dea7cd6db6a00b..d9ae7204eb493add99673b64e2249652d3adbcd3 100644 --- a/libs/shared/lib/data-access/authorization/index.ts +++ b/libs/shared/lib/data-access/authorization/index.ts @@ -1 +1 @@ -export * from './authorizationHook'; \ No newline at end of file +export * from './useAuth'; diff --git a/libs/shared/lib/data-access/authorization/useAuth.jsx b/libs/shared/lib/data-access/authorization/useAuth.jsx new file mode 100644 index 0000000000000000000000000000000000000000..644abac87fcf2c7637ecfcf35e27b9b86a69e904 --- /dev/null +++ b/libs/shared/lib/data-access/authorization/useAuth.jsx @@ -0,0 +1,71 @@ +import Keycloak from 'keycloak-js'; +import { useEffect, useRef, useState } from 'react'; +import { useAppDispatch } from '../store'; + +import { authorized } from '../store/authSlice'; + +export const useAuth = () => { + const keycloak = new Keycloak({ + // url: import.meta.env.VITE_KEYCLOAK_URL, + // realm: import.meta.env.VITE_KEYCLOAK_REALM, + // clientId: import.meta.env.VITE_KEYCLOAK_CLIENT, + //TODO: remove this hardcode + url: 'https://keycloak.graphpolaris.com', + realm: 'graphpolaris', + clientId: 'web', + }); + + const isRun = useRef(false); + const [isLogin, setLogin] = useState(false); + const dispatch = useAppDispatch(); + + useEffect(() => { + if (isRun.current) return; + + isRun.current = true; + keycloak + .init({ + onLoad: 'login-required', + enableLogging: false, + checkLoginIframe: false, + }) + .then(async (isAuthenticated) => { + // console.log("useAuth useEffect", isAuthenticated, keycloak.idTokenParsed); + setLogin(isAuthenticated); + + // just for example here: + // const profile = await getUserProfile(); + // console.log("useAuth useEffect profile", profile); + + await dispatch( + authorized({ + // Info from https://www.keycloak.org/docs/latest/securing_apps/ + userID: keycloak.idTokenParsed.preferred_username ?? '', + sessionID: keycloak.idTokenParsed.sid ?? '', + accessToken: keycloak.token ?? '', + authorized: true, + }) + ); + }) + .catch((err) => { + console.error('useAuth err', err); + throw err; + }); + }, []); + + const getUserProfile = async () => { + return keycloak + .loadUserProfile() + .then(function (profile) { + return profile; + // console.info("user info", JSON.stringify(profile, null, " ")) + }) + .catch(function () { + alert('Failed to load user profile'); + }); + }; + + return isLogin; +}; + +// export useAuth; diff --git a/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx b/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx index 7645f8c008b6742189438fa22986d30cb5d2d3c4..55669800e32c4663b87e19005ada5bc2cf1bc1ae 100644 --- a/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx +++ b/libs/shared/lib/data-access/socket/backend-message-receiver/WebSocketHandler.tsx @@ -34,7 +34,7 @@ export class WebSocketHandler implements BackendMessageReceiver { if (this.webSocket) this.close(); this.webSocket = new WebSocket( - this.url + (!!this.token ? `?jwt=${this.token}` : '') + this.url + (!!this.token ? `?jwt=${encodeURIComponent(this.token)}` : '') ); this.webSocket.onopen = () => onOpen(); this.webSocket.onmessage = this.onWebSocketMessage; diff --git a/libs/shared/lib/data-access/store/authSlice.ts b/libs/shared/lib/data-access/store/authSlice.ts index 19dbb398142df1a67ce1638ee3b614a9276ab5fa..b088f1dcf1dd6ae23dd97bfec76ed0532e8ad18f 100644 --- a/libs/shared/lib/data-access/store/authSlice.ts +++ b/libs/shared/lib/data-access/store/authSlice.ts @@ -7,7 +7,9 @@ import { useIsAuthorizedState } from '../authorization'; // Define the initial state using that type export const initialState: useIsAuthorizedState = { authorized: undefined, - accessToken: '', + accessToken: undefined, + sessionID: undefined, + userID: undefined, }; export const authSlice = createSlice({ @@ -24,6 +26,12 @@ export const authSlice = createSlice({ state.sessionID = action.payload.sessionID; state.userID = action.payload.userID; }, + logout(state) { + state.authorized = undefined; + state.accessToken = undefined; + state.sessionID = undefined; + state.userID = undefined; + }, unauthorized(state) { state.authorized = false; } @@ -31,7 +39,7 @@ export const authSlice = createSlice({ }); export const { - updateAccessToken, authorized, unauthorized + updateAccessToken, authorized, unauthorized, logout } = authSlice.actions; // Other code such as selectors can use the imported `RootState` type diff --git a/libs/shared/lib/schema/panel/schema.module.scss b/libs/shared/lib/schema/panel/schema.module.scss index aeedac18dc5ce359147965ce1b4fd4b9036a5be9..687360fdff5cafcff20f66cacb10c2f03f8d2690 100644 --- a/libs/shared/lib/schema/panel/schema.module.scss +++ b/libs/shared/lib/schema/panel/schema.module.scss @@ -15,6 +15,7 @@ .schemaPanel { width: 100%; + height: 100%; // display: 'flex'; // flex-direction: 'column'; // justify-content: 'center'; diff --git a/libs/shared/lib/schema/panel/schema.tsx b/libs/shared/lib/schema/panel/schema.tsx index 2787b279fc924b0e52d42dd024cd10f0837df276..de9362d13f6e20c90eaa202b1e1976beb06b0f19 100644 --- a/libs/shared/lib/schema/panel/schema.tsx +++ b/libs/shared/lib/schema/panel/schema.tsx @@ -96,8 +96,8 @@ export const Schema = (props: Props) => { // console.log('dbSchema', schemaGraphology, schemaGraphology.order); // }, [schemaGraphology]); - const toggleNodeQualityPopup = (id: string) => {}; - const toggleAttributeAnalyticsPopupMenu = (id: string) => {}; + const toggleNodeQualityPopup = (id: string) => { }; + const toggleAttributeAnalyticsPopupMenu = (id: string) => { }; function updateLayout() { const layoutFactory = new LayoutFactory(); @@ -182,21 +182,39 @@ export const Schema = (props: Props) => { // ); }, [schemaGraph, schemaLayout]); - const graphStyles = { width: '100%', height: '500px' }; - // console.log(nodes, edges); return ( - <div - style={{ - width: '100%', - height: '100%', - }} - > + <div className={styles.schemaPanel} > + {firstUserConnection && ( + <Card + variant="outlined" + sx={{ + width: '20rem', + marginTop: 3, + zIndex: 9, + backgroundColor: '#ffffff', + position: 'absolute', + }} + > + <CardContent> + <Typography sx={{ fontSize: 20 }} color="text.secondary"> + Press "space" while you move the schema + </Typography> + <LinearProgress + sx={{ + color: (theme) => + theme.palette.grey[ + theme.palette.mode === 'light' ? 200 : 800 + ], + }} + /> + </CardContent> + </Card> + )} {nodes.length === 0 && <p>DEBUG: No Elements</p>} <ReactFlowProvider> <ReactFlow - className={styles.schemaPanel} onlyRenderVisibleElements={false} nodesDraggable={false} nodeTypes={nodeTypes} @@ -206,39 +224,10 @@ export const Schema = (props: Props) => { onEdgesChange={onEdgeChanged} nodes={nodes} edges={edges} - style={graphStyles} onInit={onInit} panOnDrag={false} attributionPosition="top-right" > - {firstUserConnection && ( - <Card - variant="outlined" - sx={{ - minWidth: 275, - marginTop: 3, - marginRight: 10, - zIndex: 9, - backgroundColor: '#ffffff', - position: 'inherit', - }} - > - <CardContent> - <Typography sx={{ fontSize: 20 }} color="text.secondary"> - Press "space" while you move the schema - </Typography> - <LinearProgress - sx={{ - color: (theme) => - theme.palette.grey[ - theme.palette.mode === 'light' ? 200 : 800 - ], - }} - /> - </CardContent> - </Card> - )} - <Controls showInteractive={false} showZoom={false} diff --git a/libs/shared/package.json b/libs/shared/package.json index febc1b7a286832820c365fe18dbee69ef6c5b1fe..7854a1b6c4d581f85554c07b7d00e4bfbf53be96 100644 --- a/libs/shared/package.json +++ b/libs/shared/package.json @@ -43,6 +43,7 @@ "graphology-types": "^0.24.7", "immer": "^10.0.2", "jspdf": "^2.5.1", + "keycloak-js": "^21.1.1", "pixi.js": "^7.1.4", "react-cookie": "^4.1.1", "react-grid-layout": "^1.3.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b9a9b42880da5de2a4f689f5e04e0cd35dc5e11a..1ffca810838bf8a300fb78a95a3bd4a35fc8a777 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -247,6 +247,9 @@ importers: jspdf: specifier: ^2.5.1 version: 2.5.1 + keycloak-js: + specifier: ^21.1.1 + version: 21.1.1 pixi.js: specifier: ^7.1.4 version: 7.2.1(@pixi/utils@7.2.1) @@ -7494,7 +7497,6 @@ packages: /base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} - dev: true /better-opn@2.1.1: resolution: {integrity: sha512-kIPXZS5qwyKiX/HcRvDYfmBQUa8XP17I0mYZZ0y4UhpYOSvtsLHDYqmomS+Mj20aDvD3knEiQ0ecQy2nhio3yA==} @@ -11000,6 +11002,10 @@ packages: resolution: {integrity: sha512-8wb9Yw966OSxApiCt0K3yNJL8pnNeIv+OEq2YMidz4FKP6nonSRoOXc80iXY4JaN2FC11B9qsNmDsm+ZOfMROA==} dev: true + /js-sha256@0.9.0: + resolution: {integrity: sha512-sga3MHh9sgQN2+pJ9VYZ+1LPwXOxuBJBA5nrR5/ofPfuiJBE2hnjsaN8se8JznOmGLN2p49Pe5U/ttafcs/apA==} + dev: false + /js-tokens@4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} @@ -11175,6 +11181,13 @@ packages: resolution: {integrity: sha512-WbCVYJ27Sz8zi9Q7Q0xHC+05iwkm3Znipc2XTlrnJbsHMYktW4hPhXUE8Ys1engBrvffoSCqbil1JQAa7clRpA==} dev: false + /keycloak-js@21.1.1: + resolution: {integrity: sha512-Viyhf0SOpu2jM/A33vpigSCFLo8l4yg8lqzaGyxXoZ3nGO9lo68B2LwJBDtgpzqDUh8DK//yCOzdWuR2CT4keA==} + dependencies: + base64-js: 1.5.1 + js-sha256: 0.9.0 + dev: false + /kind-of@6.0.3: resolution: {integrity: sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==} engines: {node: '>=0.10.0'}