diff --git a/react-ui/src/components/devices/api/pnd.fetch.ts b/react-ui/src/components/devices/api/pnd.fetch.ts new file mode 100644 index 0000000000000000000000000000000000000000..6e677ab8d7456df132a54cf04f3131b6c5c3d636 --- /dev/null +++ b/react-ui/src/components/devices/api/pnd.fetch.ts @@ -0,0 +1,14 @@ +import { PndServiceGetPndListApiArg, api } from "@api/api" +import { createAsyncThunk } from "@reduxjs/toolkit" +import { setPnds } from "../reducer/device.reducer" + +export const fetchPnds = createAsyncThunk('device/fetchPnds', (_, thunkApi) => { + const payload: PndServiceGetPndListApiArg = { + timestamp: new Date().getTime().toString(), + } + + const subscription = thunkApi.dispatch(api.endpoints.pndServiceGetPndList.initiate(payload)) + subscription.unwrap().then((response) => { + thunkApi.dispatch(setPnds(response.pnd)) + }) +}) \ No newline at end of file diff --git a/react-ui/src/components/devices/reducer/device.reducer.ts b/react-ui/src/components/devices/reducer/device.reducer.ts index 8e4454bd285396b3aca4dce62bc7d1b1518fcd51..f574509be8821fe987dbe28344f27fee1c49fb49 100755 --- a/react-ui/src/components/devices/reducer/device.reducer.ts +++ b/react-ui/src/components/devices/reducer/device.reducer.ts @@ -1,12 +1,10 @@ import { - api, NetworkelementFlattenedManagedNetworkElement, NetworkelementManagedNetworkElement, - PndPrincipalNetworkDomain, - PndServiceGetPndListApiArg, + PndPrincipalNetworkDomain } from '@api/api' import { DeviceViewTabValues } from '@component/devices/view/device.view.tabs' -import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit' +import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { RootState } from 'src/stores' import '../routines/index' import { startListening } from '/src/stores/middleware/listener.middleware' @@ -40,11 +38,11 @@ const deviceSlice = createSlice({ name: 'device', initialState, reducers: { - setDevices: (state, action: PayloadAction<Device[]>) => { - state.devices = action.payload + setDevices: (state, action: PayloadAction<Device[] | undefined>) => { + state.devices = action.payload || [] }, - setPnds: (state, action: PayloadAction<PndPrincipalNetworkDomain[]>) => { - state.pnds = action.payload + setPnds: (state, action: PayloadAction<PndPrincipalNetworkDomain[] | undefined>) => { + state.pnds = action.payload || [] }, setActiveTab: (state, action: PayloadAction<DeviceViewTabValues>) => { state.activeTab = action.payload @@ -77,31 +75,23 @@ const deviceSlice = createSlice({ }, }) -export const { setDevices, setActiveTab, setSelectedDevice, setSelectedMne, setSelectedJson } = +export const { setDevices, setActiveTab, setSelectedDevice, setSelectedMne, setSelectedJson, setPnds } = deviceSlice.actions -const { setPnds } = deviceSlice.actions export default deviceSlice.reducer export const deviceReducerPath = deviceSlice.reducerPath -export const fetchPnds = createAsyncThunk('device/fetchPnds', (_, thunkApi) => { - const payload: PndServiceGetPndListApiArg = { - timestamp: new Date().getTime().toString(), - } - - const subscription = thunkApi.dispatch(api.endpoints.pndServiceGetPndList.initiate(payload)) - subscription.unwrap().then((response) => { - thunkApi.dispatch(setPnds(response.pnd)) - }) -}) - // add default selected device if no selected device is set startListening({ predicate: (action) => setDevices.match(action), effect: async (action, listenerApi) => { - const { device } = listenerApi.getOriginalState() as RootState - if (!device.selectedDevice && !!action.payload[0]) { - listenerApi.dispatch(setSelectedDevice(action.payload[0])) + const { device: state } = listenerApi.getOriginalState() as RootState + if (state.selectedDevice) { + return } + + // if there are no devices available do set null + const newDevices = action.payload?.[0] || null + listenerApi.dispatch(setSelectedDevice(newDevices)) }, }) diff --git a/react-ui/src/index.tsx b/react-ui/src/index.tsx index 8383248cea22fc89c4ba1c9f5248458f7fa150ec..3697efd07379da0126d042b824b9c3d857bb6fed 100755 --- a/react-ui/src/index.tsx +++ b/react-ui/src/index.tsx @@ -16,6 +16,7 @@ import { router } from './routes' import './shared/icons/icons' import { persistor, store } from './stores' +window.env = window.location.hostname === 'localhost' ? 'development' : 'production'; ReactDOM.createRoot(document.getElementById("root")).render( <React.StrictMode> diff --git a/react-ui/src/shared/api/user.fetch.ts b/react-ui/src/shared/api/user.fetch.ts new file mode 100644 index 0000000000000000000000000000000000000000..1b6dd344ed79136fc2bd22fa168a6e2befe199a6 --- /dev/null +++ b/react-ui/src/shared/api/user.fetch.ts @@ -0,0 +1,25 @@ +import { createAsyncThunk } from "@reduxjs/toolkit" +import { setUser } from "@shared/reducer/user.reducer" +import { RootState } from "src/stores" +import { api, UserServiceGetUsersApiArg } from "./api" + +export const fetchUser = createAsyncThunk('user/fetchUser', (_, thunkAPI) => { + const payload: UserServiceGetUsersApiArg = {} + + thunkAPI.dispatch(api.endpoints.userServiceGetUsers.initiate(payload)).then((response) => { + if (response.error || !response.data?.user?.length) { + // TODO proper error handling + throw new Error('Fetching the pnd list after successful login failed') + } + + const { user } = thunkAPI.getState() as RootState + const matchedUser = response.data.user.find((_user) => _user.name === user.username) + + if (!matchedUser) { + // TODO proper error handling + throw new Error('No user found with the provided username') + } + + thunkAPI.dispatch(setUser(matchedUser)) + }) +}) diff --git a/react-ui/src/shared/helper/debug.ts b/react-ui/src/shared/helper/debug.ts new file mode 100644 index 0000000000000000000000000000000000000000..6628989b8f08fc4c4cdce9f74b0291c0dfbb7726 --- /dev/null +++ b/react-ui/src/shared/helper/debug.ts @@ -0,0 +1,5 @@ +export const debugMessage = (message: string) => { + if (window?.env === 'development') { + console.warn("Debug: \n" + message) + } +} \ No newline at end of file diff --git a/react-ui/src/shared/layouts/protected.layout/protected.layout.tsx b/react-ui/src/shared/layouts/protected.layout/protected.layout.tsx index c7e13c6fe55a2f874c6eeafbc956305986051b63..17b6209a5ac3fb3620fb07cd5e44ad46424d92a6 100755 --- a/react-ui/src/shared/layouts/protected.layout/protected.layout.tsx +++ b/react-ui/src/shared/layouts/protected.layout/protected.layout.tsx @@ -1,12 +1,12 @@ +import { fetchUser } from '@api/user.fetch'; import logo from '@assets/logo.svg'; -import { fetchPnds } from '@component/devices/reducer/device.reducer'; +import { fetchPnds } from '@component/devices/api/pnd.fetch'; import { faCircleUser, faRightFromBracket } from "@fortawesome/free-solid-svg-icons"; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { useAppDispatch, useAppSelector } from '@hooks'; import { useAuth } from "@provider/auth.provider"; import { MenuProvider } from '@provider/menu/menu.provider'; import { DEVICE_URL, LOGIN_URL } from '@routes'; -import { fetchUser } from '@shared/reducer/user.reducer'; import React, { useEffect } from "react"; import { Dropdown } from "react-bootstrap"; import { useTranslation } from "react-i18next"; diff --git a/react-ui/src/shared/reducer/routine.reducer.ts b/react-ui/src/shared/reducer/routine.reducer.ts index a52ee84d3f16e89f5e899919d59fcb44428a9213..fc5a4b35b39031c02d8ea1bebef7b8e81cd718d0 100755 --- a/react-ui/src/shared/reducer/routine.reducer.ts +++ b/react-ui/src/shared/reducer/routine.reducer.ts @@ -1,33 +1,43 @@ +import { debugMessage } from '@helper/debug' import { PayloadAction, createSlice } from '@reduxjs/toolkit' import { RoutineManager } from '@utils/routine.manager' import { RootState } from '../../stores' import { startListening } from '../../stores/middleware/listener.middleware' import { setToken } from './user.reducer' +// ---------------- thunk types ---------------- + interface ThunkEntityDTO { thunk: any payload: Object /** - * Only one subscription per category is allowed. New subscription will unsubscribe and overwrite the old one + * Only one subscription per category is allowed. + * New subscription will unsubscribe and overwrite the old one */ category: CATEGORIES } -interface ThunkEntity extends ThunkEntityDTO { +/** + * This Wrapper holds the actual thunk information + * as well as additional information + */ +interface ThunkWrapper extends ThunkEntityDTO { id?: number locked: boolean } -export interface ReducerState { - thunks: { [key in keyof typeof CATEGORIES]: ThunkEntity | null } -} - export enum CATEGORIES { TABLE, TAB, } +// ---------------- reducer types ---------------- + +export interface ReducerState { + thunks: { [key in keyof typeof CATEGORIES]: ThunkWrapper | null } +} + const initialState: ReducerState = { thunks: { TABLE: null, @@ -40,7 +50,11 @@ const RoutineSlice = createSlice({ initialState, reducers: { addRoutine: (state: any, { payload }: PayloadAction<ThunkEntityDTO>) => { - const newThunk: ThunkEntity = { ...payload, locked: true } + if (state.thunks[CATEGORIES[payload.category]]?.locked) { + + } + + const newThunk: ThunkWrapper = { ...payload, locked: true } state.thunks[CATEGORIES[payload.category]] = newThunk }, @@ -48,8 +62,8 @@ const RoutineSlice = createSlice({ const thunk = state.thunks[CATEGORIES[payload.category] as any] if (!thunk) { - // TODO - throw new Error('Thunk not found') + debugMessage("Desired thunk of category " + payload.category + " is not available") + return } state.thunks[CATEGORIES[payload.category] as any] = { ...thunk, id: payload.id, locked: false } @@ -89,11 +103,17 @@ startListening({ // }, // }) -// add new routine +/** + * Add new routine + * + * This listener handles the connection between the RoutingManager that + * stores the non persistable thunk object and the peristable thunk information. + * The persistable information are stored in this reducer + */ startListening({ predicate: (action) => addRoutine.match(action), effect: async (action, listenerApi) => { - const { thunk } = action.payload as ThunkEntity + const { thunk } = action.payload as ThunkWrapper const subscription = await listenerApi.dispatch(thunk(action.payload.payload)) const thunkId = await RoutineManager.add(subscription.payload) listenerApi.dispatch( diff --git a/react-ui/src/shared/reducer/user.reducer.ts b/react-ui/src/shared/reducer/user.reducer.ts index af0f2d171675627a0a69f5aebd6e7b64125fba37..a0f2d422255f9a135236c4d8c2e3a70a5e51ec46 100755 --- a/react-ui/src/shared/reducer/user.reducer.ts +++ b/react-ui/src/shared/reducer/user.reducer.ts @@ -1,7 +1,6 @@ -import { api, RbacUser, UserServiceGetUsersApiArg } from '@api/api' +import { RbacUser } from '@api/api' import { setCookieValue } from '@helper/coookie' -import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit' -import { RootState } from '..' +import { createSlice, PayloadAction } from '@reduxjs/toolkit' export interface UserSliceState { // defined by the frontend user input. This value is getting compared with the backend response @@ -34,27 +33,4 @@ export const { setToken } = userSlice.actions export const { setUser } = userSlice.actions export default userSlice.reducer -export const userReducerPath = userSlice.reducerPath - -export const fetchUser = createAsyncThunk('user/fetchUser', (_, thunkAPI) => { - const payload: UserServiceGetUsersApiArg = {} - - thunkAPI.dispatch(api.endpoints.userServiceGetUsers.initiate(payload)).then((response) => { - if (response.error || !response.data?.user?.length) { - // TODO proper error handling - throw new Error('Fetching the pnd list after successful login failed') - } - - const { user } = thunkAPI.getState() as RootState - - // TODO ask if this is the correct approach - const matchedUser = response.data.user.find((_user) => _user.name === user.username) - - if (!matchedUser) { - // TODO proper error handling - throw new Error('No user found with the provided username') - } - - thunkAPI.dispatch(setUser(matchedUser)) - }) -}) +export const userReducerPath = userSlice.reducerPath \ No newline at end of file diff --git a/react-ui/src/shared/utils/routine.manager.ts b/react-ui/src/shared/utils/routine.manager.ts index ade07928976f5be714475325fceb2003ab6e75f0..2f1a8086ad5255fd63b2a13b149b6e3d5c708b31 100755 --- a/react-ui/src/shared/utils/routine.manager.ts +++ b/react-ui/src/shared/utils/routine.manager.ts @@ -14,7 +14,7 @@ const initialState = { /** * Routine manager is a singleton that holds all running routines. * The redux store holds any persistable information about the routines. - * The routines objects itself are stored in the RoutineManager. + * The routine objects itself are stored in the RoutineManager. */ export const RoutineManager = (() => { const state = initialState