diff --git a/react-ui/package.json b/react-ui/package.json
index 5767007a12e35553bf091fd00d8ec09411d7643a..30db7c97c2b869d29f09757dd1e7b558a59e645e 100755
--- a/react-ui/package.json
+++ b/react-ui/package.json
@@ -12,9 +12,11 @@
         "@fortawesome/free-regular-svg-icons": "^6.6.0",
         "@fortawesome/free-solid-svg-icons": "^6.6.0",
         "@fortawesome/react-fontawesome": "^0.2.2",
+        "@fullhuman/postcss-purgecss": "^7.0.2",
         "@reduxjs/toolkit": "^2.2.4",
         "@vitejs/plugin-react": "^4.2.1",
         "bootstrap": "^5.3.3",
+        "crypto-js": "^4.2.0",
         "dompurify": "^3.2.3",
         "i18next": "^24.0.5",
         "jwt-decode": "^4.0.0",
@@ -31,7 +33,6 @@
         "redux-persist": "^6.0.0",
         "sass": "1.82.0",
         "sass-embedded": "^1.80.6",
-        "@fullhuman/postcss-purgecss": "^7.0.2",
         "vite": "^6.0.3"
     },
     "devDependencies": {
@@ -88,4 +89,4 @@
             "last 1 safari version"
         ]
     }
-}
\ No newline at end of file
+}
diff --git a/react-ui/src/components/devices/view/device.view.table.tsx b/react-ui/src/components/devices/view/device.view.table.tsx
index 337148f92837e4ebd627a04cd059016b314f3794..56ad05f3f66a1016442371663dbffea6ccab69df 100755
--- a/react-ui/src/components/devices/view/device.view.table.tsx
+++ b/react-ui/src/components/devices/view/device.view.table.tsx
@@ -1,20 +1,20 @@
 import { insertMarkTags } from "@helper/text";
 import { useAppSelector } from "@hooks";
 import DOMPurify from 'dompurify';
-import { MutableRefObject, useCallback } from "react";
+import { MutableRefObject, useCallback, useRef } from "react";
 import { OverlayTrigger, Table, Tooltip } from "react-bootstrap";
 import { useTranslation } from "react-i18next";
 import { useDeviceTableViewModel } from "../view_model/device.table.viewmodel";
 
+const cropUUID = (uuid: string): string => {
+    return uuid.substring(0, 3) + "..." + uuid.substring(uuid.length - 3, uuid.length);
+}
+
 export const DeviceViewTable = (searchRef: MutableRefObject<HTMLInputElement>) => {
     const { devices, pnds, selected: selectedDevice } = useAppSelector(state => state.device);
     const { t } = useTranslation('common');
-    const { trClickHandler } = useDeviceTableViewModel(searchRef);
-
-
-    const cropUUID = (uuid: string): string => {
-        return uuid.substring(0, 3) + "..." + uuid.substring(uuid.length - 3, uuid.length);
-    }
+    const tableRef = useRef();
+    const { trClickHandler } = useDeviceTableViewModel(searchRef, tableRef);
 
     const getDeviceTable = useCallback(() => {
         const search = searchRef.current?.value;
@@ -34,23 +34,28 @@ export const DeviceViewTable = (searchRef: MutableRefObject<HTMLInputElement>) =
         return filtered.map((device, index) => {
             const user = pnds.find(pnd => pnd.id === device.pid);
 
+            const username = user?.name || ''
+            const deviceId = device.id!;
+            const cropedId = cropUUID(deviceId)
+            const devicename = device.name || '';
+
+            const rowData = username + ";" + deviceId + ";" + devicename
+
             return (
-                <tr key={index} onClick={() => trClickHandler(device)} className={selectedDevice?.device.id === device.id ? 'active' : ''}>
-                    <td key={0} dangerouslySetInnerHTML={{ __html: search ? insertMarkTags(device.name!, search) : DOMPurify.sanitize(device.name) }}></td>
-                    <OverlayTrigger overlay={<Tooltip id={device.id}>{device.id}</Tooltip>}>
-                        <td dangerouslySetInnerHTML={{ __html: search ? insertMarkTags(cropUUID(device.id!), search) : DOMPurify.sanitize(cropUUID(device.id!)) }}></td>
+                <tr data-copy-value={rowData} key={index} onClick={() => trClickHandler(device)} className={selectedDevice?.device.id === deviceId ? 'active' : ''}>
+                    <td data-copy-value={devicename} dangerouslySetInnerHTML={{ __html: search ? insertMarkTags(devicename, search) : DOMPurify.sanitize(devicename) }}></td>
+                    <OverlayTrigger overlay={<Tooltip id={device.id}>{deviceId}</Tooltip>}>
+                        <td data-copy-value={deviceId} dangerouslySetInnerHTML={{ __html: search ? insertMarkTags(cropedId, search) : DOMPurify.sanitize(cropedId) }}></td>
                     </OverlayTrigger>
-                    <td key={1} dangerouslySetInnerHTML={{ __html: search ? insertMarkTags(user?.name || '', search) : DOMPurify.sanitize(user?.name || '') }}></td>
+                    <td data-copy-value={username} dangerouslySetInnerHTML={{ __html: search ? insertMarkTags(username, search) : DOMPurify.sanitize(username) }}></td>
                     <td></td>
                 </tr>
             )
         })
     }, [devices, searchRef, pnds, selectedDevice, trClickHandler]);
 
-
-
     return (
-        <Table striped responsive className="device-table">
+        <Table striped responsive className="device-table" ref={tableRef}>
             <thead>
                 <tr>
                     <th>{t('device.table.header.name')}</th>
diff --git a/react-ui/src/components/devices/view_model/device.table.viewmodel.ts b/react-ui/src/components/devices/view_model/device.table.viewmodel.ts
index 5769780ffd9299d1ff0bc2ec0bf4edbae421c7cf..4b328d63955f3827a3f6cdabbf83951505fe7dfa 100755
--- a/react-ui/src/components/devices/view_model/device.table.viewmodel.ts
+++ b/react-ui/src/components/devices/view_model/device.table.viewmodel.ts
@@ -1,29 +1,97 @@
 import { Device, setSelectedDevice } from "@component/devices/reducer/device.reducer";
+import { faCopy } from "@fortawesome/free-solid-svg-icons";
 import { useAppDispatch } from "@hooks";
+import { useMenu } from "@provider/menu/menu.provider";
+import { useUtils } from "@provider/utils.provider";
 import { useEffect, useState } from "react";
+import { useTranslation } from "react-i18next";
+import { toast } from "react-toastify";
 
-export const useDeviceTableViewModel = (searchRef) => {
+export const useDeviceTableViewModel = (searchRef, tableRef) => {
     const [searchTerm, setSearchTerm] = useState('');
     const dispatch = useAppDispatch();
+    const { subscribe } = useMenu();
+    const { toClipboard } = useUtils();
+    const { t } = useTranslation('common');
+
+
+    const registerMenuOptions = () => {
+        const subscription = subscribe({
+            target: tableRef.current,
+            actions: [
+                {
+                    key: t('device.table.actions.copy'),
+                    icon: faCopy,
+                    action: (clickedElement) => {
+                        if (clickedElement) {
+                            const text = clickedElement.dataset.copyValue
+                            if (!text) {
+                                toast.warn(t('global.toast.copied_failed'))
+                                return
+                            }
+
+
+                            toClipboard(text)
+                        }
+                    }
+                },
+
+                {
+                    key: t('device.table.actions.copy_row'),
+                    icon: faCopy,
+                    action: (clickedElement) => {
+                        let parent = clickedElement;
+                        while (parent && parent.tagName !== 'TR') {
+                            parent = parent.parentNode;
+                        }
+
+                        const text = parent.dataset.copyValue
+                        if (!text) {
+                            toast.warn(t('global.toast.copied_failed'))
+                            return
+                        }
+                        toClipboard(text)
+                    }
+                }
+            ]
+        })
+
+        return () => {
+            subscription.unsubscribe()
+        }
+    }
+
+    // seperate use effect to rerun this after tableref and subscribe are initialized
+    useEffect(() => {
+        if (!subscribe || !tableRef.current) {
+            return
+        }
+
+        const unsubscribe = registerMenuOptions()
+
+        return () => {
+            unsubscribe()
+        }
+    }, [tableRef, subscribe])
 
 
     useEffect(() => {
+        if (!searchRef.current) {
+            return
+        }
+
         const handleSearchChange = () => {
-            if (searchRef.current) {
-                setSearchTerm(searchRef.current.value);
-            }
+            setSearchTerm(searchRef.current.value);
         };
 
-        if (searchRef.current) {
-            searchRef.current.addEventListener('input', handleSearchChange);
-        }
+        searchRef.current.addEventListener('input', handleSearchChange);
 
         return () => {
             if (searchRef.current) {
                 searchRef.current.removeEventListener('input', handleSearchChange);
             }
         };
-    }, []);
+    }, [searchRef]);
 
     const trClickHandler = (device: Device) => {
         dispatch(setSelectedDevice({ device }));
diff --git a/react-ui/src/i18n/locales/en/translations.json b/react-ui/src/i18n/locales/en/translations.json
index 46b76563c7d78355e9484e49d344f4132bf3766d..fb3ca729c6ecc380475dd3c5c567befcc5508668 100755
--- a/react-ui/src/i18n/locales/en/translations.json
+++ b/react-ui/src/i18n/locales/en/translations.json
@@ -6,7 +6,8 @@
                 "empty_field": "This field can“t be empty"
             },
             "toast": {
-                "copied": "Copied to clipboard"
+                "copied": "Copied to clipboard",
+                "copied_failed": "Copying to clipboard failed"
             },
             "menu_item": {
                 "logout": "Logout"
@@ -35,6 +36,10 @@
                     "uuid": "UUID",
                     "user": "User",
                     "last_updated": "Last updated"
+                },
+                "actions": {
+                    "copy": "Copy",
+                    "copy_row": "Copy row"
                 }
             },
             "search": {
diff --git a/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx b/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx
index 6c3ac78f306d0ff0b1c09263a4d791795757b51c..f0bc922126f6752e9fcb0182391bd3deea461bb7 100644
--- a/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx
+++ b/react-ui/src/shared/components/json_viewer/viewmodel/json_viewer.viewmodel.tsx
@@ -67,29 +67,31 @@ export const useJsonViewer = ({ json, search, container }: JsonViewerViewModelTy
     }
 
     const registerMenuOptions = () => {
-        if (container.current) {
-            const subscription = subscribe({
-                target: container.current,
-                actions: [
-                    {
-                        key: t('json_viewer.copy'),
-                        icon: faCopy,
-                        action: (clickedElement) => {
-                            let parent = clickedElement;
-                            while (parent && parent.tagName !== 'TR') {
-                                parent = parent.parentNode;
-                            }
-
-                            const copyValue = parent.dataset.copyValue
-                            toClipboard(copyValue)
+        if (!container.current) {
+            return () => { }
+        }
+
+        const subscription = subscribe({
+            target: container.current,
+            actions: [
+                {
+                    key: t('json_viewer.copy'),
+                    icon: faCopy,
+                    action: (clickedElement) => {
+                        let parent = clickedElement;
+                        while (parent && parent.tagName !== 'TR') {
+                            parent = parent.parentNode;
                         }
+
+                        const copyValue = parent.dataset.copyValue
+                        toClipboard(copyValue)
                     }
-                ]
-            })
+                }
+            ]
+        })
 
-            return () => {
-                subscription.unsubscribe();
-            }
+        return () => {
+            subscription.unsubscribe();
         }
     }
 
@@ -137,7 +139,7 @@ export const useJsonViewer = ({ json, search, container }: JsonViewerViewModelTy
     }, [searchTerm])
 
     useEffect(() => {
-        registerMenuOptions();
+        const unsubscribe = registerMenuOptions();
 
         if (search.current) {
             search.current.addEventListener('input', handleSearchInput)
@@ -147,6 +149,7 @@ export const useJsonViewer = ({ json, search, container }: JsonViewerViewModelTy
             if (search.current) {
                 search.current.removeEventListener('input', handleSearchInput)
             }
+            unsubscribe()
         }
     }, [])
 
diff --git a/react-ui/src/shared/provider/menu/menu.provider.tsx b/react-ui/src/shared/provider/menu/menu.provider.tsx
index b2525692b9f463312158d6443a17320e2bed19e7..f3af8e5c13573e8c317eb27e433ebf63872e093f 100644
--- a/react-ui/src/shared/provider/menu/menu.provider.tsx
+++ b/react-ui/src/shared/provider/menu/menu.provider.tsx
@@ -19,13 +19,11 @@ type Action = {
 }
 
 interface MenuProviderType {
-    subscribe: (value: SubscriptionValue) => MenuSubscription
+    subscribe: ((value: SubscriptionValue) => MenuSubscription) | null;
 }
 
 const MenuContext = createContext<MenuProviderType>({
-    subscribe: function (): MenuSubscription {
-        throw new Error("Function not implemented.");
-    }
+    subscribe: null
 })
 
 interface SubscriptionValue {
@@ -33,11 +31,16 @@ interface SubscriptionValue {
     actions: Array<Action>
 }
 
+interface SubscriptionMap {
+    [id: string]: SubscriptionValue
+}
+
 
 export const MenuProvider: React.FC<BasicProp> = ({ children }) => {
     const [menuPosition, setMenuPosition] = useState({ top: 0, left: 0 });
     const [showMenu, setShowMenu] = useState(false);
-    const [subscribedTargets, setSubscribedTargets] = useState<Array<SubscriptionValue>>([])
+    const [subscribedTargets, setSubscribedTargets] = useState<SubscriptionMap>({});
+
     const { logout } = useAuth()
 
     const { t } = useTranslation('common')
@@ -61,12 +64,15 @@ export const MenuProvider: React.FC<BasicProp> = ({ children }) => {
 
     const handleContextMenu = (event: React.MouseEvent<HTMLElement>) => {
         event.preventDefault();
-        const targets = subscribedTargets.filter(({ target }) => target.contains(event.target as HTMLElement))
+
+        const targets = Object.values(subscribedTargets).filter(
+            ({ target }) => target.contains(event.target as HTMLElement)
+        );
 
         setMenuPosition({ top: event.pageY, left: event.pageX });
-        setMenuItems(targets)
-        setClickedHtmlElement(event.target as HTMLElement)
-        displayMenu()
+        setMenuItems(targets);
+        setClickedHtmlElement(event.target as HTMLElement);
+        displayMenu();
     };
 
     const displayMenu = () => {
@@ -90,20 +96,27 @@ export const MenuProvider: React.FC<BasicProp> = ({ children }) => {
 
     const value = useMemo<MenuProviderType>(() => {
         return {
-            subscribe(target) {
-                const index = subscribedTargets.length;
+            subscribe(target: SubscriptionValue) {
+                const subscriptionId = crypto.randomUUID(); // Generate unique ID
 
-                setSubscribedTargets([...subscribedTargets, target])
+                setSubscribedTargets(prev => ({
+                    ...prev,
+                    [subscriptionId]: target
+                }));
 
                 const subscription: MenuSubscription = {
                     unsubscribe() {
-                        setSubscribedTargets([...subscribedTargets.splice(index, 1)])
+                        setSubscribedTargets(prev => {
+                            const next = { ...prev };
+                            delete next[subscriptionId];
+                            return next;
+                        });
                     },
                 }
-                return subscription
+                return subscription;
             },
         } as MenuProviderType
-    }, [])
+    }, []);
 
     return (
         <MenuContext.Provider value={value}>
diff --git a/react-ui/yarn.lock b/react-ui/yarn.lock
index 62e8d8fa11691d208d00262c2a3faae13d4170ec..ec57231a8b42fbbbc2c25e6801504874e8cba618 100755
--- a/react-ui/yarn.lock
+++ b/react-ui/yarn.lock
@@ -4073,6 +4073,11 @@ cross-spawn@^7.0.0, cross-spawn@^7.0.2, cross-spawn@^7.0.3, cross-spawn@^7.0.5:
     shebang-command "^2.0.0"
     which "^2.0.1"
 
+crypto-js@^4.2.0:
+  version "4.2.0"
+  resolved "https://registry.yarnpkg.com/crypto-js/-/crypto-js-4.2.0.tgz#4d931639ecdfd12ff80e8186dba6af2c2e856631"
+  integrity sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==
+
 crypto-random-string@^2.0.0:
   version "2.0.0"
   resolved "https://registry.yarnpkg.com/crypto-random-string/-/crypto-random-string-2.0.0.tgz#ef2a7a966ec11083388369baa02ebead229b30d5"