From cbe02b3b9cb8f61b8d6d65560fdd18f2a68323ee Mon Sep 17 00:00:00 2001
From: Johannes Mey <johannes.mey@tu-dresden.de>
Date: Mon, 11 Jul 2022 13:16:47 +0200
Subject: [PATCH] use redux

---
 dashboard/package-lock.json | 266 ++++++++++++++++++++++++++++++++++++
 dashboard/package.json      |   5 +
 dashboard/src/App.css       |   4 -
 dashboard/src/App.js        | 137 +++++++++----------
 dashboard/src/index.js      |   4 +
 dashboard/src/mgSlice.js    |  23 ++++
 dashboard/src/store.js      |   8 ++
 7 files changed, 371 insertions(+), 76 deletions(-)
 create mode 100644 dashboard/src/mgSlice.js
 create mode 100644 dashboard/src/store.js

diff --git a/dashboard/package-lock.json b/dashboard/package-lock.json
index 2359a9d..2c3826c 100644
--- a/dashboard/package-lock.json
+++ b/dashboard/package-lock.json
@@ -8,6 +8,7 @@
       "name": "dashboard",
       "version": "0.1.0",
       "dependencies": {
+        "@reduxjs/toolkit": "^1.8.3",
         "@testing-library/jest-dom": "^5.16.4",
         "@testing-library/react": "^13.3.0",
         "@testing-library/user-event": "^13.5.0",
@@ -15,9 +16,13 @@
         "react": "^18.2.0",
         "react-dom": "^18.2.0",
         "react-icons": "^4.4.0",
+        "react-redux": "^8.0.2",
         "react-scripts": "5.0.1",
         "react-vertical-timeline-component": "^3.5.2",
         "web-vitals": "^2.1.4"
+      },
+      "devDependencies": {
+        "@redux-devtools/core": "^3.13.1"
       }
     },
     "node_modules/@ampproject/remapping": {
@@ -2984,6 +2989,60 @@
         }
       }
     },
+    "node_modules/@redux-devtools/core": {
+      "version": "3.13.1",
+      "resolved": "https://registry.npmjs.org/@redux-devtools/core/-/core-3.13.1.tgz",
+      "integrity": "sha512-VZbma4b28D7dLn6rKTxx4r1KJrgiT2EQNF4vjkpTlXTu0cQcHkEcAO9ixMBj6rZGrT/jinCHq8gBy2bWgnDvcA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/runtime": "^7.18.3",
+        "@redux-devtools/instrument": "^2.1.0",
+        "@types/prop-types": "^15.7.5",
+        "lodash": "^4.17.21",
+        "prop-types": "^15.8.1"
+      },
+      "peerDependencies": {
+        "react": "^0.14.9 || ^15.3.0 || ^16.0.0 || ^17.0.0 || ^18.0.0",
+        "react-redux": "^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0",
+        "redux": "^3.5.2 || ^4.0.0"
+      }
+    },
+    "node_modules/@redux-devtools/instrument": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/@redux-devtools/instrument/-/instrument-2.1.0.tgz",
+      "integrity": "sha512-e8fo88kuq/zWqfNf6S/GNfaQMjF4WSPpucmYfRhzZyyXHC3PCLd/xgz7zooPErDh9QwUXK6sTVYvrkq7hPbsFA==",
+      "dev": true,
+      "dependencies": {
+        "@babel/runtime": "^7.16.7",
+        "lodash": "^4.17.21"
+      },
+      "peerDependencies": {
+        "redux": "^3.4.0 || ^4.0.0"
+      }
+    },
+    "node_modules/@reduxjs/toolkit": {
+      "version": "1.8.3",
+      "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.3.tgz",
+      "integrity": "sha512-lU/LDIfORmjBbyDLaqFN2JB9YmAT1BElET9y0ZszwhSBa5Ef3t6o5CrHupw5J1iOXwd+o92QfQZ8OJpwXvsssg==",
+      "dependencies": {
+        "immer": "^9.0.7",
+        "redux": "^4.1.2",
+        "redux-thunk": "^2.4.1",
+        "reselect": "^4.1.5"
+      },
+      "peerDependencies": {
+        "react": "^16.9.0 || ^17.0.0 || ^18",
+        "react-redux": "^7.2.1 || ^8.0.2"
+      },
+      "peerDependenciesMeta": {
+        "react": {
+          "optional": true
+        },
+        "react-redux": {
+          "optional": true
+        }
+      }
+    },
     "node_modules/@rollup/plugin-babel": {
       "version": "5.3.1",
       "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -3658,6 +3717,15 @@
         "@types/node": "*"
       }
     },
+    "node_modules/@types/hoist-non-react-statics": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+      "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+      "dependencies": {
+        "@types/react": "*",
+        "hoist-non-react-statics": "^3.3.0"
+      }
+    },
     "node_modules/@types/html-minifier-terser": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -3968,6 +4036,11 @@
       "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
       "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
     },
+    "node_modules/@types/use-sync-external-store": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+      "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+    },
     "node_modules/@types/ws": {
       "version": "8.5.3",
       "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@@ -8324,6 +8397,19 @@
         "he": "bin/he"
       }
     },
+    "node_modules/hoist-non-react-statics": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+      "dependencies": {
+        "react-is": "^16.7.0"
+      }
+    },
+    "node_modules/hoist-non-react-statics/node_modules/react-is": {
+      "version": "16.13.1",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+      "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+    },
     "node_modules/hoopy": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -13798,6 +13884,49 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
     },
+    "node_modules/react-redux": {
+      "version": "8.0.2",
+      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.2.tgz",
+      "integrity": "sha512-nBwiscMw3NoP59NFCXFf02f8xdo+vSHT/uZ1ldDwF7XaTpzm+Phk97VT4urYBl5TYAPNVaFm12UHAEyzkpNzRA==",
+      "dependencies": {
+        "@babel/runtime": "^7.12.1",
+        "@types/hoist-non-react-statics": "^3.3.1",
+        "@types/use-sync-external-store": "^0.0.3",
+        "hoist-non-react-statics": "^3.3.2",
+        "react-is": "^18.0.0",
+        "use-sync-external-store": "^1.0.0"
+      },
+      "peerDependencies": {
+        "@types/react": "^16.8 || ^17.0 || ^18.0",
+        "@types/react-dom": "^16.8 || ^17.0 || ^18.0",
+        "react": "^16.8 || ^17.0 || ^18.0",
+        "react-dom": "^16.8 || ^17.0 || ^18.0",
+        "react-native": ">=0.59",
+        "redux": "^4"
+      },
+      "peerDependenciesMeta": {
+        "@types/react": {
+          "optional": true
+        },
+        "@types/react-dom": {
+          "optional": true
+        },
+        "react-dom": {
+          "optional": true
+        },
+        "react-native": {
+          "optional": true
+        },
+        "redux": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/react-redux/node_modules/react-is": {
+      "version": "18.2.0",
+      "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+      "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+    },
     "node_modules/react-refresh": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@@ -13955,6 +14084,22 @@
         "node": ">=8"
       }
     },
+    "node_modules/redux": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz",
+      "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
+      "dependencies": {
+        "@babel/runtime": "^7.9.2"
+      }
+    },
+    "node_modules/redux-thunk": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
+      "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
+      "peerDependencies": {
+        "redux": "^4"
+      }
+    },
     "node_modules/regenerate": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -14097,6 +14242,11 @@
       "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
       "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
     },
+    "node_modules/reselect": {
+      "version": "4.1.6",
+      "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz",
+      "integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ=="
+    },
     "node_modules/resolve": {
       "version": "1.22.1",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@@ -15606,6 +15756,14 @@
         "punycode": "^2.1.0"
       }
     },
+    "node_modules/use-sync-external-store": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+      "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+      "peerDependencies": {
+        "react": "^16.8.0 || ^17.0.0 || ^18.0.0"
+      }
+    },
     "node_modules/util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
@@ -18585,6 +18743,40 @@
         "source-map": "^0.7.3"
       }
     },
+    "@redux-devtools/core": {
+      "version": "3.13.1",
+      "resolved": "https://registry.npmjs.org/@redux-devtools/core/-/core-3.13.1.tgz",
+      "integrity": "sha512-VZbma4b28D7dLn6rKTxx4r1KJrgiT2EQNF4vjkpTlXTu0cQcHkEcAO9ixMBj6rZGrT/jinCHq8gBy2bWgnDvcA==",
+      "dev": true,
+      "requires": {
+        "@babel/runtime": "^7.18.3",
+        "@redux-devtools/instrument": "^2.1.0",
+        "@types/prop-types": "^15.7.5",
+        "lodash": "^4.17.21",
+        "prop-types": "^15.8.1"
+      }
+    },
+    "@redux-devtools/instrument": {
+      "version": "2.1.0",
+      "resolved": "https://registry.npmjs.org/@redux-devtools/instrument/-/instrument-2.1.0.tgz",
+      "integrity": "sha512-e8fo88kuq/zWqfNf6S/GNfaQMjF4WSPpucmYfRhzZyyXHC3PCLd/xgz7zooPErDh9QwUXK6sTVYvrkq7hPbsFA==",
+      "dev": true,
+      "requires": {
+        "@babel/runtime": "^7.16.7",
+        "lodash": "^4.17.21"
+      }
+    },
+    "@reduxjs/toolkit": {
+      "version": "1.8.3",
+      "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-1.8.3.tgz",
+      "integrity": "sha512-lU/LDIfORmjBbyDLaqFN2JB9YmAT1BElET9y0ZszwhSBa5Ef3t6o5CrHupw5J1iOXwd+o92QfQZ8OJpwXvsssg==",
+      "requires": {
+        "immer": "^9.0.7",
+        "redux": "^4.1.2",
+        "redux-thunk": "^2.4.1",
+        "reselect": "^4.1.5"
+      }
+    },
     "@rollup/plugin-babel": {
       "version": "5.3.1",
       "resolved": "https://registry.npmjs.org/@rollup/plugin-babel/-/plugin-babel-5.3.1.tgz",
@@ -19066,6 +19258,15 @@
         "@types/node": "*"
       }
     },
+    "@types/hoist-non-react-statics": {
+      "version": "3.3.1",
+      "resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
+      "integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
+      "requires": {
+        "@types/react": "*",
+        "hoist-non-react-statics": "^3.3.0"
+      }
+    },
     "@types/html-minifier-terser": {
       "version": "6.1.0",
       "resolved": "https://registry.npmjs.org/@types/html-minifier-terser/-/html-minifier-terser-6.1.0.tgz",
@@ -19338,6 +19539,11 @@
       "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
       "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
     },
+    "@types/use-sync-external-store": {
+      "version": "0.0.3",
+      "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
+      "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
+    },
     "@types/ws": {
       "version": "8.5.3",
       "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
@@ -22491,6 +22697,21 @@
       "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
       "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw=="
     },
+    "hoist-non-react-statics": {
+      "version": "3.3.2",
+      "resolved": "https://registry.npmjs.org/hoist-non-react-statics/-/hoist-non-react-statics-3.3.2.tgz",
+      "integrity": "sha512-/gGivxi8JPKWNm/W0jSmzcMPpfpPLc3dY/6GxhX2hQ9iGj3aDfklV4ET7NjKpSinLpJ5vafa9iiGIEZg10SfBw==",
+      "requires": {
+        "react-is": "^16.7.0"
+      },
+      "dependencies": {
+        "react-is": {
+          "version": "16.13.1",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+          "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ=="
+        }
+      }
+    },
     "hoopy": {
       "version": "0.1.4",
       "resolved": "https://registry.npmjs.org/hoopy/-/hoopy-0.1.4.tgz",
@@ -26283,6 +26504,26 @@
       "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
       "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
     },
+    "react-redux": {
+      "version": "8.0.2",
+      "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.0.2.tgz",
+      "integrity": "sha512-nBwiscMw3NoP59NFCXFf02f8xdo+vSHT/uZ1ldDwF7XaTpzm+Phk97VT4urYBl5TYAPNVaFm12UHAEyzkpNzRA==",
+      "requires": {
+        "@babel/runtime": "^7.12.1",
+        "@types/hoist-non-react-statics": "^3.3.1",
+        "@types/use-sync-external-store": "^0.0.3",
+        "hoist-non-react-statics": "^3.3.2",
+        "react-is": "^18.0.0",
+        "use-sync-external-store": "^1.0.0"
+      },
+      "dependencies": {
+        "react-is": {
+          "version": "18.2.0",
+          "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+          "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
+        }
+      }
+    },
     "react-refresh": {
       "version": "0.11.0",
       "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
@@ -26406,6 +26647,20 @@
         "strip-indent": "^3.0.0"
       }
     },
+    "redux": {
+      "version": "4.2.0",
+      "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.0.tgz",
+      "integrity": "sha512-oSBmcKKIuIR4ME29/AeNUnl5L+hvBq7OaJWzaptTQJAntaPvxIJqfnjbaEiCzzaIz+XmVILfqAM3Ob0aXLPfjA==",
+      "requires": {
+        "@babel/runtime": "^7.9.2"
+      }
+    },
+    "redux-thunk": {
+      "version": "2.4.1",
+      "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-2.4.1.tgz",
+      "integrity": "sha512-OOYGNY5Jy2TWvTL1KgAlVy6dcx3siPJ1wTq741EPyUKfn6W6nChdICjZwCd0p8AZBs5kWpZlbkXW2nE/zjUa+Q==",
+      "requires": {}
+    },
     "regenerate": {
       "version": "1.4.2",
       "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
@@ -26517,6 +26772,11 @@
       "resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
       "integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ=="
     },
+    "reselect": {
+      "version": "4.1.6",
+      "resolved": "https://registry.npmjs.org/reselect/-/reselect-4.1.6.tgz",
+      "integrity": "sha512-ZovIuXqto7elwnxyXbBtCPo9YFEr3uJqj2rRbcOOog1bmu2Ag85M4hixSwFWyaBMKXNgvPaJ9OSu9SkBPIeJHQ=="
+    },
     "resolve": {
       "version": "1.22.1",
       "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
@@ -27625,6 +27885,12 @@
         "punycode": "^2.1.0"
       }
     },
+    "use-sync-external-store": {
+      "version": "1.2.0",
+      "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
+      "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
+      "requires": {}
+    },
     "util-deprecate": {
       "version": "1.0.2",
       "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
diff --git a/dashboard/package.json b/dashboard/package.json
index a8dc7ea..f87a212 100644
--- a/dashboard/package.json
+++ b/dashboard/package.json
@@ -3,6 +3,7 @@
   "version": "0.1.0",
   "private": true,
   "dependencies": {
+    "@reduxjs/toolkit": "^1.8.3",
     "@testing-library/jest-dom": "^5.16.4",
     "@testing-library/react": "^13.3.0",
     "@testing-library/user-event": "^13.5.0",
@@ -10,6 +11,7 @@
     "react": "^18.2.0",
     "react-dom": "^18.2.0",
     "react-icons": "^4.4.0",
+    "react-redux": "^8.0.2",
     "react-scripts": "5.0.1",
     "react-vertical-timeline-component": "^3.5.2",
     "web-vitals": "^2.1.4"
@@ -37,5 +39,8 @@
       "last 1 firefox version",
       "last 1 safari version"
     ]
+  },
+  "devDependencies": {
+    "@redux-devtools/core": "^3.13.1"
   }
 }
diff --git a/dashboard/src/App.css b/dashboard/src/App.css
index 8e651bb..67272f0 100644
--- a/dashboard/src/App.css
+++ b/dashboard/src/App.css
@@ -34,10 +34,6 @@
   
 }
 
-.ASTIcon {
-  transform: rotate(180deg);
-}
-
 .App-link {
   color: #61dafb;
 }
diff --git a/dashboard/src/App.js b/dashboard/src/App.js
index f8e80dc..3c4f84b 100644
--- a/dashboard/src/App.js
+++ b/dashboard/src/App.js
@@ -1,96 +1,89 @@
 import './App.css';
-import React from 'react';
+import React, { useEffect, useRef } from 'react';
 import './index.css';
 import { VerticalTimeline, VerticalTimelineElement } from 'react-vertical-timeline-component';
 import 'react-vertical-timeline-component/style.min.css';
 import { GiFamilyTree } from "react-icons/gi";
+import { useSelector, useDispatch } from 'react-redux'
+import { addAst, setAstList } from './mgSlice'
 
-class App extends React.Component {
-  constructor(props) {
-    super(props);
-    this.state = {
-      asts: [],
-      contexts: {}
-    };
-  }
+function App() {
 
-  componentDidMount() {
-    fetch('http://localhost:7070/asts/')
-      .then(res => res.json())
-      .then((asts) => {
-        this.setState({
-          asts: asts.reverse(),
-          contexts: {}
-        })
-      })
-      .catch(console.log)
+  const ws = useRef(null);
+  const dispatch = useDispatch();
+  const asts = useSelector(state => state.mg.asts);
 
-    const ws = new WebSocket("ws://localhost:7070/ast-events");
-
-    ws.onopen = (event) => {
-      ws.send("Web client connected!");
-    };
-
-    ws.onmessage = (event) => {
+  useEffect(() => {
+    ws.current = new WebSocket("ws://localhost:7070/ast-events");
+    ws.current.onopen = () => console.log("ws opened");
+    ws.current.onclose = () => console.log("ws closed");
+    ws.current.onmessage = (event) => {
       const json = JSON.parse(event.data);
       try {
         if ((json.event = "data")) {
-          var asts = this.state.asts.slice();
-          const contexts = {};
-          this.setState({
-            asts: [event.data, ...asts],
-            contexts: contexts
-          });
+          dispatch(addAst(event.data));
         }
       } catch (err) {
         console.log(err);
       }
     };
-  }
 
-  render() {
+    const wsCurrent = ws.current;
 
-    const astTimeline = this.state.asts.map(v => {
-      return (
-        <VerticalTimelineElement
-          className="ast-timeline-event"
-          contentStyle={{ background: 'rgb(33, 150, 243)', color: '#fff' }}
-          contentArrowStyle={{ borderRight: '7px solid  rgb(33, 150, 243)' }}
-          date={new Date(v.timestamp).toLocaleTimeString('en-us', { hour12: false })}
-          iconStyle={{ background: 'rgb(33, 150, 243)', color: '#fff', rotate: '180deg' }}
-          icon={<GiFamilyTree />}
-          key={v.timestamp}
-          position={'right'}
-        >
-          <h3 className="vertical-timeline-element-title">{v.parseRule}()</h3>
-          <p>
-            <img className="AstInTimeline" src={`data:image/svg+xml;utf8,${encodeURIComponent(v.diagram)}`} alt={v.parseRule} />
-          </p>
-        </VerticalTimelineElement>
+    return () => {
+      wsCurrent.close();
+    };
+  }, [dispatch]);
 
-      )
-    })
+  useEffect(() => {
+    if (asts.length === 0) {
+      fetch('http://localhost:7070/asts/')
+        .then(res => res.json())
+        .then((asts) => {
+          dispatch(setAstList(asts))
+        })
+        .catch(console.log)
+    }
+  }, [dispatch, asts.length])
 
-    const lastImage = (this.state.asts.length > 0) ? (this.state.asts[0]) : '';
 
-    return (
-      <div className="App">
-        <header className="App-header">
-          <ControlPanel />
-        </header>
-        <main>
-          <CurrentState
-            diagram={lastImage.diagram}
-            label={lastImage.parseRule}
-          />
-          <VerticalTimeline
-            className='MG-Timeline'>
-            {astTimeline}
-          </VerticalTimeline>
-        </main>
-      </div>
-    );
-  }
+  return (
+    <div className="App">
+      <header className="App-header">
+        <ControlPanel />
+      </header>
+      <main>
+        <CurrentState
+          diagram={asts[0] ? asts[0].diagram : ''}
+          label={asts[0] ? asts[0].parseRule : ''}
+        />
+        <VerticalTimeline
+          className='MG-Timeline'>
+          {asts.map(v => {
+            console.log(v.parseRule)
+            return (
+              <VerticalTimelineElement
+                className="ast-timeline-event"
+                contentStyle={{ background: 'rgb(33, 150, 243)', color: '#fff' }}
+                contentArrowStyle={{ borderRight: '7px solid  rgb(33, 150, 243)' }}
+                date={new Date(v.timestamp).toLocaleTimeString('en-us', { hour12: false })}
+                iconStyle={{ background: 'rgb(33, 150, 243)', color: '#fff', rotate: '180deg' }}
+                icon={<GiFamilyTree />}
+                key={v.timestamp}
+                position={'right'}
+              >
+                <h3 className="vertical-timeline-element-title">{v.parseRule}()</h3>
+                <p>
+                  <img className="AstInTimeline" src={`data:image/svg+xml;utf8,${encodeURIComponent(v.diagram)}`} alt={v.parseRule} />
+                </p>
+              </VerticalTimelineElement>
+
+            )
+          })}
+        </VerticalTimeline>
+      </main>
+    </div>
+  );
 }
 
 function CurrentState(props) {
diff --git a/dashboard/src/index.js b/dashboard/src/index.js
index d563c0f..634cced 100644
--- a/dashboard/src/index.js
+++ b/dashboard/src/index.js
@@ -3,11 +3,15 @@ import ReactDOM from 'react-dom/client';
 import './index.css';
 import App from './App';
 import reportWebVitals from './reportWebVitals';
+import store from './store'
+import { Provider } from 'react-redux'
 
 const root = ReactDOM.createRoot(document.getElementById('root'));
 root.render(
   <React.StrictMode>
+  <Provider store={store}>
     <App />
+  </Provider>
   </React.StrictMode>
 );
 
diff --git a/dashboard/src/mgSlice.js b/dashboard/src/mgSlice.js
new file mode 100644
index 0000000..fbbef70
--- /dev/null
+++ b/dashboard/src/mgSlice.js
@@ -0,0 +1,23 @@
+import { createSlice } from '@reduxjs/toolkit'
+
+export const mgSlice = createSlice({
+  name: 'mg',
+  initialState: {
+    asts: [],
+    contexts: {},
+  },
+  reducers: {
+    addAst: (state, action) => {
+        state.asts = [action.payload, ...state.asts];
+        console.log("added ast, size is " + state.asts.length);
+    },
+    setAstList: (state, action) => {
+        state.asts = action.payload.reverse();
+        console.log("set AST list, size is " + state.asts.length);
+    }
+  }
+})
+
+export const { addAst, setAstList } = mgSlice.actions
+
+export default mgSlice.reducer
\ No newline at end of file
diff --git a/dashboard/src/store.js b/dashboard/src/store.js
new file mode 100644
index 0000000..fd5cd81
--- /dev/null
+++ b/dashboard/src/store.js
@@ -0,0 +1,8 @@
+import { configureStore } from '@reduxjs/toolkit'
+import mgReducer from './mgSlice'
+
+export default configureStore({
+  reducer: {
+    mg: mgReducer
+  }
+})
\ No newline at end of file
-- 
GitLab