diff --git a/.env b/.env index 2006710..41d577c 100644 --- a/.env +++ b/.env @@ -1,5 +1,6 @@ VITE_DEBUG_REQUESTS=false VITE_DEBUG_AGGRID=false +VITE_DEBUG_HOOK_RENDER=false VITE_API_GATEWAY=/api/gateway VITE_WS_GATEWAY=/ws/gateway diff --git a/package-lock.json b/package-lock.json index 9c13cba..75cdadd 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,7 +11,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@gridsuite/commons-ui": "0.63.2", + "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.63.2.tgz", "@hookform/resolvers": "^3.3.4", "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", @@ -2953,8 +2953,9 @@ }, "node_modules/@gridsuite/commons-ui": { "version": "0.63.2", - "resolved": "https://registry.npmjs.org/@gridsuite/commons-ui/-/commons-ui-0.63.2.tgz", - "integrity": "sha512-MxFBu8wzojgVsiMP23YzGoD8UPByXRLsCFMbrL0Rpt7y4YiPD7zKwPoMs+gA7n2lL/KGtlkYg2NdLHtwI5CYEA==", + "resolved": "file:../commons-ui/gridsuite-commons-ui-0.63.2.tgz", + "integrity": "sha512-mQLOg3A4OUCEioijw3wbiquwcDBKj/rPDtSNSDvEF4amfiUYDcl9He3RtElTJYdV3qRGnrObAWj+m1N4AWmHVw==", + "license": "MPL-2.0", "dependencies": { "@react-querybuilder/dnd": "^7.2.0", "@react-querybuilder/material": "^7.2.0", @@ -2970,6 +2971,7 @@ "react-dnd-html5-backend": "^16.0.1", "react-querybuilder": "^7.2.0", "react-virtualized": "^9.22.5", + "type-fest": "^4.21.0", "uuid": "^9.0.1" }, "engines": { @@ -2995,6 +2997,7 @@ "react-intl": "^6.6.4", "react-papaparse": "^4.1.0", "react-router-dom": "^6.22.3", + "reconnecting-websocket": "^4.4.0", "yup": "^1.4.0" } }, @@ -14622,9 +14625,9 @@ } }, "node_modules/type-fest": { - "version": "4.15.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.15.0.tgz", - "integrity": "sha512-tB9lu0pQpX5KJq54g+oHOLumOx+pMep4RaM6liXh2PKmVRFF+/vAtUP0ZaJ0kOySfVNjF6doBWPHhBhISKdlIA==", + "version": "4.23.0", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-4.23.0.tgz", + "integrity": "sha512-ZiBujro2ohr5+Z/hZWHESLz3g08BBdrdLMieYFULJO+tWc437sn8kQsWLJoZErY8alNhxre9K4p3GURAG11n+w==", "engines": { "node": ">=16" }, diff --git a/package.json b/package.json index b06746b..4a98086 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "dependencies": { "@emotion/react": "^11.11.4", "@emotion/styled": "^11.11.5", - "@gridsuite/commons-ui": "0.63.2", + "@gridsuite/commons-ui": "file:../commons-ui/gridsuite-commons-ui-0.63.2.tgz", "@hookform/resolvers": "^3.3.4", "@mui/icons-material": "^5.15.14", "@mui/lab": "5.0.0-alpha.169", diff --git a/src/components/App/app-top-bar.tsx b/src/components/App/app-top-bar.tsx index 99d1f68..c07094e 100644 --- a/src/components/App/app-top-bar.tsx +++ b/src/components/App/app-top-bar.tsx @@ -9,23 +9,26 @@ import { forwardRef, FunctionComponent, ReactElement, + useCallback, useEffect, useMemo, useState, } from 'react'; import { capitalize, Tab, Tabs, useTheme } from '@mui/material'; import { ManageAccounts, PeopleAlt } from '@mui/icons-material'; -import { logout, TopBar } from '@gridsuite/commons-ui'; -import { useParameterState } from '../parameters'; import { - APP_NAME, + AppMetadataCommon, + logout, PARAM_LANGUAGE, PARAM_THEME, -} from '../../utils/config-params'; + TopBar, +} from '@gridsuite/commons-ui'; +import { useParameterState } from '../parameters'; +import { APP_NAME } from '../../utils/config-params'; import { NavLink, useMatches, useNavigate } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; import { FormattedMessage } from 'react-intl'; -import { AppsMetadataSrv, MetadataJson, StudySrv } from '../../services'; +import { appsMetadataSrv, studySrv } from '../../services'; import GridAdminLogoLight from '../../images/GridAdmin_logo_light.svg?react'; import GridAdminLogoDark from '../../images/GridAdmin_logo_dark.svg?react'; import AppPackage from '../../../package.json'; @@ -67,7 +70,7 @@ const tabs = new Map([ const AppTopBar: FunctionComponent = () => { const theme = useTheme(); const dispatch = useDispatch(); - const user = useSelector((state: AppState) => state.user); + const user = useSelector((state: AppState) => state.user ?? null); const userManagerInstance = useSelector( (state: AppState) => state.userManager?.instance ); @@ -87,14 +90,18 @@ const AppTopBar: FunctionComponent = () => { const [languageLocal, handleChangeLanguage] = useParameterState(PARAM_LANGUAGE); - const [appsAndUrls, setAppsAndUrls] = useState([]); + const [appsAndUrls, setAppsAndUrls] = useState([]); useEffect(() => { if (user !== null) { - AppsMetadataSrv.fetchAppsAndUrls().then((res) => { + appsMetadataSrv.fetchAppsMetadata().then((res) => { setAppsAndUrls(res); }); } }, [user]); + const additionalModulesFetcher = useCallback( + () => studySrv.getServersInfos(APP_NAME), + [] + ); return ( { user={user ?? undefined} appsAndUrls={appsAndUrls} globalVersionPromise={() => - AppsMetadataSrv.fetchVersion().then( - (res) => res?.deployVersion ?? 'unknown' - ) + appsMetadataSrv + .fetchVersion() + .then((res) => res?.deployVersion ?? 'unknown') } - additionalModulesPromise={StudySrv.getServersInfos} + additionalModulesPromise={additionalModulesFetcher} onThemeClick={handleChangeTheme} theme={themeLocal} onLanguageClick={handleChangeLanguage} diff --git a/src/components/App/app-wrapper.tsx b/src/components/App/app-wrapper.tsx index 9e8f8c8..cf68d89 100644 --- a/src/components/App/app-wrapper.tsx +++ b/src/components/App/app-wrapper.tsx @@ -26,6 +26,7 @@ import { LIGHT_THEME, login_en, login_fr, + PARAM_THEME, SnackbarProvider, top_bar_en, top_bar_fr, @@ -35,7 +36,6 @@ import { Provider, useSelector } from 'react-redux'; import messages_en from '../../translations/en.json'; import messages_fr from '../../translations/fr.json'; import { store } from '../../redux/store'; -import { PARAM_THEME } from '../../utils/config-params'; import { AppState } from '../../redux/reducer'; import { AppWithAuthRouter } from '../../routes'; diff --git a/src/components/App/app.tsx b/src/components/App/app.tsx index 64da484..fa00e1f 100644 --- a/src/components/App/app.tsx +++ b/src/components/App/app.tsx @@ -13,23 +13,24 @@ import { } from 'react'; import { useDispatch, useSelector } from 'react-redux'; import { Grid } from '@mui/material'; -import { CardErrorBoundary, useSnackMessage } from '@gridsuite/commons-ui'; +import { + CardErrorBoundary, + COMMON_APP_NAME, + ConfigParameters, + getComputedLanguage, + PARAM_LANGUAGE, + PARAM_THEME, + useSnackMessage, +} from '@gridsuite/commons-ui'; import { selectComputedLanguage, selectLanguage, selectTheme, } from '../../redux/actions'; import { AppState } from '../../redux/reducer'; -import { ConfigNotif, ConfigParameters, ConfigSrv } from '../../services'; -import { - APP_NAME, - COMMON_APP_NAME, - PARAM_LANGUAGE, - PARAM_THEME, -} from '../../utils/config-params'; -import { getComputedLanguage } from '../../utils/language'; +import { configNotificationSrv, configSrv } from '../../services'; +import { APP_NAME } from '../../utils/config-params'; import AppTopBar from './app-top-bar'; -import ReconnectingWebSocket from 'reconnecting-websocket'; import { useDebugRender } from '../../utils/hooks'; import { AppDispatch } from '../../redux/store'; @@ -37,7 +38,7 @@ const App: FunctionComponent> = (props, context) => { useDebugRender('app'); const { snackError } = useSnackMessage(); const dispatch = useDispatch(); - const user = useSelector((state: AppState) => state.user); + const user = useSelector((state: AppState) => state.user ?? null); const updateParams = useCallback( (params: ConfigParameters) => { @@ -65,33 +66,33 @@ const App: FunctionComponent> = (props, context) => { [dispatch] ); - const connectNotificationsUpdateConfig = - useCallback((): ReconnectingWebSocket => { - const ws = ConfigNotif.connectNotificationsWsUpdateConfig(); - ws.onmessage = function (event) { - let eventData = JSON.parse(event.data); - if (eventData?.headers?.parameterName) { - ConfigSrv.fetchConfigParameter( - eventData.headers.parameterName - ) - .then((param) => updateParams([param])) - .catch((error) => - snackError({ - messageTxt: error.message, - headerId: 'paramsRetrievingError', - }) - ); - } - }; - ws.onerror = function (event) { - console.error('Unexpected Notification WebSocket error', event); - }; - return ws; - }, [updateParams, snackError]); + const connectNotificationsUpdateConfig = useCallback(() => { + const ws = + configNotificationSrv.connectNotificationsWsUpdateConfig(APP_NAME); + ws.onmessage = function (event) { + let eventData = JSON.parse(event.data); + if (eventData?.headers?.parameterName) { + configSrv + .fetchConfigParameter(eventData.headers.parameterName) + .then((param) => updateParams([param])) + .catch((error) => + snackError({ + messageTxt: error.message, + headerId: 'paramsRetrievingError', + }) + ); + } + }; + ws.onerror = function (event) { + console.error('Unexpected Notification WebSocket error', event); + }; + return ws; + }, [updateParams, snackError]); useEffect(() => { if (user !== null) { - ConfigSrv.fetchConfigParameters(COMMON_APP_NAME) + configSrv + .fetchConfigParameters(COMMON_APP_NAME) .then((params) => updateParams(params)) .catch((error) => snackError({ @@ -100,7 +101,8 @@ const App: FunctionComponent> = (props, context) => { }) ); - ConfigSrv.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 bf13895..38f421c 100644 --- a/src/components/parameters.tsx +++ b/src/components/parameters.tsx @@ -7,7 +7,7 @@ import { useCallback, useEffect, useState } from 'react'; import { useSelector } from 'react-redux'; -import { ConfigSrv } from '../services'; +import { configSrv } from '../services'; import { useSnackMessage } from '@gridsuite/commons-ui'; import { AppState, AppStateKey } from '../redux/reducer'; @@ -25,7 +25,8 @@ export function useParameterState( const handleChangeParamLocalState = useCallback( (value: AppState[K]) => { setParamLocalState(value); - ConfigSrv.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/pages/profiles/add-profile-dialog.tsx b/src/pages/profiles/add-profile-dialog.tsx index f11395e..f17785f 100644 --- a/src/pages/profiles/add-profile-dialog.tsx +++ b/src/pages/profiles/add-profile-dialog.tsx @@ -19,7 +19,7 @@ import { import { FormattedMessage } from 'react-intl'; import { Controller, useForm } from 'react-hook-form'; import { ManageAccounts } from '@mui/icons-material'; -import { UserAdminSrv, UserProfile } from '../../services'; +import { userAdminSrv, UserProfile } from '../../services'; import { useSnackMessage } from '@gridsuite/commons-ui'; import { GridTableRef } from '../../components/Grid'; import PaperForm from '../common/paper-form'; @@ -44,7 +44,8 @@ const AddProfileDialog: FunctionComponent = (props) => { const profileData: UserProfile = { name: name, }; - UserAdminSrv.addProfile(profileData) + userAdminSrv + .addProfile(profileData) .catch((error) => snackError({ messageTxt: error.message, diff --git a/src/pages/profiles/modification/parameter-selection.tsx b/src/pages/profiles/modification/parameter-selection.tsx index bcf9f62..bd0d2c1 100644 --- a/src/pages/profiles/modification/parameter-selection.tsx +++ b/src/pages/profiles/modification/parameter-selection.tsx @@ -12,7 +12,7 @@ import { Grid, IconButton, Tooltip } from '@mui/material'; import { useIntl } from 'react-intl'; import { DirectoryItemSelector, ElementType } from '@gridsuite/commons-ui'; import { useController, useWatch } from 'react-hook-form'; -import { DirectorySrv } from '../../../services'; +import { directorySrv } from '../../../services'; import LinkedPathDisplay from './linked-path-display'; export interface ParameterSelectionProps { @@ -45,7 +45,8 @@ const ParameterSelection: FunctionComponent = ( setSelectedElementName(undefined); setParameterLinkValid(undefined); } else { - DirectorySrv.fetchPath(watchParamId) + directorySrv + .fetchPath(watchParamId) .then((res: any) => { setParameterLinkValid(true); setSelectedElementName( diff --git a/src/pages/profiles/modification/profile-modification-dialog.tsx b/src/pages/profiles/modification/profile-modification-dialog.tsx index b773e23..2c93e79 100644 --- a/src/pages/profiles/modification/profile-modification-dialog.tsx +++ b/src/pages/profiles/modification/profile-modification-dialog.tsx @@ -11,7 +11,6 @@ import ProfileModificationForm, { USER_QUOTA_BUILD_NB, USER_QUOTA_CASE_NB, } from './profile-modification-form'; -import yup from '../../../utils/yup-config'; import { yupResolver } from '@hookform/resolvers/yup'; import { useForm } from 'react-hook-form'; import { @@ -21,8 +20,8 @@ import { useMemo, useState, } from 'react'; -import { CustomMuiDialog, useSnackMessage } from '@gridsuite/commons-ui'; -import { UserAdminSrv, UserProfile } from '../../../services'; +import { CustomMuiDialog, useSnackMessage, yup } from '@gridsuite/commons-ui'; +import { userAdminSrv, UserProfile } from '../../../services'; import { UUID } from 'crypto'; // TODO remove FetchStatus when exported in commons-ui (available soon) @@ -80,7 +79,8 @@ const ProfileModificationDialog: FunctionComponent< maxAllowedCases: profileFormData[USER_QUOTA_CASE_NB], maxAllowedBuilds: profileFormData[USER_QUOTA_BUILD_NB], }; - UserAdminSrv.modifyProfile(profileData) + userAdminSrv + .modifyProfile(profileData) .catch((error) => { snackError({ messageTxt: error.message, @@ -103,7 +103,8 @@ const ProfileModificationDialog: FunctionComponent< useEffect(() => { if (profileId && open) { setDataFetchStatus(FetchStatus.FETCHING); - UserAdminSrv.getProfile(profileId) + userAdminSrv + .getProfile(profileId) .then((response) => { setDataFetchStatus(FetchStatus.FETCH_SUCCESS); reset({ diff --git a/src/pages/profiles/profiles-table.tsx b/src/pages/profiles/profiles-table.tsx index 82eb611..c73645f 100644 --- a/src/pages/profiles/profiles-table.tsx +++ b/src/pages/profiles/profiles-table.tsx @@ -25,7 +25,7 @@ import { GridTable, GridTableRef, } from '../../components/Grid'; -import { UserAdminSrv, UserProfile } from '../../services'; +import { userAdminSrv, UserProfile } from '../../services'; import { ColDef, GetRowIdParams, @@ -74,7 +74,8 @@ const ProfilesTable: FunctionComponent = (props) => { const deleteProfiles = useCallback(() => { let profileNames = rowsSelection.map((userProfile) => userProfile.name); - return UserAdminSrv.deleteProfiles(profileNames) + return userAdminSrv + .deleteProfiles(profileNames) .catch((error) => { if (error.status === 422) { snackError({ @@ -149,7 +150,7 @@ const ProfilesTable: FunctionComponent = (props) => { return ( ref={props.gridRef} - dataLoader={UserAdminSrv.fetchProfiles} + dataLoader={userAdminSrv.fetchProfiles} columnDefs={columns} defaultColDef={defaultColDef} gridId="table-profiles" diff --git a/src/pages/users/UsersPage.tsx b/src/pages/users/UsersPage.tsx index d85b3a1..9ddcc9d 100644 --- a/src/pages/users/UsersPage.tsx +++ b/src/pages/users/UsersPage.tsx @@ -32,7 +32,7 @@ import { GridTable, GridTableRef, } from '../../components/Grid'; -import { UserAdminSrv, UserInfos, UserProfile } from '../../services'; +import { userAdminSrv, UserInfos, UserProfile } from '../../services'; import { useSnackMessage } from '@gridsuite/commons-ui'; import { Controller, SubmitHandler, useForm } from 'react-hook-form'; import { @@ -68,7 +68,8 @@ const UsersPage: FunctionComponent = () => { const [profileNameOptions, setprofileNameOptions] = useState([]); useEffect(() => { - UserAdminSrv.fetchProfilesWithoutValidityCheck() + userAdminSrv + .fetchProfilesWithoutValidityCheck() .then((allProfiles: UserProfile[]) => { let profiles: string[] = [ intl.formatMessage({ id: 'users.table.profile.none' }), @@ -153,7 +154,8 @@ const UsersPage: FunctionComponent = () => { const [rowsSelection, setRowsSelection] = useState([]); const deleteUsers = useCallback((): Promise | undefined => { let subs = rowsSelection.map((user) => user.sub); - return UserAdminSrv.deleteUsers(subs) + return userAdminSrv + .deleteUsers(subs) .catch((error) => snackError({ messageTxt: error.message, @@ -169,7 +171,8 @@ const UsersPage: FunctionComponent = () => { const addUser = useCallback( (id: string) => { - UserAdminSrv.addUser(id) + userAdminSrv + .addUser(id) .catch((error) => snackError({ messageTxt: `Error while adding user "${id}"${ @@ -203,7 +206,8 @@ const UsersPage: FunctionComponent = () => { const handleCellEditingStopped = useCallback( (event: CellEditingStoppedEvent) => { if (event.valueChanged && event.data) { - UserAdminSrv.udpateUser(event.data) + userAdminSrv + .updateUser(event.data) .catch((error) => snackError({ messageTxt: error.message, @@ -223,7 +227,7 @@ const UsersPage: FunctionComponent = () => { ref={gridRef} - dataLoader={UserAdminSrv.fetchUsers} + dataLoader={userAdminSrv.fetchUsers} columnDefs={columns} defaultColDef={defaultColDef} onCellEditingStopped={handleCellEditingStopped} diff --git a/src/redux/actions.ts b/src/redux/actions.ts index 841435b..4c90cd2 100644 --- a/src/redux/actions.ts +++ b/src/redux/actions.ts @@ -5,8 +5,13 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { GsTheme, UserManagerState } from '@gridsuite/commons-ui'; -import { PARAM_LANGUAGE } from '../utils/config-params'; +import { + GsLang, + GsTheme, + PARAM_LANGUAGE, + PARAM_THEME, + UserManagerState, +} from '@gridsuite/commons-ui'; import { Action } from 'redux'; import { AppState } from './reducer'; @@ -54,7 +59,7 @@ export function updateUserManagerError( export const SELECT_THEME = 'SELECT_THEME'; export type ThemeAction = Readonly> & { - theme: GsTheme; + [PARAM_THEME]: AppState[typeof PARAM_THEME]; }; export function selectTheme(theme: GsTheme): ThemeAction { @@ -63,10 +68,10 @@ export function selectTheme(theme: GsTheme): ThemeAction { export const SELECT_LANGUAGE = 'SELECT_LANGUAGE'; export type LanguageAction = Readonly> & { - [PARAM_LANGUAGE]: AppState['language']; + [PARAM_LANGUAGE]: AppState[typeof PARAM_LANGUAGE]; }; -export function selectLanguage(language: AppState['language']): LanguageAction { +export function selectLanguage(language: GsLang): LanguageAction { return { type: SELECT_LANGUAGE, [PARAM_LANGUAGE]: language }; } diff --git a/src/redux/local-storage.ts b/src/redux/local-storage.ts deleted file mode 100644 index f567b52..0000000 --- a/src/redux/local-storage.ts +++ /dev/null @@ -1,43 +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, - 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() { - return getComputedLanguage(getLocalStorageLanguage()); -} diff --git a/src/redux/reducer.ts b/src/redux/reducer.ts index db8844e..6adba0c 100644 --- a/src/redux/reducer.ts +++ b/src/redux/reducer.ts @@ -6,13 +6,6 @@ */ import { createReducer, Draft } from '@reduxjs/toolkit'; -import { - getLocalStorageComputedLanguage, - getLocalStorageLanguage, - getLocalStorageTheme, - saveLocalStorageTheme, -} from './local-storage'; - import { ComputedLanguageAction, LanguageAction, @@ -30,13 +23,18 @@ import { AuthenticationActions, AuthenticationRouterErrorAction, AuthenticationRouterErrorState, - CommonStoreState, + 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, @@ -49,10 +47,12 @@ import { UserManagerState, 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 = CommonStoreState & { +export type AppState = { + user?: User; computedLanguage: GsLangUser; [PARAM_THEME]: GsTheme; [PARAM_LANGUAGE]: GsLang; @@ -69,15 +69,15 @@ const initialState: AppState = { instance: null, error: null, }, - user: null, + user: undefined, signInCallbackError: null, authenticationRouterError: null, 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 = @@ -98,7 +98,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 8ae60c5..8e4c182 100644 --- a/src/redux/store.ts +++ b/src/redux/store.ts @@ -7,10 +7,8 @@ import { legacy_createStore as createStore, Store } from 'redux'; import { Actions, AppState, reducer } from './reducer'; -import { setCommonStore } from '@gridsuite/commons-ui'; export const store: Store = createStore(reducer); -setCommonStore(store); export type AppDispatch = typeof store.dispatch; // to avoid to reset the state with HMR @@ -18,3 +16,7 @@ export type AppDispatch = typeof store.dispatch; if (import.meta.env.DEV && import.meta.hot) { import.meta.hot.accept('./reducer', () => store.replaceReducer(reducer)); } + +export function getUser() { + return store.getState().user; +} diff --git a/src/routes/router.tsx b/src/routes/router.tsx index 20de342..c32634e 100644 --- a/src/routes/router.tsx +++ b/src/routes/router.tsx @@ -15,6 +15,7 @@ import { import { FormattedMessage } from 'react-intl'; import { AuthenticationRouter, + getErrorMessage, getPreLoginPath, initializeAuthenticationProd, } from '@gridsuite/commons-ui'; @@ -30,13 +31,12 @@ import { } from 'react-router-dom'; import { useDispatch, useSelector } from 'react-redux'; import { AppState } from '../redux/reducer'; -import { AppsMetadataSrv, UserAdminSrv } from '../services'; +import { appLocalSrv, userAdminSrv } from '../services'; import { App } from '../components/App'; import { Profiles, Users } from '../pages'; import ErrorPage from './ErrorPage'; import { updateUserManagerDestructured } from '../redux/actions'; import HomePage from './HomePage'; -import { getErrorMessage } from '../utils/error'; import { AppDispatch } from '../redux/store'; export enum MainPaths { @@ -146,8 +146,8 @@ const AppAuthStateWithRouterLayer: FunctionComponent< (await initializeAuthenticationProd( dispatch, initialMatchSilentRenewCallbackUrl != null, - AppsMetadataSrv.fetchIdpSettings, - UserAdminSrv.fetchValidateUser, + appLocalSrv.fetchIdpSettings, + userAdminSrv.fetchValidateUser, initialMatchSignInCallbackUrl != null )) ?? null, null @@ -176,7 +176,7 @@ export const AppWithAuthRouter: FunctionComponent<{ basename: string; layout: App; }> = (props, context) => { - const user = useSelector((state: AppState) => state.user); + const user = useSelector((state: AppState) => state.user ?? null); const router = useMemo( () => createBrowserRouter( diff --git a/src/services/app-local.ts b/src/services/app-local.ts new file mode 100644 index 0000000..cbb77ed --- /dev/null +++ b/src/services/app-local.ts @@ -0,0 +1,20 @@ +/* + * 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/apps-metadata.ts b/src/services/apps-metadata.ts deleted file mode 100644 index c28fe21..0000000 --- a/src/services/apps-metadata.ts +++ /dev/null @@ -1,94 +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/. - */ - -import { Env } from '@gridsuite/commons-ui'; -import { getErrorMessage } from '../utils/error'; -import { Url } from '../utils/api-rest'; - -// 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 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.debug(`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.debug('Fetching apps and urls...'); - return fetchEnv() - .then((env: EnvJson) => - fetch(`${env.appsMetadataServerUrl}/apps-metadata.json`) - ) - .then((response: Response) => response.json()); -} diff --git a/src/services/config-notification.ts b/src/services/config-notification.ts deleted file mode 100644 index c97ecbc..0000000 --- a/src/services/config-notification.ts +++ /dev/null @@ -1,25 +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/. - */ - -import ReconnectingWebSocket, { Event } from 'reconnecting-websocket'; -import { APP_NAME } from '../utils/config-params'; -import { getUrlWithToken, getWsBase } from '../utils/api-ws'; - -const PREFIX_CONFIG_NOTIFICATION_WS = `${getWsBase()}/config-notification`; - -export function connectNotificationsWsUpdateConfig(): ReconnectingWebSocket { - const webSocketUrl = `${PREFIX_CONFIG_NOTIFICATION_WS}/notify?appName=${APP_NAME}`; - const reconnectingWebSocket = new ReconnectingWebSocket( - () => getUrlWithToken(webSocketUrl), - undefined, - { debug: import.meta.env.VITE_DEBUG_REQUESTS === 'true' } - ); - reconnectingWebSocket.onopen = function (event: Event) { - console.info(`Connected Websocket update config ui: ${webSocketUrl}`); - }; - return reconnectingWebSocket; -} diff --git a/src/services/config.ts b/src/services/config.ts index 0152f02..e007d7d 100644 --- a/src/services/config.ts +++ b/src/services/config.ts @@ -5,58 +5,12 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { GsLang, GsTheme } from '@gridsuite/commons-ui'; -import { - APP_NAME, - getAppName, - PARAM_LANGUAGE, - PARAM_THEME, -} from '../utils/config-params'; -import { backendFetch, backendFetchJson, getRestBase } from '../utils/api-rest'; +import { ConfigComSvc } from '@gridsuite/commons-ui'; +import { APP_NAME } from '../utils/config-params'; +import { getUser } from '../redux/store'; -const PREFIX_CONFIG_QUERIES = `${getRestBase()}/config`; - -// 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: GsLang; - } - | { - readonly name: typeof PARAM_THEME; - value: GsTheme; - }; -export type ConfigParameters = ConfigParameter[]; - -export function fetchConfigParameters( - appName: string = APP_NAME -): Promise { - console.debug(`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.debug( - `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.debug( - `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', - }) as Promise as Promise; +export default class ConfigSvc extends ConfigComSvc { + public constructor() { + super(APP_NAME, getUser); + } } diff --git a/src/services/directory.ts b/src/services/directory.ts index a379812..edb94e0 100644 --- a/src/services/directory.ts +++ b/src/services/directory.ts @@ -5,21 +5,19 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { backendFetchJson, getRestBase } from '../utils/api-rest'; import { UUID } from 'crypto'; -import { ElementAttributes } from '@gridsuite/commons-ui'; +import { DirectoryComSvc, ElementAttributes } from '@gridsuite/commons-ui'; +import { getUser } from '../redux/store'; -const DIRECTORY_URL = `${getRestBase()}/directory/v1`; +export default class DirectorySvc extends DirectoryComSvc { + public constructor() { + super(getUser); + } -export function fetchPath(elementUuid: UUID): Promise { - console.debug(`Fetching element and its parents info...`); - return backendFetchJson(`${DIRECTORY_URL}/elements/${elementUuid}/path`, { - headers: { - Accept: 'application/json', - }, - cache: 'default', - }).catch((reason) => { - console.error(`Error while fetching the servers data : ${reason}`); - throw reason; - }) as Promise; + public async fetchPath(elementUuid: UUID) { + console.debug('Fetching element and its parents info...'); + return this.backendFetchJson( + `${this.getPrefix(1)}/elements/${elementUuid}/path` + ); + } } diff --git a/src/services/index.ts b/src/services/index.ts index 78cb65a..4348e66 100644 --- a/src/services/index.ts +++ b/src/services/index.ts @@ -1,17 +1,35 @@ -export * as ConfigSrv from './config'; -export type * from './config'; +import { getUser } from '../redux/store'; +import { + AppsMetadataComSvc, + ConfigNotificationComSvc, + ExploreComSvc, + setCommonServices, + StudyComSvc, +} from '@gridsuite/commons-ui'; +import AppLocalSvc from './app-local'; +import ConfigSvc from './config'; +import DirectorySvc from './directory'; +import UserAdminSvc from './user-admin'; -export * as ConfigNotif from './config-notification'; -export type * from './config-notification'; +export type { EnvJson } from './app-local'; +export type { UserInfos, UserProfile } from './user-admin'; -export * as AppsMetadataSrv from './apps-metadata'; -export type * from './apps-metadata'; +export const appLocalSrv = new AppLocalSvc(), + appsMetadataSrv = new AppsMetadataComSvc(appLocalSrv), + configSrv = new ConfigSvc(), + configNotificationSrv = new ConfigNotificationComSvc(getUser), + directorySrv = new DirectorySvc(), + exploreSrv = new ExploreComSvc(getUser), + studySrv = new StudyComSvc(getUser), + userAdminSrv = new UserAdminSvc(); -export * as StudySrv from './study'; -export type * from './study'; - -export * as UserAdminSrv from './user-admin'; -export type * from './user-admin'; - -export * as DirectorySrv from './directory'; -export type * from './directory'; +setCommonServices( + appLocalSrv, + appsMetadataSrv, + configSrv, + configNotificationSrv, + directorySrv, + exploreSrv, + studySrv, + userAdminSrv +); diff --git a/src/services/study.ts b/src/services/study.ts deleted file mode 100644 index 42712c4..0000000 --- a/src/services/study.ts +++ /dev/null @@ -1,28 +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, getRestBase } from '../utils/api-rest'; -import { getErrorMessage } from '../utils/error'; -import { APP_NAME } from '../utils/config-params'; - -const STUDY_URL = `${getRestBase()}/study/v1`; - -export function getServersInfos() { - return backendFetchJson(`${STUDY_URL}/servers/about?view=${APP_NAME}`, { - 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/user-admin.ts b/src/services/user-admin.ts index 466d78a..e3ee98b 100644 --- a/src/services/user-admin.ts +++ b/src/services/user-admin.ts @@ -5,42 +5,9 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { User } from 'oidc-client'; -import { backendFetch, backendFetchJson, getRestBase } from '../utils/api-rest'; -import { extractUserSub, getToken, getUser } from '../utils/api'; import { UUID } from 'crypto'; - -const USER_ADMIN_URL = `${getRestBase()}/user-admin/v1`; - -export function getUserSub(): Promise { - return extractUserSub(getUser()); -} - -/* - * fetchValidateUser is call from commons-ui AuthServices to validate user infos before setting state.user! - */ -export function fetchValidateUser(user: User): Promise { - return extractUserSub(user) - .then((sub) => { - console.debug(`Fetching access for user "${sub}"...`); - return backendFetch( - `${USER_ADMIN_URL}/users/${sub}`, - { method: 'head' }, - getToken(user) ?? undefined - ); - }) - .then((response: Response) => { - //if the response is ok, the responseCode will be either 200 or 204 otherwise it's an HTTP error and it will be caught - return response.status === 200; - }) - .catch((error) => { - if (error.status === 403) { - return false; - } else { - throw error; - } - }); -} +import { UserAdminComSvc } from '@gridsuite/commons-ui'; +import { getUser } from '../redux/store'; export type UserInfos = { sub: string; @@ -48,64 +15,6 @@ export type UserInfos = { isAdmin: boolean; }; -export function fetchUsers(): Promise { - console.debug(`Fetching list of users...`); - return backendFetchJson(`${USER_ADMIN_URL}/users`, { - headers: { - Accept: 'application/json', - //'Content-Type': 'application/json; utf-8', - }, - cache: 'default', - }).catch((reason) => { - console.error(`Error while fetching the servers data : ${reason}`); - throw reason; - }) as Promise; -} - -export function udpateUser(userInfos: UserInfos) { - console.debug(`Updating a user...`); - - return backendFetch(`${USER_ADMIN_URL}/users/${userInfos.sub}`, { - method: 'PUT', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(userInfos), - }) - .then(() => undefined) - .catch((reason) => { - console.error(`Error while updating user : ${reason}`); - throw reason; - }); -} - -export function deleteUsers(subs: string[]): Promise { - console.debug(`Deleting sub users "${JSON.stringify(subs)}"...`); - return backendFetch(`${USER_ADMIN_URL}/users`, { - method: 'delete', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(subs), - }) - .then(() => undefined) - .catch((reason) => { - console.error(`Error while deleting the servers data : ${reason}`); - throw reason; - }); -} - -export function addUser(sub: string): Promise { - console.debug(`Creating sub user "${sub}"...`); - return backendFetch(`${USER_ADMIN_URL}/users/${sub}`, { method: 'post' }) - .then(() => undefined) - .catch((reason) => { - console.error(`Error while adding user : ${reason}`); - throw reason; - }); -} - export type UserProfile = { id?: UUID; name: string; @@ -115,97 +24,100 @@ export type UserProfile = { maxAllowedBuilds?: number; }; -export function fetchProfiles(): Promise { - console.debug(`Fetching list of profiles...`); - return backendFetchJson(`${USER_ADMIN_URL}/profiles`, { - headers: { - Accept: 'application/json', - }, - cache: 'default', - }).catch((reason) => { - console.error(`Error while fetching list of profiles : ${reason}`); - throw reason; - }) as Promise; -} +export default class UserAdminSvc extends UserAdminComSvc { + public constructor() { + super(getUser); + } + public async fetchUsers() { + console.debug('Fetching list of users...'); + return this.backendFetchJson(`${this.getPrefix(1)}/users`); + } + + public async updateUser(userInfos: UserInfos) { + console.debug('Updating a user...'); + await this.backendFetch(`${this.getPrefix(1)}/users/${userInfos.sub}`, { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(userInfos), + }); + } -export function fetchProfilesWithoutValidityCheck(): Promise { - console.debug(`Fetching list of profiles...`); - return backendFetchJson( - `${USER_ADMIN_URL}/profiles?checkLinksValidity=false`, - { + public async deleteUsers(subs: string[]) { + const jsonSubs = JSON.stringify(subs); + console.debug(`Deleting sub users "${jsonSubs}"...`); + await this.backendFetch(`${this.getPrefix(1)}/users`, { + method: 'DELETE', headers: { - Accept: 'application/json', + 'Content-Type': 'application/json', }, - cache: 'default', - } - ).catch((reason) => { - console.error( - `Error while fetching list of profiles (without check) : ${reason}` - ); - throw reason; - }) as Promise; -} + body: jsonSubs, + }); + } -export function getProfile(profileId: UUID): Promise { - console.debug(`Fetching a profile...`); - return backendFetchJson(`${USER_ADMIN_URL}/profiles/${profileId}`, { - headers: { - Accept: 'application/json', - }, - cache: 'default', - }).catch((reason) => { - console.error(`Error while fetching profile : ${reason}`); - throw reason; - }) as Promise; -} + public async addUser(sub: string) { + console.debug(`Creating sub user "${sub}"...`); + await this.backendFetch(`${this.getPrefix(1)}/users/${sub}`, { + method: 'POST', + }); + } -export function modifyProfile(profileData: UserProfile) { - console.debug(`Updating a profile...`); + public async fetchProfiles() { + console.debug('Fetching list of profiles...'); + return this.backendFetchJson( + `${this.getPrefix(1)}/profiles` + ); + } - return backendFetch(`${USER_ADMIN_URL}/profiles/${profileData.id}`, { - method: 'PUT', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(profileData), - }) - .then(() => undefined) - .catch((reason) => { - console.error(`Error while updating the data : ${reason}`); - throw reason; - }); -} + public async fetchProfilesWithoutValidityCheck() { + console.debug(`Fetching list of profiles...`); + return this.backendFetchJson( + `${this.getPrefix(1)}/profiles?checkLinksValidity=false` + ); + } + + public async getProfile(profileId: UUID) { + console.debug(`Fetching a profile...`); + return this.backendFetchJson( + `${this.getPrefix(1)}/profiles/${profileId}` + ); + } + + public async modifyProfile(profileData: UserProfile) { + console.debug(`Updating a profile...`); + await this.backendFetch( + `${this.getPrefix(1)}/profiles/${profileData.id}`, + { + method: 'PUT', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(profileData), + } + ); + } -export function addProfile(profileData: UserProfile): Promise { - console.debug(`Creating user profile "${profileData.name}"...`); - return backendFetch(`${USER_ADMIN_URL}/profiles`, { - method: 'post', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - }, - body: JSON.stringify(profileData), - }) - .then(() => undefined) - .catch((reason) => { - console.error(`Error while pushing adding profile : ${reason}`); - throw reason; + public async addProfile(profileData: UserProfile) { + console.debug(`Creating user profile "${profileData.name}"...`); + await this.backendFetch(`${this.getPrefix(1)}/profiles`, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(profileData), }); -} + } -export function deleteProfiles(names: string[]): Promise { - console.debug(`Deleting profiles "${JSON.stringify(names)}"...`); - return backendFetch(`${USER_ADMIN_URL}/profiles`, { - method: 'delete', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify(names), - }) - .then(() => undefined) - .catch((reason) => { - console.error(`Error while deleting profiles : ${reason}`); - throw reason; + public async deleteProfiles(names: string[]) { + console.debug(`Deleting profiles "${JSON.stringify(names)}"...`); + await this.backendFetch(`${this.getPrefix(1)}/profiles`, { + method: 'DELETE', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(names), }); + } } diff --git a/src/utils/api-rest.ts b/src/utils/api-rest.ts deleted file mode 100644 index 7989c0d..0000000 --- a/src/utils/api-rest.ts +++ /dev/null @@ -1,91 +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 { getToken, parseError, Token } from './api'; - -export type { Token } from './api'; - -export interface ErrorWithStatus extends Error { - status?: number; -} - -export type Url = string | URL; -export type InitRequest = Partial; - -export function getRestBase(): string { - // We use the `baseURI` (from `` in index.html) to build the URL, which is corrected by httpd/nginx - return ( - document.baseURI.replace(/\/+$/, '') + import.meta.env.VITE_API_GATEWAY - ); -} - -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() - ); -} diff --git a/src/utils/api-ws.ts b/src/utils/api-ws.ts deleted file mode 100644 index cb8a3cb..0000000 --- a/src/utils/api-ws.ts +++ /dev/null @@ -1,24 +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/. - */ - -import { getToken } from './api'; - -export type * from './api'; - -export function getWsBase(): string { - // We use the `baseURI` (from `` in index.html) to build the URL, which is corrected by httpd/nginx - return ( - document.baseURI - .replace(/^http(s?):\/\//, 'ws$1://') - .replace(/\/+$/, '') + import.meta.env.VITE_WS_GATEWAY - ); -} - -export function getUrlWithToken(baseUrl: string): string { - const querySymbol = baseUrl.includes('?') ? '&' : '?'; - return `${baseUrl}${querySymbol}access_token=${getToken()}`; -} diff --git a/src/utils/api.ts b/src/utils/api.ts deleted file mode 100644 index 6f9e7a5..0000000 --- a/src/utils/api.ts +++ /dev/null @@ -1,46 +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/. - */ - -import { User } from 'oidc-client'; -import { AppState } from '../redux/reducer'; -import { store } from '../redux/store'; - -export type Token = string; - -export function getToken(user?: User): Token | null { - return (user ?? getUser())?.id_token ?? null; -} - -export function getUser(): User | null { - const state: AppState = store.getState(); - return state.user; -} - -export function extractUserSub(user: User | null): Promise { - return new Promise((resolve, reject) => { - const sub = user?.profile?.sub; - if (!sub) { - reject( - new Error( - `Fetching access for missing user.profile.sub : ${JSON.stringify( - user - )}` - ) - ); - } else { - resolve(sub); - } - }); -} - -export function parseError(text: string) { - try { - return JSON.parse(text); - } catch (err) { - return null; - } -} diff --git a/src/utils/auth.ts b/src/utils/auth.ts deleted file mode 100644 index 6b2cfcb..0000000 --- a/src/utils/auth.ts +++ /dev/null @@ -1,13 +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 type User = { - id_token: string; - profile?: { - sub?: string; - }; -}; diff --git a/src/utils/config-params.ts b/src/utils/config-params.ts index 6fd3b22..835ed4e 100644 --- a/src/utils/config-params.ts +++ b/src/utils/config-params.ts @@ -5,29 +5,4 @@ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ -import { LiteralUnion } from 'type-fest'; - -export const COMMON_APP_NAME = 'common'; export const APP_NAME = 'admin'; - -export const PARAM_THEME = 'theme'; -export const PARAM_LANGUAGE = 'language'; - -const COMMON_CONFIG_PARAMS_NAMES = new Set([PARAM_THEME, PARAM_LANGUAGE]); - -export type AppConfigParameter = LiteralUnion< - 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 af9d81c..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/hooks.ts b/src/utils/hooks.ts index 29fce4e..a3f6cb4 100644 --- a/src/utils/hooks.ts +++ b/src/utils/hooks.ts @@ -7,9 +7,9 @@ export function useDebugRender(label: string) { // uncomment when you want the output in the console - /*if (import.meta.env.DEV) { + if (import.meta.env.VITE_DEBUG_HOOK_RENDER) { label = `${label} render`; console.count?.(label); console.timeStamp?.(label); - }*/ + } } diff --git a/src/utils/language.ts b/src/utils/language.ts deleted file mode 100644 index 48f3ac3..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: string[] = [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): GsLangUser { - return language === LANG_SYSTEM ? getSystemLanguage() : language; -} diff --git a/src/utils/yup-config.js b/src/utils/yup-config.js deleted file mode 100644 index 6bf1807..0000000 --- a/src/utils/yup-config.js +++ /dev/null @@ -1,25 +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/. - */ - -// TODO: this file is going to be available soon in commons-ui - -import * as yup from 'yup'; - -yup.setLocale({ - mixed: { - required: 'YupRequired', - notType: ({ type }) => { - if (type === 'number') { - return 'YupNotTypeNumber'; - } else { - return 'YupNotTypeDefault'; - } - }, - }, -}); - -export default yup; diff --git a/src/vite-env.d.ts b/src/vite-env.d.ts index db95603..f10d2ad 100644 --- a/src/vite-env.d.ts +++ b/src/vite-env.d.ts @@ -7,3 +7,24 @@ /// /// + +/* Don't know why but seem that TypeScript merge definitions of these two interfaces with existing ones. + * https://vitejs.dev/guide/env-and-mode#intellisense-for-typescript + */ +import { UrlString } from '@gridsuite/commons-ui'; + +interface ImportMetaEnv { + /* From @gridsuite/commons-ui */ + readonly VITE_API_GATEWAY: UrlString; + readonly VITE_WS_GATEWAY: UrlString; + // readonly VITE_DEBUG_REQUESTS?: boolean; + + /* From this app */ + readonly VITE_DEBUG_REQUESTS: boolean; + readonly VITE_DEBUG_AGGRID: boolean; + readonly VITE_DEBUG_HOOK_RENDER: boolean; +} + +interface ImportMeta { + readonly env: ImportMetaEnv; +}