diff --git a/.env.development b/.env.development index 1451cfd..2f1d4e1 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1 @@ REACT_APP_USE_AUTHENTICATION=false - -REACT_APP_SRV_STUDY_URI=study-server diff --git a/README.md b/README.md index 11e6ab7..8b497be 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ This template setup the authentication mechanism and provides a configured empty To customize this repository for an app, search and replace the string `XXX` with the name of the app. For example, GridXXX -> GridFoo, gridXXX-app -> gridfoo-app. -Create a new view in study-server and replace `yyy` with the new token in rest api `src/rest/study.ts`. +Create a new view in study-server and replace `yyy` with the new token in rest api `src/services/index.ts`. ## Typescript config diff --git a/src/components/app-top-bar.tsx b/src/components/app-top-bar.tsx index d4458f6..b75b87e 100644 --- a/src/components/app-top-bar.tsx +++ b/src/components/app-top-bar.tsx @@ -11,23 +11,24 @@ import React, { useEffect, useState, } from 'react'; -import { - AppMetadataCommon, - LIGHT_THEME, - logout, - TopBar, - UserManagerState, -} from '@gridsuite/commons-ui'; import Parameters, { useParameterState } from './parameters'; -import { APP_NAME, PARAM_LANGUAGE, PARAM_THEME } from '../utils/config-params'; +import { APP_NAME } from '../utils/config-params'; import { useDispatch, useSelector } from 'react-redux'; -import { fetchAppsAndUrls, fetchVersion } from '../utils/rest-api'; -import { getServersInfos } from '../rest/study'; +import { appsMetadataSrv, studySrv } from '../services'; import { useNavigate } from 'react-router-dom'; import { ReactComponent as PowsyblLogo } from '../images/powsybl_logo.svg'; import AppPackage from '../../package.json'; import { AppState } from '../redux/reducer'; import { AppDispatch } from '../redux/store'; +import { + AppMetadataCommon, + LIGHT_THEME, + logout, + PARAM_LANGUAGE, + PARAM_THEME, + TopBar, + UserManagerState, +} from '@gridsuite/commons-ui'; export type AppTopBarProps = { user?: AppState['user']; @@ -50,14 +51,29 @@ const AppTopBar: FunctionComponent = (props) => { const [showParameters, setShowParameters] = useState(false); const displayParameters = useCallback(() => setShowParameters(true), []); const hideParameters = useCallback(() => setShowParameters(false), []); + const onLogoutClick = useCallback( + () => logout(dispatch, props.userManager.instance), + [dispatch, props.userManager.instance] + ); useEffect(() => { if (props.user !== undefined) { - fetchAppsAndUrls().then((res) => { + appsMetadataSrv.fetchAppsMetadata().then((res) => { setAppsAndUrls(res); }); } }, [props.user]); + const globalVersionFetcher = useCallback( + () => + appsMetadataSrv + .fetchVersion() + .then((res) => res?.deployVersion ?? 'unknown'), + [] + ); + const additionalModulesFetcher = useCallback( + () => studySrv.getServersInfos('yyy'), + [] + ); return ( <> @@ -74,18 +90,12 @@ const AppTopBar: FunctionComponent = (props) => { appVersion={AppPackage.version} appLicense={AppPackage.license} onParametersClick={displayParameters} - onLogoutClick={() => - logout(dispatch, props.userManager.instance) - } + onLogoutClick={onLogoutClick} onLogoClick={() => navigate('/', { replace: true })} user={props.user} appsAndUrls={appsAndUrls} - globalVersionPromise={() => - fetchVersion().then( - (res) => res?.deployVersion ?? 'unknown' - ) - } - additionalModulesPromise={getServersInfos} + globalVersionPromise={globalVersionFetcher} + additionalModulesPromise={additionalModulesFetcher} onThemeClick={handleChangeTheme} theme={themeLocal} onLanguageClick={handleChangeLanguage} diff --git a/src/components/app-wrapper.tsx b/src/components/app-wrapper.tsx index 0803483..da0a8c4 100644 --- a/src/components/app-wrapper.tsx +++ b/src/components/app-wrapper.tsx @@ -22,6 +22,7 @@ import { LIGHT_THEME, login_en, login_fr, + PARAM_THEME, SnackbarProvider, top_bar_en, top_bar_fr, @@ -34,7 +35,6 @@ import messages_fr from '../translations/fr.json'; import messages_plugins_en from '../plugins/translations/en.json'; import messages_plugins_fr from '../plugins/translations/fr.json'; import { store } from '../redux/store'; -import { PARAM_THEME } from '../utils/config-params'; import { IntlConfig } from 'react-intl/src/types'; import { AppState } from '../redux/reducer'; diff --git a/src/components/app.tsx b/src/components/app.tsx index ae32ea5..f8a2e99 100644 --- a/src/components/app.tsx +++ b/src/components/app.tsx @@ -25,9 +25,15 @@ import { Box, Typography } from '@mui/material'; import { AuthenticationRouter, CardErrorBoundary, + COMMON_APP_NAME, + ConfigParameters, + getComputedLanguage, + getErrorMessage, getPreLoginPath, initializeAuthenticationDev, initializeAuthenticationProd, + PARAM_LANGUAGE, + PARAM_THEME, useSnackMessage, } from '@gridsuite/commons-ui'; import { @@ -37,23 +43,14 @@ import { } from '../redux/actions'; import { AppState } from '../redux/reducer'; import { - ConfigParameters, - connectNotificationsWsUpdateConfig, - fetchConfigParameter, - fetchConfigParameters, - fetchIdpSettings, - fetchValidateUser, -} from '../utils/rest-api'; -import { - APP_NAME, - COMMON_APP_NAME, - PARAM_LANGUAGE, - PARAM_THEME, -} from '../utils/config-params'; -import { getComputedLanguage } from '../utils/language'; + appLocalSrv, + configNotificationSrv, + configSrv, + userAdminSrv, +} from '../services'; +import { APP_NAME } from '../utils/config-params'; import AppTopBar, { AppTopBarProps } from './app-top-bar'; import ReconnectingWebSocket from 'reconnecting-websocket'; -import { getErrorMessage } from '../utils/error'; import { AppDispatch } from '../redux/store'; const App: FunctionComponent = () => { @@ -107,11 +104,15 @@ const App: FunctionComponent = () => { const connectNotificationsUpdateConfig = useCallback((): ReconnectingWebSocket => { - const ws = connectNotificationsWsUpdateConfig(); + const ws = + configNotificationSrv.connectNotificationsWsUpdateConfig( + APP_NAME + ); ws.onmessage = function (event) { let eventData = JSON.parse(event.data); if (eventData.headers?.parameterName) { - fetchConfigParameter(eventData.headers.parameterName) + configSrv + .fetchConfigParameter(eventData.headers.parameterName) .then((param) => updateParams([param])) .catch((error) => snackError({ @@ -152,8 +153,8 @@ const App: FunctionComponent = () => { ? initializeAuthenticationProd( dispatch, initialMatchSilentRenewCallbackUrl != null, - fetchIdpSettings, - fetchValidateUser, + appLocalSrv.fetchIdpSettings, + userAdminSrv.fetchValidateUser, initialMatchSigninCallbackUrl != null ) : initializeAuthenticationDev( @@ -182,7 +183,8 @@ const App: FunctionComponent = () => { useEffect(() => { if (user !== undefined) { - fetchConfigParameters(COMMON_APP_NAME) + configSrv + .fetchConfigParameters(COMMON_APP_NAME) .then((params) => updateParams(params)) .catch((error) => snackError({ @@ -191,7 +193,8 @@ const App: FunctionComponent = () => { }) ); - fetchConfigParameters(APP_NAME) + configSrv + .fetchConfigParameters(APP_NAME) .then((params) => updateParams(params)) .catch((error) => snackError({ diff --git a/src/components/parameters.tsx b/src/components/parameters.tsx index b46df2b..34dacad 100644 --- a/src/components/parameters.tsx +++ b/src/components/parameters.tsx @@ -29,7 +29,7 @@ import { TypographyTypeMap, } from '@mui/material'; import { CSSObject, Theme } from '@emotion/react'; -import { updateConfigParameter } from '../utils/rest-api'; +import { configSrv } from '../services'; import { useSnackMessage } from '@gridsuite/commons-ui'; import { AppState, AppStateKey } from '../redux/reducer'; @@ -62,7 +62,8 @@ export function useParameterState( const handleChangeParamLocalState = useCallback( (value: AppState[K]) => { setParamLocalState(value); - updateConfigParameter(paramName, value as string) //TODO how to check/cast? + configSrv + .updateConfigParameter(paramName, value as string) //TODO how to check/cast? .catch((error) => { setParamLocalState(paramGlobalState); snackError({ diff --git a/src/react-app-env.d.ts b/src/react-app-env.d.ts index a3e466c..59cdbc2 100644 --- a/src/react-app-env.d.ts +++ b/src/react-app-env.d.ts @@ -1,10 +1,12 @@ -/** - * Copyright (c) 2024, RTE (http://www.rte-france.com) +/* + * Copyright © 2024, RTE (http://www.rte-france.com) * This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +import { UrlString } from '@gridsuite/commons-ui'; + /// import { UrlString } from '@gridsuite/commons-ui'; @@ -16,8 +18,8 @@ type EnvDev = { type EnvProd = { REACT_APP_USE_AUTHENTICATION: false; - REACT_APP_API_GATEWAY: string; - REACT_APP_WS_GATEWAY: string; + REACT_APP_API_GATEWAY: UrlString; + REACT_APP_WS_GATEWAY: UrlString; }; type EnvCompile = EnvProd | EnvDev; diff --git a/src/redux/actions.ts b/src/redux/actions.ts index 65aa3cf..22c2ec6 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -5,8 +5,7 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { GsTheme } from '@gridsuite/commons-ui'; -import { PARAM_LANGUAGE } from '../utils/config-params'; +import { GsTheme, PARAM_LANGUAGE } from '@gridsuite/commons-ui'; import { Action } from 'redux'; import { AppState } from './reducer'; diff --git a/src/redux/local-storage.ts b/src/redux/local-storage.ts deleted file mode 100644 index f38ddab..0000000 --- a/src/redux/local-storage.ts +++ /dev/null @@ -1,44 +0,0 @@ -/** - * Copyright (c) 2020, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { - DARK_THEME, - GsLang, - GsLangUser, - GsTheme, - LANG_SYSTEM, -} from '@gridsuite/commons-ui'; -import { getComputedLanguage } from '../utils/language'; -import { APP_NAME } from '../utils/config-params'; - -const LOCAL_STORAGE_THEME_KEY = (APP_NAME + '_THEME').toUpperCase(); -const LOCAL_STORAGE_LANGUAGE_KEY = (APP_NAME + '_LANGUAGE').toUpperCase(); - -export function getLocalStorageTheme() { - return ( - (localStorage.getItem(LOCAL_STORAGE_THEME_KEY) as GsTheme) || DARK_THEME - ); -} - -export function saveLocalStorageTheme(theme: GsTheme): void { - localStorage.setItem(LOCAL_STORAGE_THEME_KEY, theme); -} - -export function getLocalStorageLanguage() { - return ( - (localStorage.getItem(LOCAL_STORAGE_LANGUAGE_KEY) as GsLang) || - LANG_SYSTEM - ); -} - -export function saveLocalStorageLanguage(language: GsLang): void { - localStorage.setItem(LOCAL_STORAGE_LANGUAGE_KEY, language); -} - -export function getLocalStorageComputedLanguage(): GsLangUser { - return getComputedLanguage(getLocalStorageLanguage()); -} diff --git a/src/redux/reducer.ts b/src/redux/reducer.ts index cda395c..ffd6be8 100644 --- a/src/redux/reducer.ts +++ b/src/redux/reducer.ts @@ -6,12 +6,6 @@ */ import { createReducer, Draft } from '@reduxjs/toolkit'; -import { - getLocalStorageComputedLanguage, - getLocalStorageLanguage, - getLocalStorageTheme, - saveLocalStorageTheme, -} from './local-storage'; import { ComputedLanguageAction, LanguageAction, @@ -23,12 +17,18 @@ import { AuthenticationActions, AuthenticationRouterErrorAction, AuthenticationRouterErrorState, + getLocalStorageComputedLanguage, + getLocalStorageLanguage, + getLocalStorageTheme, GsLang, GsLangUser, GsTheme, LOGOUT_ERROR, LogoutErrorAction, + PARAM_LANGUAGE, + PARAM_THEME, RESET_AUTHENTICATION_ROUTER_ERROR, + saveLocalStorageTheme, SHOW_AUTH_INFO_LOGIN, ShowAuthenticationRouterLoginAction, SIGNIN_CALLBACK_ERROR, @@ -40,9 +40,9 @@ import { UserAction, UserValidationErrorAction, } from '@gridsuite/commons-ui'; -import { PARAM_LANGUAGE, PARAM_THEME } from '../utils/config-params'; import { ReducerWithInitialState } from '@reduxjs/toolkit/dist/createReducer'; import { User } from 'oidc-client'; +import { APP_NAME } from '../utils/config-params'; export type AppState = { user: User | undefined; @@ -63,9 +63,9 @@ const initialState: AppState = { showAuthenticationRouterLogin: false, // params - [PARAM_THEME]: getLocalStorageTheme(), - [PARAM_LANGUAGE]: getLocalStorageLanguage(), - computedLanguage: getLocalStorageComputedLanguage(), + [PARAM_THEME]: getLocalStorageTheme(APP_NAME), + [PARAM_LANGUAGE]: getLocalStorageLanguage(APP_NAME), + computedLanguage: getLocalStorageComputedLanguage(APP_NAME), }; export type Actions = @@ -83,7 +83,7 @@ export const reducer: ReducerWithInitialState = createReducer( SELECT_THEME, (state: Draft, action: ThemeAction) => { state.theme = action.theme; - saveLocalStorageTheme(state.theme); + saveLocalStorageTheme(APP_NAME, state.theme); } ); diff --git a/src/redux/store.ts b/src/redux/store.ts index 89ab5af..a529a3c 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -6,6 +6,12 @@ */ import { createStore, Store } from 'redux'; import { Actions, AppState, reducer } from './reducer'; +import { initCommonServices } from '@gridsuite/commons-ui'; +import { APP_NAME } from '../utils/config-params'; export const store: Store = createStore(reducer); export type AppDispatch = typeof store.dispatch; + +export function getUser() { + return store.getState().user; +} diff --git a/src/rest/study.ts b/src/rest/study.ts deleted file mode 100644 index ae23221..0000000 --- a/src/rest/study.ts +++ /dev/null @@ -1,30 +0,0 @@ -/** - * Copyright (c) 2023, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ -import { GridSuiteModule } from '@gridsuite/commons-ui'; -import { backendFetchJson } from '../utils/rest-api'; -import { getErrorMessage } from '../utils/error'; - -const API_URL = - '/api/' + - (process.env.REACT_APP_USE_AUTHENTICATION === 'true' - ? `${process.env.REACT_APP_API_GATEWAY}/study/v1` - : `${process.env.REACT_APP_SRV_STUDY_URI}/v1`); - -export function getServersInfos() { - return backendFetchJson(`${API_URL}/servers/about?view=yyy`, { - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - cache: 'default', - }).catch((error) => { - console.error( - `Error while fetching the servers infos : ${getErrorMessage(error)}` - ); - throw error; - }) as Promise; -} diff --git a/src/services/app-local.ts b/src/services/app-local.ts new file mode 100644 index 0000000..cce6482 --- /dev/null +++ b/src/services/app-local.ts @@ -0,0 +1,19 @@ +/* + * Copyright © 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ +import { AppLocalComSvc, Env } from '@gridsuite/commons-ui'; + +export type EnvJson = Env & typeof import('../../public/env.json'); + +export default class AppLocalSvc extends AppLocalComSvc { + public constructor() { + super(); + } + + public async fetchEnv(): Promise { + return (await super.fetchEnv()) as EnvJson; + } +} diff --git a/src/services/index.ts b/src/services/index.ts new file mode 100644 index 0000000..8934647 --- /dev/null +++ b/src/services/index.ts @@ -0,0 +1,70 @@ +/* + * Copyright © 2024, RTE (http://www.rte-france.com) + * This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. + */ + +import { + AppsMetadataComSvc, + ConfigComSvc, + ConfigNotificationComSvc, + DirectoryComSvc, + ExploreComSvc, + setCommonServices, + StudyComSvc, + UrlString, + UserAdminComSvc, +} from '@gridsuite/commons-ui'; +import AppLocalSvc from './app-local'; +import { getUser } from '../redux/store'; +import { APP_NAME } from '../utils/config-params'; + +export type { EnvJson } from './app-local'; + +// If you want to use user-admin-server in dev mode you must avoid passing through gateway +// and use the user-admin-server directly. SetupProxy should allow this. +// const PREFIX_USER_ADMIN_SERVER_QUERIES = +// process.env.REACT_APP_API_PREFIX + +// (process.env.REACT_APP_USE_AUTHENTICATION === 'true' +// ? `${process.env.REACT_APP_API_GATEWAY}/user-admin` +// : process.env.REACT_APP_USER_ADMIN_URI); + +export const appLocalSrv = new AppLocalSvc(), + appsMetadataSrv = new AppsMetadataComSvc(appLocalSrv), + configSrv = new ConfigComSvc( + APP_NAME, + getUser, + process.env.REACT_APP_API_GATEWAY as UrlString + ), + configNotificationSrv = new ConfigNotificationComSvc( + getUser, + process.env.REACT_APP_WS_GATEWAY as UrlString + ), + directorySrv = new DirectoryComSvc( + getUser, + process.env.REACT_APP_API_GATEWAY as UrlString + ), + exploreSrv = new ExploreComSvc( + getUser, + process.env.REACT_APP_API_GATEWAY as UrlString + ), + studySrv = new StudyComSvc( + getUser, + process.env.REACT_APP_API_GATEWAY as UrlString + ), + userAdminSrv = new UserAdminComSvc( + getUser, + process.env.REACT_APP_API_GATEWAY as UrlString + ); + +setCommonServices( + appLocalSrv, + appsMetadataSrv, + configSrv, + configNotificationSrv, + directorySrv, + exploreSrv, + studySrv, + userAdminSrv +); diff --git a/src/setupProxy.js b/src/setupProxy.js index 4b47f9b..7b1b15b 100644 --- a/src/setupProxy.js +++ b/src/setupProxy.js @@ -18,9 +18,5 @@ module.exports = function (app) { ws: true, }) ); - app.use( - createProxyMiddleware('http://localhost:5001/api/study-server', { - pathRewrite: { '^/api/study-server/': '/' }, - }) - ); + // TODO: redirect localhost:9000/api/gateway/study directly to http://localhost:5001/ }; diff --git a/src/utils/config-params.ts b/src/utils/config-params.ts index 30807f3..bf6eaf7 100644 --- a/src/utils/config-params.ts +++ b/src/utils/config-params.ts @@ -5,27 +5,4 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -export const COMMON_APP_NAME = 'common'; export const APP_NAME = 'XXX'; - -export const PARAM_THEME = 'theme'; -export const PARAM_LANGUAGE = 'language'; - -const COMMON_CONFIG_PARAMS_NAMES = new Set([PARAM_THEME, PARAM_LANGUAGE]); - -export type AppConfigParameter = - | typeof PARAM_THEME - | typeof PARAM_LANGUAGE - | string; - -export type AppConfigType = typeof COMMON_APP_NAME | typeof APP_NAME; - -/** - * Permit knowing if a parameter is common/shared between webapps or is specific to this application. - * @param paramName the parameter name/key - */ -export function getAppName(paramName: AppConfigParameter): AppConfigType { - return COMMON_CONFIG_PARAMS_NAMES.has(paramName) - ? COMMON_APP_NAME - : APP_NAME; -} diff --git a/src/utils/error.ts b/src/utils/error.ts deleted file mode 100644 index c8959d9..0000000 --- a/src/utils/error.ts +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright (c) 2024, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -export function getErrorMessage(error: unknown): string | null { - if (error instanceof Error) { - return error.message; - } else if (error instanceof Object && 'message' in error) { - if ( - typeof error.message === 'string' || - typeof error.message === 'number' || - typeof error.message === 'boolean' - ) { - return `${error.message}`; - } else { - return JSON.stringify(error.message ?? undefined) ?? null; - } - } else if (typeof error === 'string') { - return error; - } else { - return JSON.stringify(error ?? undefined) ?? null; - } -} diff --git a/src/utils/language.ts b/src/utils/language.ts deleted file mode 100644 index aa7003a..0000000 --- a/src/utils/language.ts +++ /dev/null @@ -1,27 +0,0 @@ -/** - * Copyright (c) 2021, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { - GsLang, - GsLangUser, - LANG_ENGLISH, - LANG_FRENCH, - LANG_SYSTEM, -} from '@gridsuite/commons-ui'; - -const supportedLanguages = [LANG_FRENCH, LANG_ENGLISH]; - -export function getSystemLanguage(): GsLangUser { - const systemLanguage = navigator.language.split(/[-_]/)[0]; - return supportedLanguages.includes(systemLanguage) - ? (systemLanguage as GsLangUser) - : LANG_ENGLISH; -} - -export function getComputedLanguage(language: GsLang) { - return language === LANG_SYSTEM ? getSystemLanguage() : language; -} diff --git a/src/utils/rest-api.ts b/src/utils/rest-api.ts deleted file mode 100644 index df1728f..0000000 --- a/src/utils/rest-api.ts +++ /dev/null @@ -1,290 +0,0 @@ -/** - * Copyright (c) 2020, RTE (http://www.rte-france.com) - * This Source Code Form is subject to the terms of the Mozilla Public - * License, v. 2.0. If a copy of the MPL was not distributed with this - * file, You can obtain one at http://mozilla.org/MPL/2.0/. - */ - -import { AppMetadata, Env, GsLangUser, GsTheme } from '@gridsuite/commons-ui'; -import { User } from 'oidc-client'; -import { - APP_NAME, - getAppName, - PARAM_LANGUAGE, - PARAM_THEME, -} from './config-params'; -import { store } from '../redux/store'; -import ReconnectingWebSocket, { Event } from 'reconnecting-websocket'; -import { AppState } from '../redux/reducer'; -import { getErrorMessage } from './error'; - -export interface ErrorWithStatus extends Error { - status?: number; -} - -// TODO remove when exported in commons-ui (src/utils/AuthService.ts) -type IdpSettings = { - authority: string; - client_id: string; - redirect_uri: string; - post_logout_redirect_uri: string; - silent_redirect_uri: string; - scope: string; - maxExpiresIn?: number; -}; - -export type Url = string | URL; -export type InitRequest = Partial; -export type Token = string; - -const PREFIX_USER_ADMIN_SERVER_QUERIES = `${process.env.REACT_APP_API_GATEWAY}/user-admin`; - -// If you want to use user-admin-server in dev mode you must avoid passing through gateway -// and use the user-admin-server directly. SetupProxy should allow this. -// const PREFIX_USER_ADMIN_SERVER_QUERIES = -// process.env.REACT_APP_API_PREFIX + -// (process.env.REACT_APP_USE_AUTHENTICATION === 'true' -// ? `${process.env.REACT_APP_API_GATEWAY}/user-admin` -// : process.env.REACT_APP_USER_ADMIN_URI); - -const PREFIX_CONFIG_QUERIES = `${process.env.REACT_APP_API_GATEWAY}/config`; -const PREFIX_CONFIG_NOTIFICATION_WS = `${process.env.REACT_APP_WS_GATEWAY}/config-notification`; - -function getToken(): Token | null { - const state: AppState = store.getState(); - return state.user?.id_token ?? null; -} - -export function connectNotificationsWsUpdateConfig(): ReconnectingWebSocket { - const webSocketBaseUrl = document.baseURI - .replace(/^http:\/\//, 'ws://') - .replace(/^https:\/\//, 'wss://'); - const webSocketUrl = `${webSocketBaseUrl}${PREFIX_CONFIG_NOTIFICATION_WS}/notify?appName=${APP_NAME}`; - - const reconnectingWebSocket = new ReconnectingWebSocket( - () => `${webSocketUrl}&access_token=${getToken()}` - ); - reconnectingWebSocket.onopen = function (event: Event) { - console.info( - `Connected Websocket update config ui ${webSocketUrl} ...` - ); - }; - return reconnectingWebSocket; -} - -function parseError(text: string) { - try { - return JSON.parse(text); - } catch (err) { - return null; - } -} - -function handleError(response: Response): Promise { - return response.text().then((text: string) => { - const errorName = 'HttpResponseError : '; - let error: ErrorWithStatus; - const errorJson = parseError(text); - if (errorJson?.status && errorJson?.error && errorJson?.message) { - error = new Error( - `${errorName}${errorJson.status} ${errorJson.error}, message : ${errorJson.message}` - ) as ErrorWithStatus; - error.status = errorJson.status; - } else { - error = new Error( - `${errorName}${response.status} ${response.statusText}` - ) as ErrorWithStatus; - error.status = response.status; - } - throw error; - }); -} - -function prepareRequest(init?: InitRequest, token?: Token): RequestInit { - if (!(typeof init == 'undefined' || typeof init == 'object')) { - throw new TypeError( - `Argument 2 of backendFetch is not an object ${typeof init}` - ); - } - const initCopy: RequestInit = { ...init }; - initCopy.headers = new Headers(initCopy.headers || {}); - const tokenCopy = token || getToken(); - initCopy.headers.append('Authorization', `Bearer ${tokenCopy}`); - return initCopy; -} - -function safeFetch(url: Url, initCopy?: InitRequest) { - return fetch(url, initCopy).then((response: Response) => - response.ok ? response : handleError(response) - ); -} - -export function backendFetch( - url: Url, - init?: InitRequest, - token?: Token -): Promise { - return safeFetch(url, prepareRequest(init, token)); -} - -export function backendFetchText( - url: Url, - init?: InitRequest, - token?: Token -): Promise { - return backendFetch(url, init, token).then((safeResponse: Response) => - safeResponse.text() - ); -} - -export function backendFetchJson( - url: Url, - init?: InitRequest, - token?: Token -): Promise { - return backendFetch(url, init, token).then((safeResponse: Response) => - safeResponse.json() - ); -} - -export function fetchValidateUser(user: User): Promise { - const sub = user?.profile?.sub; - if (!sub) { - return Promise.reject( - new Error( - `Error : Fetching access for missing user.profile.sub : ${user}` - ) - ); - } - - console.info(`Fetching access for user...`); - const CheckAccessUrl = `${PREFIX_USER_ADMIN_SERVER_QUERIES}/v1/users/${sub}`; - console.debug(CheckAccessUrl); - - return backendFetch(CheckAccessUrl, { method: 'head' }, user?.id_token) - .then((response: Response) => { - //if the response is ok, the responseCode will be either 200 or 204 otherwise it's a Http error and it will be caught - return response.status === 200; - }) - .catch((error) => { - if (error.status === 403) { - return false; - } else { - throw error; - } - }); -} - -export type EnvJson = Env & typeof import('../../public/env.json'); - -function fetchEnv(): Promise { - return fetch('env.json').then((res: Response) => res.json()); -} - -export function fetchIdpSettings(): Promise { - return fetch('idpSettings.json').then((res) => res.json()); -} - -export type VersionJson = { - deployVersion?: string; -}; - -export function fetchVersion(): Promise { - console.info(`Fetching global metadata...`); - return fetchEnv() - .then((env: EnvJson) => - fetch(`${env.appsMetadataServerUrl}/version.json`) - ) - .then((response: Response) => response.json()) - .catch((error) => { - console.error( - `Error while fetching the version : ${getErrorMessage(error)}` - ); - throw error; - }); -} - -export type MetadataCommon = { - name: string; - url: Url; - appColor: string; - hiddenInAppsMenu: boolean; -}; - -export type MetadataStudy = MetadataCommon & { - readonly name: 'Study'; - resources?: { - types: string[]; - path: string; - }[]; - predefinedEquipmentProperties?: { - substation?: { - region?: string[]; - tso?: string[]; - totallyFree?: unknown[]; - Demo?: string[]; - }; - load?: { - codeOI?: string[]; - }; - }; - defaultParametersValues?: { - fluxConvention?: string; - enableDeveloperMode?: string; //maybe 'true'|'false' type? - mapManualRefresh?: string; //maybe 'true'|'false' type? - }; -}; - -// https://github.com/gridsuite/deployment/blob/main/docker-compose/docker-compose.base.yml -// https://github.com/gridsuite/deployment/blob/main/k8s/resources/common/config/apps-metadata.json -export type MetadataJson = MetadataCommon | MetadataStudy; - -export function fetchAppsAndUrls(): Promise { - console.info(`Fetching apps and urls...`); - return fetchEnv() - .then((env: EnvJson) => - fetch(`${env.appsMetadataServerUrl}/apps-metadata.json`) - ) - .then((response: Response) => response.json()); -} - -// https://github.com/gridsuite/config-server/blob/main/src/main/java/org/gridsuite/config/server/dto/ParameterInfos.java -export type ConfigParameter = - | { - readonly name: typeof PARAM_LANGUAGE; - value: GsLangUser; - } - | { - readonly name: typeof PARAM_THEME; - value: GsTheme; - }; -export type ConfigParameters = ConfigParameter[]; - -export function fetchConfigParameters( - appName: string = APP_NAME -): Promise { - console.info(`Fetching UI configuration params for app : ${appName}`); - const fetchParams = `${PREFIX_CONFIG_QUERIES}/v1/applications/${appName}/parameters`; - return backendFetchJson(fetchParams) as Promise; -} - -export function fetchConfigParameter(name: string): Promise { - const appName = getAppName(name); - console.info(`Fetching UI config parameter '${name}' for app '${appName}'`); - const fetchParams = `${PREFIX_CONFIG_QUERIES}/v1/applications/${appName}/parameters/${name}`; - return backendFetchJson(fetchParams) as Promise; -} - -export function updateConfigParameter( - name: string, - value: Parameters[0] -): Promise { - const appName = getAppName(name); - console.info( - `Updating config parameter '${name}=${value}' for app '${appName}'` - ); - const updateParams = `${PREFIX_CONFIG_QUERIES}/v1/applications/${appName}/parameters/${name}?value=${encodeURIComponent( - value - )}`; - return backendFetch(updateParams, { method: 'put' }).then(); -}