diff --git a/react-ui/src/components/subscriptions/action.subscription.ts b/react-ui/src/components/subscriptions/action.subscription.ts
new file mode 100644
index 0000000000000000000000000000000000000000..746e3df3633f8ef2c11c75463257578b3b1f3b13
--- /dev/null
+++ b/react-ui/src/components/subscriptions/action.subscription.ts
@@ -0,0 +1,2 @@
+export const FETCH_MNE_ACTION = 'subscription/device/fetchSelectedMNE';
+export const FETCH_DEVICE_ACTION = 'subscription/device/fetchDevices'
\ No newline at end of file
diff --git a/react-ui/src/components/subscriptions/device.subscription.ts b/react-ui/src/components/subscriptions/device.subscription.ts
new file mode 100644
index 0000000000000000000000000000000000000000..5afcc7c68d5c7be6aeb2549a5ef2e5dae0d113cd
--- /dev/null
+++ b/react-ui/src/components/subscriptions/device.subscription.ts
@@ -0,0 +1,43 @@
+import { NetworkElementServiceGetAllFlattenedApiArg, api } from "@api/api";
+import { setDevices } from "@reducer/device.reducer";
+import { setUser } from "@reducer/user.reducer";
+import { createAsyncThunk } from "@reduxjs/toolkit";
+import { RootState } from "src/stores";
+import { startListening } from "../../../src/stores/middleware/listener.middleware";
+import { FETCH_DEVICE_ACTION } from "./action.subscription";
+
+// continously fetch devices
+const FETCH_DEVICES_INTERVAL = 15000; // in ms
+startListening({
+    actionCreator: setUser,
+    effect: async (_, listenerApi) => {
+        listenerApi.dispatch(fetchDevicesThunk());
+    },
+})
+
+export const fetchDevicesThunk = createAsyncThunk(FETCH_DEVICE_ACTION, (_, thunkApi) => {
+    const { user } = thunkApi.getState() as RootState;
+
+    const payload: NetworkElementServiceGetAllFlattenedApiArg = {
+        pid: Object.keys(user?.user.roles)[0],
+        timestamp: new Date().getTime().toString(),
+    }
+
+    const subscription = thunkApi.dispatch(api.endpoints.networkElementServiceGetAllFlattened.initiate(payload, {
+        subscriptionOptions: {
+            pollingInterval: FETCH_DEVICES_INTERVAL,
+            skipPollingIfUnfocused: true,
+        }
+    }));
+
+    return subscription;
+});
+
+
+// save fetched devices
+startListening({
+    predicate: (action) => api.endpoints.networkElementServiceGetAllFlattened.matchFulfilled(action),
+    effect: async (action, listenerApi) => {
+        listenerApi.dispatch(setDevices(action.payload.mne));
+    },
+})
\ No newline at end of file
diff --git a/react-ui/src/components/subscriptions/index.ts b/react-ui/src/components/subscriptions/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..2a3e02f20022a66fa6d4c68dd814bd03e0c973aa
--- /dev/null
+++ b/react-ui/src/components/subscriptions/index.ts
@@ -0,0 +1,26 @@
+import { AsyncThunk } from '@reduxjs/toolkit';
+import { fetchDevicesThunk } from './device.subscription';
+import { fetchSelectedMneThunk } from './mne.subscription';
+
+
+export enum THUNK_TYPE {
+    MNE =  'device/fetch',
+    DEVICE =  'mne/fetch',
+}
+
+export interface SubscriptionThunkModule {
+    thunkFn?: AsyncThunk<any, any, {}>
+    type: THUNK_TYPE,
+}
+
+export const SubscriptionThunks: SubscriptionThunkModule[] = [
+    {
+        thunkFn: fetchDevicesThunk,
+        type: THUNK_TYPE.DEVICE
+    }, {
+
+        thunkFn: fetchSelectedMneThunk,
+        type: THUNK_TYPE.MNE
+    }
+]
+
diff --git a/react-ui/src/components/subscriptions/mne.subscription.ts b/react-ui/src/components/subscriptions/mne.subscription.ts
new file mode 100644
index 0000000000000000000000000000000000000000..b2b036007846f030f58c27763516aaee7e8c9ef4
--- /dev/null
+++ b/react-ui/src/components/subscriptions/mne.subscription.ts
@@ -0,0 +1,45 @@
+import { api, NetworkElementServiceGetApiArg } from "@api/api";
+import { Device, setSelectedDevice, setSelectedMne } from "@reducer/device.reducer";
+import { CATEGORIES, triggerSubscription } from "@reducer/subscription.reducer";
+import { createAsyncThunk } from "@reduxjs/toolkit";
+import { RootState } from "src/stores";
+import { THUNK_TYPE } from ".";
+import { startListening } from "../../../src/stores/middleware/listener.middleware";
+import { FETCH_MNE_ACTION } from "./action.subscription";
+
+// fetch mne if selected device is set
+startListening({
+    predicate: (action) => setSelectedDevice.match(action) && !!action.payload,
+    effect: async (action, listenerApi) => {
+        listenerApi.dispatch(triggerSubscription({category: CATEGORIES.TAB, thunkType: THUNK_TYPE.MNE, payload: action.payload}));
+    },
+})
+
+
+const FETCH_MNE_INTERVAL = 5000; // in ms
+export const fetchSelectedMneThunk = createAsyncThunk(FETCH_MNE_ACTION, async (device: Device, thunkApi) => {
+    const { user } = thunkApi.getState() as RootState;
+
+    const payload: NetworkElementServiceGetApiArg = {
+        pid: Object.keys(user?.user.roles)[0],
+        timestamp: new Date().getTime().toString(),
+        mneid: device.id,
+    }
+
+    const subscription = thunkApi.dispatch(api.endpoints.networkElementServiceGet.initiate(payload, {
+        subscriptionOptions: {
+            pollingInterval: FETCH_MNE_INTERVAL,
+            skipPollingIfUnfocused: true,
+        }
+    }));
+
+    return {...subscription};
+});
+
+// save fetched mne
+startListening({
+    predicate: (action) => api.endpoints.networkElementServiceGet.matchFulfilled(action),
+    effect: async (action, listenerApi) => {
+        listenerApi.dispatch(setSelectedMne(action.payload.mne));
+    },
+})
\ No newline at end of file
diff --git a/react-ui/src/components/view/device/deivce.view.tabs.tsx b/react-ui/src/components/view/device/deivce.view.tabs.tsx
deleted file mode 100644
index a49c7616baf74fb12991ede6ee410ff533a54fa6..0000000000000000000000000000000000000000
--- a/react-ui/src/components/view/device/deivce.view.tabs.tsx
+++ /dev/null
@@ -1,28 +0,0 @@
-
-export enum DeviceViewTabValues {
-    METADATA = 'metadata',
-    YANGMODEL = 'yang_model'
-}
-
-export const DeviceViewTabs = (activeTab: DeviceViewTabValues) => {
-    
-    const metadataTab = () => {
-        return  (
-            <div>test</div>
-        )
-    }
-    
-    const yangModelTab = () => {
-        return (
-            <div>asdf</div>
-        )
-    }
-    
-
-    return (
-        <>
-            {(activeTab === DeviceViewTabValues.METADATA) && metadataTab()}
-            {(activeTab === DeviceViewTabValues.YANGMODEL) && yangModelTab()}
-        </>
-    );
-}
diff --git a/react-ui/src/components/view/device/device.view.table.tsx b/react-ui/src/components/view/device/device.view.table.tsx
index e2e6c4db5707086618c23bee8f985cc9245d3411..17d4bfd25e598320bc4ab0a2791b3ee558a69d84 100644
--- a/react-ui/src/components/view/device/device.view.table.tsx
+++ b/react-ui/src/components/view/device/device.view.table.tsx
@@ -7,7 +7,8 @@ import { useTranslation } from "react-i18next";
 export const DeviceViewTable = (searchRef: MutableRefObject<HTMLInputElement>) => {
     const { devices, pnds } = useAppSelector(state => state.device);
     const { t } = useTranslation('common');
-    const { searchTerm } = useDeviceTableViewModel(searchRef);
+    const { searchTerm, trClickHandler } = useDeviceTableViewModel(searchRef);
+
 
     const cropUUID = (uuid: string): string => {
         return uuid.substring(0, 3) + "..." + uuid.substring(uuid.length - 3, uuid.length);
@@ -27,7 +28,7 @@ export const DeviceViewTable = (searchRef: MutableRefObject<HTMLInputElement>) =
             const user = pnds.find(pnd => pnd.id === device.pid);
 
             return (
-                <tr key={index}>
+                <tr key={index} onClick={() => trClickHandler(device)}>
                     <td>{device.name}</td>
                     <OverlayTrigger overlay={<Tooltip id={device.id}>{device.id}</Tooltip>}>
                         <td>{cropUUID(device.id)}</td>
diff --git a/react-ui/src/components/view/device/device.view.tabs.tsx b/react-ui/src/components/view/device/device.view.tabs.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..cc096b14705cd89e2c57acfdbebd37614f39edc4
--- /dev/null
+++ b/react-ui/src/components/view/device/device.view.tabs.tsx
@@ -0,0 +1,57 @@
+import { useAppSelector } from "@hooks";
+
+export enum DeviceViewTabValues {
+    METADATA = 'metadata',
+    YANGMODEL = 'yang_model'
+}
+
+export const DeviceViewTabs = (activeTab: DeviceViewTabValues) => {
+    const { selectedDevice } = useAppSelector(state => state.device);
+
+
+    const metadataTab = () => {
+        return (
+            <div>
+                {selectedDevice.mne.name}
+            </div>
+        )
+    }
+
+    const yangModelTab = () => {
+        return (
+            <div>asdf</div>
+        )
+    }
+
+    const renderLoading = () => {
+        return (
+            <div>
+                Loading...
+            </div>
+        )
+    }
+
+    const renderNoDeviceSelected = () => {
+
+        return (
+            <div>
+                No device selected
+            </div>
+        )
+    }
+
+
+    return (
+        <>
+            {selectedDevice?.mne ? (
+                <>
+                    {activeTab === DeviceViewTabValues.METADATA && metadataTab()}
+                    {activeTab === DeviceViewTabValues.YANGMODEL && yangModelTab()}
+                </>
+            ) :
+                selectedDevice ? renderLoading() : renderNoDeviceSelected()
+            }
+
+        </>
+    );
+}
diff --git a/react-ui/src/components/view/device/device.view.tsx b/react-ui/src/components/view/device/device.view.tsx
index d4955d4b334ac65a6f9c8bd3ae50a46870884fbd..0ef5c05b96ded16966d81393ab681e215b1fd5c5 100644
--- a/react-ui/src/components/view/device/device.view.tsx
+++ b/react-ui/src/components/view/device/device.view.tsx
@@ -2,7 +2,7 @@ import { useDeviceViewModel } from '@viewmodel/device.viewmodel';
 import { useRef } from 'react';
 import { Button, Col, Container, FloatingLabel, Form, Nav, NavLink, Row } from 'react-bootstrap';
 import { useTranslation } from 'react-i18next';
-import { DeviceViewTabs, DeviceViewTabValues } from './deivce.view.tabs';
+import { DeviceViewTabs, DeviceViewTabValues } from './device.view.tabs';
 import './device.scss';
 import { DeviceViewTable } from './device.view.table';
 
diff --git a/react-ui/src/components/view_model/device.table.viewmodel.ts b/react-ui/src/components/view_model/device.table.viewmodel.ts
index 224904699c3a26ccc3e6f1498035c27efd41ca72..22d8ae9e0857891d20a6b5504c6c7df7165c44b2 100644
--- a/react-ui/src/components/view_model/device.table.viewmodel.ts
+++ b/react-ui/src/components/view_model/device.table.viewmodel.ts
@@ -1,7 +1,11 @@
+import { useAppDispatch } from "@hooks";
+import { Device, setSelectedDevice } from "@reducer/device.reducer";
 import { useEffect, useState } from "react";
 
 export const useDeviceTableViewModel = (searchRef) => {
     const [searchTerm, setSearchTerm] = useState('');
+    const dispatch = useAppDispatch();
+
 
     useEffect(() => {
         const handleSearchChange = () => {
@@ -21,9 +25,13 @@ export const useDeviceTableViewModel = (searchRef) => {
         };
     }, []);
 
+    const trClickHandler = (device: Device) => {
+        dispatch(setSelectedDevice(device));
+    }
 
 
     return {
-        searchTerm
+        searchTerm,
+        trClickHandler
     }
 }
\ No newline at end of file
diff --git a/react-ui/src/components/view_model/device.viewmodel.ts b/react-ui/src/components/view_model/device.viewmodel.ts
index b7b46cd6aa7b2e044b68c497cc24111d881b7514..ec75927f033dd5d19b4779fd6d6fe6d000e6cd39 100644
--- a/react-ui/src/components/view_model/device.viewmodel.ts
+++ b/react-ui/src/components/view_model/device.viewmodel.ts
@@ -1,6 +1,6 @@
 import { useAppDispatch, useAppSelector } from "@hooks";
 import { setActiveTab as setActiveTabState } from "@reducer/device.reducer";
-import { DeviceViewTabValues } from "@view/device/deivce.view.tabs";
+import { DeviceViewTabValues } from "@view/device/device.view.tabs";
 
 export const useDeviceViewModel = () => {
     const {activeTab} = useAppSelector(state => state.device);
diff --git a/react-ui/src/index.tsx b/react-ui/src/index.tsx
index 3eb8a604f746fc0f6f677e7b16f0debe39942559..a2ddc81a9091427a9d3eaf25a711836dc6819de3 100644
--- a/react-ui/src/index.tsx
+++ b/react-ui/src/index.tsx
@@ -32,4 +32,6 @@ ReactDOM.createRoot(document.getElementById("root")).render(
             </PersistGate>
         </Provider>
     </React.StrictMode>
-);
\ No newline at end of file
+);
+
+import './components/subscriptions'
\ No newline at end of file
diff --git a/react-ui/src/stores/index.ts b/react-ui/src/stores/index.ts
index e00e33f485e2d834ef3dc254d788977bb2662750..8d2be362f62efa197e771cb2caaecd9798c4cc60 100644
--- a/react-ui/src/stores/index.ts
+++ b/react-ui/src/stores/index.ts
@@ -1,11 +1,12 @@
 import { configureStore } from '@reduxjs/toolkit'
 import { setupListeners } from '@reduxjs/toolkit/query'
+import { FETCH_DEVICE_ACTION, FETCH_MNE_ACTION } from '@subscription/action.subscription'
 import { FLUSH, PAUSE, PERSIST, PURGE, REGISTER, REHYDRATE } from 'redux-persist'
 import persistStore from 'redux-persist/es/persistStore'
 import { emptySplitApi } from './api.store'
 import { rtkQueryErrorLogger } from './middleware/devLogging.middleware'
-import persistedReducer from './persist.store'
 import { listenerMiddleware } from './middleware/listener.middleware'
+import persistedReducer from './persist.store'
 
 
 export const store = configureStore({
@@ -13,7 +14,7 @@ export const store = configureStore({
   middleware: (getDefaultMiddleware) =>
     getDefaultMiddleware({
       serializableCheck: {
-        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER],
+        ignoredActions: [FLUSH, REHYDRATE, PAUSE, PERSIST, PURGE, REGISTER, FETCH_DEVICE_ACTION + '/fulfilled', FETCH_MNE_ACTION + '/fulfilled'],
       },
     }).prepend(listenerMiddleware.middleware).concat(emptySplitApi.middleware, rtkQueryErrorLogger),
 })
diff --git a/react-ui/src/stores/persist.store.ts b/react-ui/src/stores/persist.store.ts
index feb5241a879f80dfeafa7de5b5e7276c5813b07e..f14de1bd7f383e3f667a880293a34a52605c8c3e 100644
--- a/react-ui/src/stores/persist.store.ts
+++ b/react-ui/src/stores/persist.store.ts
@@ -1,24 +1,24 @@
+import deviceReducer from "@reducer/device.reducer";
+import subscriptionReducer from "@reducer/subscription.reducer";
 import userReducer from "@reducer/user.reducer";
 import { combineReducers } from "redux";
 import { persistReducer } from "redux-persist";
 import storage from "redux-persist/es/storage";
 import { emptySplitApi } from "./api.store";
-import scheduleReducer from "@reducer/schedule.reducer";
-import deviceReducer from "@reducer/device.reducer";
 
 
 /** local storage config */
 const rootPersistConfig = {
     key: 'root',
     storage,
-    blacklist: [emptySplitApi.reducerPath],
+    blacklist: [emptySplitApi.reducerPath, ],
 }
 
 
 const rootReducer = combineReducers({
     user: userReducer, 
     device: deviceReducer, 
-    scheduler: scheduleReducer, 
+    subscription: subscriptionReducer, 
     [emptySplitApi.reducerPath]: emptySplitApi.reducer,
 })
 
diff --git a/react-ui/src/stores/reducer/device.reducer.ts b/react-ui/src/stores/reducer/device.reducer.ts
index 3252fd8c792d6a2929878ebd5e348976edbdcd82..5919062d27f4464dca20b03e78d2cf4ca4d3148c 100644
--- a/react-ui/src/stores/reducer/device.reducer.ts
+++ b/react-ui/src/stores/reducer/device.reducer.ts
@@ -1,25 +1,30 @@
-import { api, NetworkelementFlattenedManagedNetworkElement, NetworkElementServiceGetAllFlattenedApiArg, PndPrincipalNetworkDomain, PndServiceGetPndListApiArg } from '@api/api';
+import { api, NetworkelementFlattenedManagedNetworkElement, NetworkelementManagedNetworkElement, PndPrincipalNetworkDomain, PndServiceGetPndListApiArg } from '@api/api';
 import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
-import { QueryActionCreatorResult } from '@reduxjs/toolkit/query';
-import { RootState } from '..';
+import { DeviceViewTabValues } from '@view/device/device.view.tabs';
 import { startListening } from '../middleware/listener.middleware';
-import { setUser } from './user.reducer';
-import { DeviceViewTabValues } from '@view/device/deivce.view.tabs';
 
-type Device = NetworkelementFlattenedManagedNetworkElement;
+export type Device = NetworkelementFlattenedManagedNetworkElement;
 
+interface SelectedDeviceInterface {
+    device: Device,
+    mne: NetworkelementManagedNetworkElement | null
+}
+
+type SelectedDeviceType = SelectedDeviceInterface | undefined;
 
 export interface DeviceSliceState {
     devices: Device[],
     pnds: PndPrincipalNetworkDomain[],
-    
+
     activeTab: DeviceViewTabValues
+    selectedDevice: SelectedDeviceType
 }
 
 const initialState: DeviceSliceState = {
     devices: [],
     pnds: [],
-    activeTab: DeviceViewTabValues.METADATA
+    activeTab: DeviceViewTabValues.METADATA,
+    selectedDevice: null
 }
 
 const deviceSlice = createSlice({
@@ -29,10 +34,19 @@ const deviceSlice = createSlice({
         setDevices: (state, action: PayloadAction<Device[]>) => { state.devices = action.payload },
         setPnds: (state, action: PayloadAction<PndPrincipalNetworkDomain[]>) => { state.pnds = action.payload },
         setActiveTab: (state, action: PayloadAction<DeviceViewTabValues>) => { state.activeTab = action.payload },
+        setSelectedDevice: (state, action: PayloadAction<Device | null>) => { 
+            let selectedDevice: SelectedDeviceType;
+            if (action.payload) {
+                selectedDevice =  {device: action.payload, mne: null};
+            }
+
+            state.selectedDevice = selectedDevice;
+        },
+        setSelectedMne: (state, action: PayloadAction<NetworkelementManagedNetworkElement>) => { state.selectedDevice.mne = action.payload },
     },
 })
 
-export const { setDevices, setActiveTab } = deviceSlice.actions
+export const { setDevices, setActiveTab, setSelectedDevice, setSelectedMne } = deviceSlice.actions
 const { setPnds } = deviceSlice.actions
 
 export default deviceSlice.reducer
@@ -40,45 +54,6 @@ export const deviceReducerPath = deviceSlice.reducerPath;
 
 
 
-let fetchSubscription: QueryActionCreatorResult<any>[] = [];
-export const abortFetching = () => {
-    fetchSubscription.forEach((subscription) => {
-        subscription.unsubscribe();
-    });
-    fetchSubscription = [];
-}
-
-// continously fetch devices
-const FETCH_DEVICES_INTERVAL = 15000; // in ms
-startListening({
-    actionCreator: setUser,
-    effect: async (_, listenerApi) => {
-        const { user } = listenerApi.getState() as RootState;
-
-        const payload: NetworkElementServiceGetAllFlattenedApiArg = {
-            pid: Object.keys(user?.user.roles)[0],
-            timestamp: new Date().getTime().toString(),
-        }
-
-        const subscription = listenerApi.dispatch(api.endpoints.networkElementServiceGetAllFlattened.initiate(payload, {
-            subscriptionOptions: {
-                pollingInterval: FETCH_DEVICES_INTERVAL,
-                skipPollingIfUnfocused: true,
-            }
-        }));
-
-        fetchSubscription = [...fetchSubscription, subscription];
-    },
-})
-
-// save fetched devices
-startListening({
-    predicate: (action) => api.endpoints.networkElementServiceGetAllFlattened.matchFulfilled(action),
-    effect: async (action, listenerApi) => {
-        listenerApi.dispatch(setDevices(action.payload.mne));
-    },
-})
-
 export const fetchPnds = createAsyncThunk('device/fetchPnds', (_, thunkApi) => {
     const payload: PndServiceGetPndListApiArg = {
         timestamp: new Date().getTime().toString(),
@@ -89,3 +64,4 @@ export const fetchPnds = createAsyncThunk('device/fetchPnds', (_, thunkApi) => {
         thunkApi.dispatch(setPnds(response.pnd));
     });
 });
+
diff --git a/react-ui/src/stores/reducer/schedule.reducer.ts b/react-ui/src/stores/reducer/schedule.reducer.ts
deleted file mode 100644
index 2e2bbe36ba76278a7c16533a1839081ca370a6a1..0000000000000000000000000000000000000000
--- a/react-ui/src/stores/reducer/schedule.reducer.ts
+++ /dev/null
@@ -1,72 +0,0 @@
-import { PayloadAction, createSlice } from '@reduxjs/toolkit';
-import { startListening } from '../middleware/listener.middleware';
-
-export enum ScheduleState { INIT, RUNNING, STOPPED }
-
-export type Task = {
-    job: (options: object) => void,
-    interval: number,
-    type: string
-};
-
-interface Schedule {
-    task: Task,
-    id: number,
-    state: ScheduleState,
-    intervalId: NodeJS.Timeout | undefined
-}
-
-export interface ScheduleReducerState {
-    schedules: Schedule[]
-}
-
-const initialState: ScheduleReducerState = {
-    schedules: []
-}
-
-const ScheduleSlice = createSlice({
-    name: 'schedule',
-    initialState,
-    reducers: {
-        registerTask: (state, action: PayloadAction<Task>) => {
-            const newSchedule = {
-                task: action.payload.task,
-                id: state.schedules.length,
-                state: ScheduleState.INIT,
-                intervalId: undefined
-            }
-
-            state.schedules = [...state.schedules, newSchedule]
-        },
-        startSchedule: (state, action: PayloadAction<Schedule>) => {
-            const schedule = action.payload;
-            schedule.intervalId = setInterval(schedule.task.job, schedule.task.interval);
-            schedule.state = ScheduleState.RUNNING;
-            
-            state.schedules[schedule.id] = schedule; 
-        },
-    },
-})
-
-export const { registerTask } = ScheduleSlice.actions
-export const { startSchedule } = ScheduleSlice.actions
-
-export default ScheduleSlice.reducer
-
-
-// startListening({
-//     actionCreator: addSchedule,
-
-//     effect: (action, listenerApi) => {
-//         const newState = listenerApi.getState() as ScheduleReducerState;
-//         const originalState = listenerApi.getOriginalState() as ScheduleReducerState;
-
-//         // get the added schedule
-//         const schedule = newState.schedules.filter(s => !originalState.schedules.includes(s)).at(0);
-//         if (!schedule) {
-//             throw new Error("Added schedule not found in store");
-//         }
-
-//         listenerApi.dispatch(startSchedule(schedule))
-//     },
-// })
\ No newline at end of file
diff --git a/react-ui/src/stores/reducer/subscription.reducer.ts b/react-ui/src/stores/reducer/subscription.reducer.ts
new file mode 100644
index 0000000000000000000000000000000000000000..a21024eab0d1f867d33edc479a15f72dfd1fdf85
--- /dev/null
+++ b/react-ui/src/stores/reducer/subscription.reducer.ts
@@ -0,0 +1,105 @@
+import { PayloadAction, createSlice, current } from '@reduxjs/toolkit';
+import { SubscriptionThunks, THUNK_TYPE } from '@subscription/index';
+import { RootState } from '..';
+import { addSubscription, unsubscribe, unsubscribeAll } from '../../utils/api/subscription.handler';
+import { startListening } from '../middleware/listener.middleware';
+
+
+
+interface ThunkEntityDTO {
+    thunkType: THUNK_TYPE,
+    payload: any
+
+    /**
+     * Only one subscription per category is allowed. New subscription will unsubscribe and overwrite the old one
+     */
+    category: CATEGORIES,
+}
+
+interface ThunkEntity extends ThunkEntityDTO {
+    id?: number,
+    locked: boolean    
+}
+
+
+export interface SubscriptionReducerState {
+    thunks: {[key in keyof typeof CATEGORIES]: ThunkEntity | null}
+}
+
+export enum CATEGORIES {
+    TABLE,
+    TAB
+}
+
+const initialState: SubscriptionReducerState = {
+    thunks: {
+        TABLE: null,
+        TAB: null
+    }
+}
+
+const SubscriptionSlice = createSlice({
+    name: 'subscription',
+    initialState,
+    reducers: {
+        triggerSubscription: (state, {payload}: PayloadAction<ThunkEntityDTO>) => {
+            // overwrite old subscription if it exists 
+            const currentState = current(state)
+            const currentThunk = currentState.thunks[CATEGORIES[payload.category]];
+            
+            const newThunk: ThunkEntity = {...payload, locked: true};
+            
+            state.thunks[CATEGORIES[payload.category]] = newThunk;
+        },
+
+        setThunkId: (state, {payload}: PayloadAction<{id: number, category: CATEGORIES}>) => {
+            let thunk = state.thunks[CATEGORIES[payload.category]];
+
+            if (!thunk) {
+                // TODO
+                throw new Error('Thunk not found');
+            }
+            
+            state.thunks[CATEGORIES[payload.category]] = {...thunk, id: payload.id, locked: false};
+        },
+
+        stopAllSubscriptions: (state) => {
+            unsubscribeAll()
+            state.thunks = initialState.thunks;
+        },
+    },
+})
+
+export const { triggerSubscription, stopAllSubscriptions } = SubscriptionSlice.actions
+
+// unsubscribe old subscription 
+startListening({
+    predicate: (action) => triggerSubscription.match(action),
+    effect: async (action, listenerApi) => {
+        const {subscription} = listenerApi.getOriginalState() as RootState;
+        const lastThunk = subscription.thunks[CATEGORIES[action.payload.category]];
+        unsubscribe(lastThunk.id);
+    },
+})
+
+// add new subscription
+startListening({
+    predicate: (action) => triggerSubscription.match(action),
+    effect: async (action, listenerApi) => {
+        const {thunkType} = action.payload as ThunkEntity;
+
+        const {thunkFn} = SubscriptionThunks.find(({type}) => type === thunkType);
+        if (!thunkFn) {
+            // TODO
+            throw new Error('Thunk not found');
+        }
+        
+        const subscription = await listenerApi.dispatch(thunkFn(action.payload.payload));
+        const thunkId = await addSubscription(subscription.payload);
+        listenerApi.dispatch(SubscriptionSlice.actions.setThunkId({id: thunkId, category: action.payload.category}));
+
+    },
+})
+
+
+export default SubscriptionSlice.reducer
diff --git a/react-ui/src/utils/api/subscription.handler.ts b/react-ui/src/utils/api/subscription.handler.ts
new file mode 100644
index 0000000000000000000000000000000000000000..e288a4a6f468fc1f919edcd2e65c46966f14f4f3
--- /dev/null
+++ b/react-ui/src/utils/api/subscription.handler.ts
@@ -0,0 +1,65 @@
+import { QueryActionCreatorResult } from '@reduxjs/toolkit/query';
+
+type SubscriptionType = QueryActionCreatorResult<any>;
+
+interface SubscriptionEntity {
+    subscription: SubscriptionType,
+    id: number
+}
+
+interface SubscriptionReducerState {
+    subscriptions: SubscriptionEntity[]
+}
+
+
+const initialState: SubscriptionReducerState = {
+    subscriptions: []
+}
+
+let state = initialState;
+
+
+export const addSubscription = (subscription: SubscriptionType): number => {
+    const id = state.subscriptions.length;
+
+    const subscriptionEntity: SubscriptionEntity = {
+        subscription,
+        id
+    }
+
+    state.subscriptions = [...state.subscriptions, subscriptionEntity];
+
+    return id;
+}
+
+
+export const unsubscribeAll = () => {
+    state.subscriptions.forEach(({ subscription }) => {
+        unsubscribeAction(subscription)
+    });
+
+    state.subscriptions = initialState.subscriptions;
+}
+
+/**
+ * @param id 
+ * @returns returns true if the subscription was stopped, false if it was not found
+ */
+export const unsubscribe = (id: number): boolean => {
+    const subscription = state.subscriptions.find(({ id: subscriptionId }) => subscriptionId === id);
+
+    if (subscription) {
+        unsubscribeAction(subscription.subscription);
+    }
+
+    return !!subscription;
+}
+
+/**
+ * Actual unsubscribe action
+ * 
+ * @param subscription 
+ */
+const unsubscribeAction = (subscription: SubscriptionType) => {
+    subscription.unsubscribe();
+}
\ No newline at end of file
diff --git a/react-ui/src/utils/provider/auth.provider.tsx b/react-ui/src/utils/provider/auth.provider.tsx
index e3a90d7118edeeb558523116e2771570e5049190..44901c66f80d47a43c339120e897fc613595ac78 100644
--- a/react-ui/src/utils/provider/auth.provider.tsx
+++ b/react-ui/src/utils/provider/auth.provider.tsx
@@ -1,7 +1,7 @@
 import { AuthServiceLoginApiArg, AuthServiceLoginApiResponse, useAuthServiceLoginMutation } from "@api/api";
+import { unsubscribeAll } from "@api/subscription.handler";
 import { getCookieValue } from "@helper/coookie";
 import { useAppDispatch, useAppSelector } from "@hooks";
-import { abortFetching } from "@reducer/device.reducer";
 import { setToken } from "@reducer/user.reducer";
 import { DEVICE_URL, LOGIN_URL } from "@routes";
 import { jwtDecode } from "jwt-decode";
@@ -37,7 +37,7 @@ export const AuthProvider = ({ children }) => {
 
     useEffect(() => {
         const token = getCookieValue('token');
-        
+
         if (token) {
             navigate(DEVICE_URL)
         } else {
@@ -100,7 +100,7 @@ export const AuthProvider = ({ children }) => {
     }
 
     const logout = () => {
-        abortFetching();
+        unsubscribeAll();
         dispatch(setToken(null));
         // TODO: purge other information
     }
@@ -125,3 +125,4 @@ export const AuthProvider = ({ children }) => {
 export const useAuth = () => {
     return useContext(AuthContext);
 }
+
diff --git a/react-ui/tsconfig.json b/react-ui/tsconfig.json
index 32b67b0d1754aa4b4f5e592231a532dc52fdf60f..41c9c8f6684c18bff5f0ac8aa7c808040e92cfbf 100644
--- a/react-ui/tsconfig.json
+++ b/react-ui/tsconfig.json
@@ -1,44 +1,47 @@
 {
     "compilerOptions": {
-      "target": "ES2020",
-      "useDefineForClassFields": true,
-      "lib": ["ES2020", "DOM", "DOM.Iterable"],
-      "module": "ESNext",
-      "skipLibCheck": true,
-  
-      /* Bundler mode */
-      "moduleResolution": "bundler",
-      "allowImportingTsExtensions": true,
-      "resolveJsonModule": true,
-      "isolatedModules": true,
-      "noEmit": true,
-      "jsx": "react-jsx",
-  
-      /* Linting */
-      "strict": false,
-      "noUnusedLocals": true,
-      "noUnusedParameters": true,
-      "noFallthroughCasesInSwitch": true,
+        "target": "ES2020",
+        "useDefineForClassFields": true,
+        "lib": ["ES2020", "DOM", "DOM.Iterable"],
+        "module": "ESNext",
+        "skipLibCheck": true,
 
-      "baseUrl": ".",
-      "paths": {
-          "@assets/*": ["assets/*"],
-          "@api/*": ["src/utils/api/*"],
-          "@viewmodel/*": ["src/components/view_model/*"],
-          "@view/*": ["src/components/view/*"],
-          "@reducer/*": ["src/stores/reducer/*"],
-          "@provider/*": ["src/utils/provider/*"],
-          "@layout/*": ["src/utils/layouts/*"],
-          "@hooks": ["src/hooks"],
-          "@routes": ["src/routes.tsx"],
-          "@task/*": ["src/utils/tasks/*"],
-          "@helper/*": ["src/utils/helper/*"],
-      }
+        /* Bundler mode */
+        "moduleResolution": "bundler",
+        "allowImportingTsExtensions": true,
+        "resolveJsonModule": true,
+        "isolatedModules": true,
+        "noEmit": true,
+        "jsx": "react-jsx",
+
+        /* Linting */
+        "strict": false,
+        "noUnusedLocals": true,
+        "noUnusedParameters": true,
+        "noFallthroughCasesInSwitch": true,
+
+        "baseUrl": ".",
+        "paths": {
+            "@assets/*": ["assets/*"],
+            "@api/*": ["src/utils/api/*"],
+            "@viewmodel/*": ["src/components/view_model/*"],
+            "@view/*": ["src/components/view/*"],
+            "@reducer/*": ["src/stores/reducer/*"],
+            "@provider/*": ["src/utils/provider/*"],
+            "@layout/*": ["src/utils/layouts/*"],
+            "@hooks": ["src/hooks"],
+            "@routes": ["src/routes.tsx"],
+            "@task/*": ["src/utils/tasks/*"],
+            "@helper/*": ["src/utils/helper/*"],
+            "@subscription/*": ["src/components/subscriptions/*"]
+        }
     },
     "include": [
-      "src/**/*.d.ts",
-      "src/**/*.ts",
-      "src/**/*.tsx", "src/stores/api.store.ts", "scripts/test.ts",
-    ],
+        "src/**/*.d.ts",
+        "src/**/*.ts",
+        "src/**/*.tsx",
+        "src/stores/api.store.ts",
+        "scripts/test.ts"
+    ]
     //"references": [{ "path": "./tsconfig.node.json" }]
-}
\ No newline at end of file
+}
diff --git a/react-ui/vite.config.mjs b/react-ui/vite.config.mjs
index c5073c14e64ba0065869955c523069418953c1cc..96be1c8444581aa8a9268841dcc275755fb31b45 100644
--- a/react-ui/vite.config.mjs
+++ b/react-ui/vite.config.mjs
@@ -1,48 +1,56 @@
-import { defineConfig } from 'vite'
 import react from '@vitejs/plugin-react'
-
+import { defineConfig } from 'vite'
 
 export default defineConfig({
-  plugins: [react()],
-  server: {
-    port: 3000,
-    proxy: {
-      '/api': {
-        target: 'http://127.0.0.1:8080',
-        changeOrigin: true,
-        secure: false,   
-        rewrite: (path) => path.replace(/^\/api/, ''),
-        configure: (proxy, _options) => {
-          proxy.on('error', (err, _req, _res) => {
-            console.log('proxy error', err);
-          });
-          proxy.on('proxyReq', (proxyReq, req, _res) => {
-            console.log('Sending Request to the Target:', req.method, req.url);
-          });
-          proxy.on('proxyRes', (proxyRes, req, _res) => {
-            console.log('Received Response from the Target:', proxyRes.statusCode, req.url);
-          });
+    plugins: [react()],
+    server: {
+        port: 3000,
+        proxy: {
+            '/api': {
+                target: 'http://127.0.0.1:8080',
+                changeOrigin: true,
+                secure: false,
+                rewrite: (path) => path.replace(/^\/api/, ''),
+                configure: (proxy, _options) => {
+                    proxy.on('error', (err, _req, _res) => {
+                        console.log('proxy error', err)
+                    })
+                    proxy.on('proxyReq', (proxyReq, req, _res) => {
+                        console.log(
+                            'Sending Request to the Target:',
+                            req.method,
+                            req.url
+                        )
+                    })
+                    proxy.on('proxyRes', (proxyRes, req, _res) => {
+                        console.log(
+                            'Received Response from the Target:',
+                            proxyRes.statusCode,
+                            req.url
+                        )
+                    })
+                },
+            },
+        },
+    },
+    resolve: {
+        alias: {
+            '@assets': '/assets',
+            '@api': '/src/utils/api',
+            '@viewmodel': '/src/components/view_model',
+            '@view': '/src/components/view',
+            '@reducer': '/src/stores/reducer',
+            '@provider': '/src/utils/provider',
+            '@layout': '/src/utils/layouts',
+            '@hooks': '/src/hooks.ts',
+            '@task': '/src/utils/tasks',
+            '@helper': '/src/utils/helper',
+            '@routes': '/src/routes.tsx',
+            '@subscription': '/src/components/subscriptions',
         },
-      }
-    }
-  },
-  resolve: {
-    alias: {
-      '@assets': '/assets',
-      '@api': '/src/utils/api',
-      "@viewmodel": "/src/components/view_model",
-      "@view": "/src/components/view",
-      "@reducer": "/src/stores/reducer",
-      "@provider": "/src/utils/provider",
-      "@layout": "/src/utils/layouts",
-      "@hooks": "/src/hooks.ts",
-      "@task": "/src/utils/tasks",
-      "@helper": "/src/utils/helper",
-      "@routes": "/src/routes.tsx",
     },
-  },
 
-  build: {
-    sourcemap: true, // Source Maps für den Build aktivieren
-  },
-});
\ No newline at end of file
+    build: {
+        sourcemap: true, // Source Maps für den Build aktivieren
+    },
+})