diff --git a/frontend/package-lock.json b/frontend/package-lock.json index d9a05a2745041e1b9271910f1ed79f8601ffd17c..4447911a1c4bdb9de1d1097922d5d3a0e074b6bb 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,12 +8,16 @@ "name": "frontend", "version": "0.0.0", "dependencies": { + "@tailwindcss/postcss": "^4.0.6", + "@tailwindcss/vite": "^4.0.6", "axios": "^1.7.9", "chart.js": "^4.4.7", "prop-types": "^15.8.1", "react": "^18.3.1", "react-chartjs-2": "^5.3.0", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-router-dom": "^7.1.5", + "tailwindcss": "^4.0.6" }, "devDependencies": { "@eslint/js": "^9.17.0", @@ -26,11 +30,22 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.16", "globals": "^15.14.0", - "postcss": "^8.5.1", - "tailwindcss": "^4.0.0", + "postcss": "^8.5.2", "vite": "^6.0.5" } }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.24.2", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.24.2.tgz", @@ -38,7 +53,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -55,7 +69,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -72,7 +85,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -89,7 +101,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -106,7 +117,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -123,7 +133,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -140,7 +149,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -157,7 +165,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -174,7 +181,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -191,7 +197,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -208,7 +213,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -225,7 +229,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -242,7 +245,6 @@ "cpu": [ "mips64el" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -259,7 +261,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -276,7 +277,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -293,7 +293,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -310,7 +309,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -327,7 +325,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -344,7 +341,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -361,7 +357,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -378,7 +373,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -395,7 +389,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -412,7 +405,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -429,7 +421,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -446,7 +437,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -676,7 +666,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -690,7 +679,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -704,7 +692,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -718,7 +705,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -732,7 +718,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -746,7 +731,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -760,7 +744,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -774,7 +757,6 @@ "cpu": [ "arm" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -788,7 +770,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -802,7 +783,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -816,7 +796,6 @@ "cpu": [ "loong64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -830,7 +809,6 @@ "cpu": [ "ppc64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -844,7 +822,6 @@ "cpu": [ "riscv64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -858,7 +835,6 @@ "cpu": [ "s390x" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -872,7 +848,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -886,7 +861,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -900,7 +874,6 @@ "cpu": [ "arm64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -914,7 +887,6 @@ "cpu": [ "ia32" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -928,7 +900,6 @@ "cpu": [ "x64" ], - "dev": true, "license": "MIT", "optional": true, "os": [ @@ -1161,11 +1132,254 @@ "@swc/counter": "^0.1.3" } }, + "node_modules/@tailwindcss/node": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/node/-/node-4.0.6.tgz", + "integrity": "sha512-jb6E0WeSq7OQbVYcIJ6LxnZTeC4HjMvbzFBMCrQff4R50HBlo/obmYNk6V2GCUXDeqiXtvtrQgcIbT+/boB03Q==", + "license": "MIT", + "dependencies": { + "enhanced-resolve": "^5.18.0", + "jiti": "^2.4.2", + "tailwindcss": "4.0.6" + } + }, + "node_modules/@tailwindcss/oxide": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide/-/oxide-4.0.6.tgz", + "integrity": "sha512-lVyKV2y58UE9CeKVcYykULe9QaE1dtKdxDEdrTPIdbzRgBk6bdxHNAoDqvcqXbIGXubn3VOl1O/CFF77v/EqSA==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@tailwindcss/oxide-android-arm64": "4.0.6", + "@tailwindcss/oxide-darwin-arm64": "4.0.6", + "@tailwindcss/oxide-darwin-x64": "4.0.6", + "@tailwindcss/oxide-freebsd-x64": "4.0.6", + "@tailwindcss/oxide-linux-arm-gnueabihf": "4.0.6", + "@tailwindcss/oxide-linux-arm64-gnu": "4.0.6", + "@tailwindcss/oxide-linux-arm64-musl": "4.0.6", + "@tailwindcss/oxide-linux-x64-gnu": "4.0.6", + "@tailwindcss/oxide-linux-x64-musl": "4.0.6", + "@tailwindcss/oxide-win32-arm64-msvc": "4.0.6", + "@tailwindcss/oxide-win32-x64-msvc": "4.0.6" + } + }, + "node_modules/@tailwindcss/oxide-android-arm64": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-android-arm64/-/oxide-android-arm64-4.0.6.tgz", + "integrity": "sha512-xDbym6bDPW3D2XqQqX3PjqW3CKGe1KXH7Fdkc60sX5ZLVUbzPkFeunQaoP+BuYlLc2cC1FoClrIRYnRzof9Sow==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-arm64": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-arm64/-/oxide-darwin-arm64-4.0.6.tgz", + "integrity": "sha512-1f71/ju/tvyGl5c2bDkchZHy8p8EK/tDHCxlpYJ1hGNvsYihZNurxVpZ0DefpN7cNc9RTT8DjrRoV8xXZKKRjg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-darwin-x64": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-darwin-x64/-/oxide-darwin-x64-4.0.6.tgz", + "integrity": "sha512-s/hg/ZPgxFIrGMb0kqyeaqZt505P891buUkSezmrDY6lxv2ixIELAlOcUVTkVh245SeaeEiUVUPiUN37cwoL2g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-freebsd-x64": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-freebsd-x64/-/oxide-freebsd-x64-4.0.6.tgz", + "integrity": "sha512-Z3Wo8FWZnmio8+xlcbb7JUo/hqRMSmhQw8IGIRoRJ7GmLR0C+25Wq+bEX/135xe/yEle2lFkhu9JBHd4wZYiig==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm-gnueabihf": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm-gnueabihf/-/oxide-linux-arm-gnueabihf-4.0.6.tgz", + "integrity": "sha512-SNSwkkim1myAgmnbHs4EjXsPL7rQbVGtjcok5EaIzkHkCAVK9QBQsWeP2Jm2/JJhq4wdx8tZB9Y7psMzHYWCkA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-gnu": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-gnu/-/oxide-linux-arm64-gnu-4.0.6.tgz", + "integrity": "sha512-tJ+mevtSDMQhKlwCCuhsFEFg058kBiSy4TkoeBG921EfrHKmexOaCyFKYhVXy4JtkaeeOcjJnCLasEeqml4i+Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-arm64-musl": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-arm64-musl/-/oxide-linux-arm64-musl-4.0.6.tgz", + "integrity": "sha512-IoArz1vfuTR4rALXMUXI/GWWfx2EaO4gFNtBNkDNOYhlTD4NVEwE45nbBoojYiTulajI4c2XH8UmVEVJTOJKxA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-gnu": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-gnu/-/oxide-linux-x64-gnu-4.0.6.tgz", + "integrity": "sha512-QtsUfLkEAeWAC3Owx9Kg+7JdzE+k9drPhwTAXbXugYB9RZUnEWWx5x3q/au6TvUYcL+n0RBqDEO2gucZRvRFgQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-linux-x64-musl": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-linux-x64-musl/-/oxide-linux-x64-musl-4.0.6.tgz", + "integrity": "sha512-QthvJqIji2KlGNwLcK/PPYo7w1Wsi/8NK0wAtRGbv4eOPdZHkQ9KUk+oCoP20oPO7i2a6X1aBAFQEL7i08nNMA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-arm64-msvc": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.0.6.tgz", + "integrity": "sha512-+oka+dYX8jy9iP00DJ9Y100XsqvbqR5s0yfMZJuPR1H/lDVtDfsZiSix1UFBQ3X1HWxoEEl6iXNJHWd56TocVw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/oxide-win32-x64-msvc": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-x64-msvc/-/oxide-win32-x64-msvc-4.0.6.tgz", + "integrity": "sha512-+o+juAkik4p8Ue/0LiflQXPmVatl6Av3LEZXpBTfg4qkMIbZdhCGWFzHdt2NjoMiLOJCFDddoV6GYaimvK1Olw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@tailwindcss/postcss": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/postcss/-/postcss-4.0.6.tgz", + "integrity": "sha512-noTaGPHjGCXTCc487TWnfAEN0VMjqDAecssWDOsfxV2hFrcZR0AHthX7IdY/0xHTg/EtpmIPdssddlZ5/B7JnQ==", + "license": "MIT", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "@tailwindcss/node": "^4.0.6", + "@tailwindcss/oxide": "^4.0.6", + "lightningcss": "^1.29.1", + "postcss": "^8.4.41", + "tailwindcss": "4.0.6" + } + }, + "node_modules/@tailwindcss/vite": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/@tailwindcss/vite/-/vite-4.0.6.tgz", + "integrity": "sha512-O25vZ/URWbZ2JHdk2o8wH7jOKqEGCsYmX3GwGmYS5DjE4X3mpf93a72Rn7VRnefldNauBzr5z2hfZptmBNtTUQ==", + "license": "MIT", + "dependencies": { + "@tailwindcss/node": "^4.0.6", + "@tailwindcss/oxide": "^4.0.6", + "lightningcss": "^1.29.1", + "tailwindcss": "4.0.6" + }, + "peerDependencies": { + "vite": "^5.2.0 || ^6" + } + }, + "node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", - "dev": true, "license": "MIT" }, "node_modules/@types/json-schema": { @@ -1696,6 +1910,15 @@ "dev": true, "license": "MIT" }, + "node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -1842,6 +2065,18 @@ "node": ">=0.4.0" } }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", + "license": "Apache-2.0", + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/doctrine": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", @@ -1877,6 +2112,19 @@ "dev": true, "license": "ISC" }, + "node_modules/enhanced-resolve": { + "version": "5.18.1", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.1.tgz", + "integrity": "sha512-ZSW3ma5GkcQBIpwZTSRAI8N71Uuwgs93IezB7mf7R60tC8ZbJideoDNKjHn2O9KIlx6rkGTTEk1xUCK2E1Y2Yg==", + "license": "MIT", + "dependencies": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + }, + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/es-abstract": { "version": "1.23.9", "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", @@ -2052,7 +2300,6 @@ "version": "0.24.2", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.24.2.tgz", "integrity": "sha512-+9egpBW8I3CD5XPe0n6BfT5fxLzxrlDzqydF3aviG+9ni1lDC/OvMHcxqEFV0+LANZG5R1bFMWfUrjVsdwxJvA==", - "dev": true, "hasInstallScript": true, "license": "MIT", "bin": { @@ -2462,7 +2709,6 @@ "version": "2.3.3", "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, "hasInstallScript": true, "license": "MIT", "optional": true, @@ -2627,6 +2873,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC" + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -3173,6 +3425,15 @@ "node": ">= 0.4" } }, + "node_modules/jiti": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", + "license": "MIT", + "bin": { + "jiti": "lib/jiti-cli.mjs" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -3253,6 +3514,234 @@ "node": ">= 0.8.0" } }, + "node_modules/lightningcss": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.29.1.tgz", + "integrity": "sha512-FmGoeD4S05ewj+AkhTY+D+myDvXI6eL27FjHIjoyUkO/uw7WZD1fBVs0QxeYWa7E17CUHJaYX/RUGISCtcrG4Q==", + "license": "MPL-2.0", + "dependencies": { + "detect-libc": "^1.0.3" + }, + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "lightningcss-darwin-arm64": "1.29.1", + "lightningcss-darwin-x64": "1.29.1", + "lightningcss-freebsd-x64": "1.29.1", + "lightningcss-linux-arm-gnueabihf": "1.29.1", + "lightningcss-linux-arm64-gnu": "1.29.1", + "lightningcss-linux-arm64-musl": "1.29.1", + "lightningcss-linux-x64-gnu": "1.29.1", + "lightningcss-linux-x64-musl": "1.29.1", + "lightningcss-win32-arm64-msvc": "1.29.1", + "lightningcss-win32-x64-msvc": "1.29.1" + } + }, + "node_modules/lightningcss-darwin-arm64": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.29.1.tgz", + "integrity": "sha512-HtR5XJ5A0lvCqYAoSv2QdZZyoHNttBpa5EP9aNuzBQeKGfbyH5+UipLWvVzpP4Uml5ej4BYs5I9Lco9u1fECqw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-darwin-x64": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-darwin-x64/-/lightningcss-darwin-x64-1.29.1.tgz", + "integrity": "sha512-k33G9IzKUpHy/J/3+9MCO4e+PzaFblsgBjSGlpAaFikeBFm8B/CkO3cKU9oI4g+fjS2KlkLM/Bza9K/aw8wsNA==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-freebsd-x64": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-freebsd-x64/-/lightningcss-freebsd-x64-1.29.1.tgz", + "integrity": "sha512-0SUW22fv/8kln2LnIdOCmSuXnxgxVC276W5KLTwoehiO0hxkacBxjHOL5EtHD8BAXg2BvuhsJPmVMasvby3LiQ==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm-gnueabihf": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm-gnueabihf/-/lightningcss-linux-arm-gnueabihf-1.29.1.tgz", + "integrity": "sha512-sD32pFvlR0kDlqsOZmYqH/68SqUMPNj+0pucGxToXZi4XZgZmqeX/NkxNKCPsswAXU3UeYgDSpGhu05eAufjDg==", + "cpu": [ + "arm" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-gnu": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-gnu/-/lightningcss-linux-arm64-gnu-1.29.1.tgz", + "integrity": "sha512-0+vClRIZ6mmJl/dxGuRsE197o1HDEeeRk6nzycSy2GofC2JsY4ifCRnvUWf/CUBQmlrvMzt6SMQNMSEu22csWQ==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-arm64-musl": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-arm64-musl/-/lightningcss-linux-arm64-musl-1.29.1.tgz", + "integrity": "sha512-UKMFrG4rL/uHNgelBsDwJcBqVpzNJbzsKkbI3Ja5fg00sgQnHw/VrzUTEc4jhZ+AN2BvQYz/tkHu4vt1kLuJyw==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-gnu": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-gnu/-/lightningcss-linux-x64-gnu-1.29.1.tgz", + "integrity": "sha512-u1S+xdODy/eEtjADqirA774y3jLcm8RPtYztwReEXoZKdzgsHYPl0s5V52Tst+GKzqjebkULT86XMSxejzfISw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-linux-x64-musl": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-linux-x64-musl/-/lightningcss-linux-x64-musl-1.29.1.tgz", + "integrity": "sha512-L0Tx0DtaNUTzXv0lbGCLB/c/qEADanHbu4QdcNOXLIe1i8i22rZRpbT3gpWYsCh9aSL9zFujY/WmEXIatWvXbw==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-arm64-msvc": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-arm64-msvc/-/lightningcss-win32-arm64-msvc-1.29.1.tgz", + "integrity": "sha512-QoOVnkIEFfbW4xPi+dpdft/zAKmgLgsRHfJalEPYuJDOWf7cLQzYg0DEh8/sn737FaeMJxHZRc1oBreiwZCjog==", + "cpu": [ + "arm64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/lightningcss-win32-x64-msvc": { + "version": "1.29.1", + "resolved": "https://registry.npmjs.org/lightningcss-win32-x64-msvc/-/lightningcss-win32-x64-msvc-1.29.1.tgz", + "integrity": "sha512-NygcbThNBe4JElP+olyTI/doBNGJvLs3bFCRPdvuCcxZCcCZ71B858IHpdm7L1btZex0FvCmM17FK98Y9MRy1Q==", + "cpu": [ + "x64" + ], + "license": "MPL-2.0", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 12.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3343,7 +3832,6 @@ "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", "integrity": "sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w==", - "dev": true, "funding": [ { "type": "github", @@ -3600,7 +4088,6 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", - "dev": true, "license": "ISC" }, "node_modules/possible-typed-array-names": { @@ -3614,10 +4101,9 @@ } }, "node_modules/postcss": { - "version": "8.5.1", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.1.tgz", - "integrity": "sha512-6oz2beyjc5VMn/KV1pPw8fliQkhBXrVn1Z3TVyqZxU8kZpzEKhBdmCFqI6ZbmGtamQvQGuU1sgPTk8ZrXDD7jQ==", - "dev": true, + "version": "8.5.2", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.2.tgz", + "integrity": "sha512-MjOadfU3Ys9KYoX0AdkBlFEF1Vx37uCCeN4ZHnmwm9FfpbsGWMZeBLMmmpY+6Ocqod7mkdZ0DT31OlbsFrLlkA==", "funding": [ { "type": "opencollective", @@ -3727,6 +4213,46 @@ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==", "license": "MIT" }, + "node_modules/react-router": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/react-router/-/react-router-7.1.5.tgz", + "integrity": "sha512-8BUF+hZEU4/z/JD201yK6S+UYhsf58bzYIDq2NS1iGpwxSXDu7F+DeGSkIXMFBuHZB21FSiCzEcUb18cQNdRkA==", + "license": "MIT", + "dependencies": { + "@types/cookie": "^0.6.0", + "cookie": "^1.0.1", + "set-cookie-parser": "^2.6.0", + "turbo-stream": "2.4.0" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + }, + "peerDependenciesMeta": { + "react-dom": { + "optional": true + } + } + }, + "node_modules/react-router-dom": { + "version": "7.1.5", + "resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-7.1.5.tgz", + "integrity": "sha512-/4f9+up0Qv92D3bB8iN5P1s3oHAepSGa9h5k6tpTFlixTTskJZwKGhJ6vRJ277tLD1zuaZTt95hyGWV1Z37csQ==", + "license": "MIT", + "dependencies": { + "react-router": "7.1.5" + }, + "engines": { + "node": ">=20.0.0" + }, + "peerDependencies": { + "react": ">=18", + "react-dom": ">=18" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -3803,7 +4329,6 @@ "version": "4.32.0", "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.32.0.tgz", "integrity": "sha512-JmrhfQR31Q4AuNBjjAX4s+a/Pu/Q8Q9iwjWBsjRH1q52SPFE2NqRMK6fUZKKnvKO6id+h7JIRf0oYsph53eATg==", - "dev": true, "license": "MIT", "dependencies": { "@types/estree": "1.0.6" @@ -3912,6 +4437,12 @@ "semver": "bin/semver.js" } }, + "node_modules/set-cookie-parser": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/set-cookie-parser/-/set-cookie-parser-2.7.1.tgz", + "integrity": "sha512-IOc8uWeOZgnb3ptbCURJWNjWUPcO3ZnTTdzsurqERrP6nPyv+paC55vJM0LpOlT2ne+Ix+9+CRG1MNLlyZ4GjQ==", + "license": "MIT" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", @@ -4064,7 +4595,6 @@ "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", - "dev": true, "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -4208,12 +4738,26 @@ } }, "node_modules/tailwindcss": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.0.tgz", - "integrity": "sha512-ULRPI3A+e39T7pSaf1xoi58AqqJxVCLg8F/uM5A3FadUbnyDTgltVnXJvdkTjwCOGA6NazqHVcwPJC5h2vRYVQ==", - "dev": true, + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-4.0.6.tgz", + "integrity": "sha512-mysewHYJKaXgNOW6pp5xon/emCsfAMnO8WMaGKZZ35fomnR/T5gYnRg2/yRTTrtXiEl1tiVkeRt0eMO6HxEZqw==", "license": "MIT" }, + "node_modules/tapable": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.1.tgz", + "integrity": "sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/turbo-stream": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/turbo-stream/-/turbo-stream-2.4.0.tgz", + "integrity": "sha512-FHncC10WpBd2eOmGwpmQsWLDoK4cqsA/UT/GqNoaKOQnT8uzhtCbg3EoUDMvqpOSAI0S26mr0rkjzbOO6S3v1g==", + "license": "ISC" + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -4369,7 +4913,6 @@ "version": "6.0.11", "resolved": "https://registry.npmjs.org/vite/-/vite-6.0.11.tgz", "integrity": "sha512-4VL9mQPKoHy4+FE0NnRE/kbY51TOfaknxAjt3fJbGJxhIpBZiqVzlZDEesWWsuREXHwNdAoOFZ9MkPEVXczHwg==", - "dev": true, "license": "MIT", "dependencies": { "esbuild": "^0.24.2", diff --git a/frontend/package.json b/frontend/package.json index c5252239296bbfd1076decc7c1660e8677673c78..a2ba73a02aad9214ec0fc8d84738e97b4ff1a69e 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -10,12 +10,16 @@ "preview": "vite preview" }, "dependencies": { + "@tailwindcss/postcss": "^4.0.6", + "@tailwindcss/vite": "^4.0.6", "axios": "^1.7.9", "chart.js": "^4.4.7", "prop-types": "^15.8.1", "react": "^18.3.1", "react-chartjs-2": "^5.3.0", - "react-dom": "^18.3.1" + "react-dom": "^18.3.1", + "react-router-dom": "^7.1.5", + "tailwindcss": "^4.0.6" }, "devDependencies": { "@eslint/js": "^9.17.0", @@ -28,8 +32,7 @@ "eslint-plugin-react-hooks": "^5.0.0", "eslint-plugin-react-refresh": "^0.4.16", "globals": "^15.14.0", - "postcss": "^8.5.1", - "tailwindcss": "^4.0.0", + "postcss": "^8.5.2", "vite": "^6.0.5" } } diff --git a/frontend/src/App.css b/frontend/src/App.css deleted file mode 100644 index ab58e686ecae8171854a97bdff8f0acba49e3e10..0000000000000000000000000000000000000000 --- a/frontend/src/App.css +++ /dev/null @@ -1,70 +0,0 @@ - -.container { - max-width: 600px; - margin: 20px auto; - padding: 20px; - border: 1px solid #ddd; - border-radius: 10px; - background-color: #f9f9f9; - font-family: Arial, sans-serif; -} - -h2 { - text-align: center; - margin-bottom: 15px; -} - -.section { - margin-bottom: 15px; -} - -h3 { - margin-bottom: 8px; -} - -.input, .select { - width: 100%; - padding: 8px; - margin-bottom: 8px; - border: 1px solid #ccc; - border-radius: 5px; -} - -.input-group { - margin-bottom: 10px; -} - -.button { - padding: 10px 15px; - border: none; - border-radius: 5px; - cursor: pointer; - margin-right: 10px; - font-size: 14px; - color: white; -} - -.upload { - background-color: #007bff; -} - -.load { - background-color: #28a745; -} - -.predict { - background-color: #6f42c1; - width: 100%; -} - -.button:hover { - opacity: 0.8; -} - -.prediction-box { - margin-top: 20px; - padding: 10px; - border: 1px solid #ccc; - border-radius: 5px; - background-color: #fff; -} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 9da29ca216ada732743998d8c33b0fd5f8b7f4ef..f8c391ad4eb1f15a2b673e33702199e5e12130ba 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,429 +1,36 @@ -import { useState, useEffect } from "react"; -import axios from "axios"; -import "./App.css"; // Import the CSS file - -const API_BASE_URL = "http://localhost:8000"; // Update if your backend is running elsewhere - -const ModelManager = () => { - const [models, setModels] = useState([]); - const [selectedModel, setSelectedModel] = useState(""); - const [inputParams, setInputParams] = useState([]); - const [outputParams, setOutputParams] = useState([]); - const [inputValues, setInputValues] = useState({}); - const [predictions, setPredictions] = useState(null); - const [file, setFile] = useState(null); - - // Fetch available models - const fetchModels = async () => { - try { - const res = await axios.get(`${API_BASE_URL}/models`); - setModels(res.data.models); - } catch (err) { - console.error("Error fetching models:", err); - } - }; - - useEffect(() => { - fetchModels(); - }, []); - - // Upload a new model - const handleUpload = async () => { - if (!file) { - alert("Please select a model file to upload."); - return; - } - const formData = new FormData(); - formData.append("file", file); - - try { - const res = await axios.post(`${API_BASE_URL}/upload`, formData, { - headers: { "Content-Type": "multipart/form-data" }, - }); - alert(res.data.message); - fetchModels(); // Refresh models list after upload - } catch (error) { - console.error("Error uploading model:", error); - } - }; - - // Load a selected model - const handleLoadModel = async () => { - if (!selectedModel) { - alert("Please select a model."); - return; - } - - console.log("Sending model_name:", selectedModel); // Debugging - - try { - const res = await axios.post(`${API_BASE_URL}/load_model`, { - model_name: selectedModel, - }, { - headers: { "Content-Type": "application/json" }, - }); - alert(res.data.message); - } catch (error) { - console.error("Error loading model:", error.response?.data || error.message); - } - }; - - // Handle input parameter changes - const handleInputChange = (param, value) => { - setInputValues({ ...inputValues, [param]: parseFloat(value) || 0 }); - }; - - // Make a prediction - const handlePredict = async () => { - if (!selectedModel) { - alert("Please select a model first."); - return; - } - if (inputParams.length === 0 || outputParams.length === 0) { - alert("Define both input and output parameters."); - return; - } - - try { - const res = await axios.post(`${API_BASE_URL}/predict`, { - model_name: selectedModel, - input_data: inputValues, - input_params: inputParams, - output_params: outputParams, - }); - setPredictions(res.data.prediction); - } catch (error) { - console.error("Error making prediction:", error); - } - }; - - return ( - <div className="container"> - <h2>Model Manager</h2> - - {/* Upload Section */} - <div className="section"> - <input type="file" onChange={(e) => setFile(e.target.files[0])} /> - <button onClick={handleUpload} className="button upload"> - Upload Model - </button> - </div> - - {/* Model Selection */} - <div className="section"> - <h3>Select Model</h3> - <select - value={selectedModel} - onChange={(e) => setSelectedModel(e.target.value)} - className="select" - > - <option value="">-- Select a model --</option> - {models.map((model, idx) => ( - <option key={idx} value={model}>{model}</option> - ))} - </select> - <button onClick={handleLoadModel} className="button load"> - Load Model - </button> - </div> - - {/* Input Parameter Definition */} - <div className="section"> - <h3>Define Input Parameters</h3> - <input - type="text" - placeholder="Comma-separated inputs (e.g., AmountServer,Coolingdefect)" - className="input" - value={inputParams.join(", ")} - onChange={(e) => setInputParams(e.target.value.split(",").map(p => p.trim()))} - /> - </div> - - {/* Output Parameter Definition */} - <div className="section"> - <h3>Define Output Parameters</h3> - <input - type="text" - placeholder="Comma-separated outputs (e.g., Throughput,OEE)" - className="input" - value={outputParams.join(", ")} - onChange={(e) => setOutputParams(e.target.value.split(",").map(p => p.trim()))} - /> - </div> - - {/* Input Values */} - <div className="section"> - <h3>Enter Input Values</h3> - {inputParams.map((param, idx) => ( - <div key={idx} className="input-group"> - <label>{param}</label> - <input - type="number" - placeholder={`Enter ${param}`} - className="input" - onChange={(e) => handleInputChange(param, e.target.value)} - /> - </div> - ))} - </div> - - {/* Prediction Button */} - <button onClick={handlePredict} className="button predict"> - Predict - </button> - - {/* Prediction Results */} - {predictions && ( - <div className="prediction-box"> - <h3>Prediction Results:</h3> - <pre>{JSON.stringify(predictions, null, 2)}</pre> - </div> - )} +import { BrowserRouter as Router, Routes, Route, Link } from "react-router-dom"; +import ModelManager from "./components/home.jsx"; +import ModelTraining from "./components/trainmodel.jsx"; + +const AnotherPage = () => <div className="p-4">This is Another Page</div>; + +function App() { + return ( + <Router> + <div className="flex h-screen"> + {/* Sidebar Navigation */} + <nav className="w-64 bg-gray-800 text-white p-4 h-full flex flex-col"> + <h1 className="text-xl font-bold mb-4">Dashboard</h1> + <ul className="space-y-2"> + <li> + <Link to="/" className="block p-2 hover:bg-gray-700 rounded">Home</Link> + </li> + <li> + <Link to="/trainmodel" className="block p-2 hover:bg-gray-700 rounded">Training</Link> + </li> + </ul> + </nav> + + {/* Main Content */} + <div className="flex-1 bg-gray-100 p-4 overflow-auto"> + <Routes> + <Route path="/" element={<ModelManager />} /> + <Route path="/trainmodel" element={<ModelTraining />} /> + </Routes> </div> - ); -}; - -export default ModelManager; - - - -// second iteration -// import "./App.css"; -// import { useState } from "react"; -// import axios from "axios"; - -// export default function App() { -// const [page, setPage] = useState("predict"); - -// return ( -// <div className="dashboard"> -// {/* Sidebar Navigation */} -// <nav className="sidebar"> -// <h2>ML Dashboard</h2> -// <ul> -// <li><button onClick={() => setPage("predict")}>Prediction</button></li> -// <li><button onClick={() => setPage("model")}>Model Management</button></li> -// </ul> -// </nav> - -// {/* Dynamic Content */} -// <div className="main-content"> -// {page === "predict" ? <PredictionDashboard /> : <ModelManagement />} -// </div> -// </div> -// ); -// } - -// function PredictionDashboard() { -// const [inputs, setInputs] = useState({ -// AmountServer: "", -// Coolingdefect: "", -// InterarrivalTime: "", -// defekteModulanzahl: "" -// }); -// const [prediction, setPrediction] = useState(null); -// const [loading, setLoading] = useState(false); -// const [error, setError] = useState(null); - -// const handleChange = (e) => { -// setInputs({ ...inputs, [e.target.name]: e.target.value }); -// }; - -// const handleSubmit = async (e) => { -// e.preventDefault(); -// setError(null); -// setLoading(true); - -// try { -// const response = await axios.post("http://localhost:8000/predict", { -// AmountServer: Number(inputs.AmountServer), -// Coolingdefect: Number(inputs.Coolingdefect), -// InterarrivalTime: Number(inputs.InterarrivalTime), -// defekteModulanzahl: Number(inputs.defekteModulanzahl) -// }); -// setPrediction(response.data); -// } catch (error) { -// setError("Failed to get prediction. Please try again later."); -// } finally { -// setLoading(false); -// } -// }; - -// return ( -// <div> -// <h2>Machine Learning Prediction</h2> -// <form onSubmit={handleSubmit}> -// <input type="number" name="AmountServer" placeholder="Amount of Servers" onChange={handleChange} required /> -// <input type="number" name="Coolingdefect" placeholder="Cooling Defect" onChange={handleChange} required /> -// <input type="number" name="InterarrivalTime" placeholder="Interarrival Time" onChange={handleChange} required /> -// <input type="number" name="defekteModulanzahl" placeholder="Defekte Modulanzahl" onChange={handleChange} required /> -// <button type="submit" disabled={loading}>{loading ? "Loading..." : "Predict"}</button> -// </form> -// {error && <p className="error">{error}</p>} -// {prediction && <pre>{JSON.stringify(prediction, null, 2)}</pre>} -// </div> -// ); -// } - -// function ModelManagement() { -// const [modelFile, setModelFile] = useState(null); -// const [scalerFile, setScalerFile] = useState(null); -// const [uploading, setUploading] = useState(false); -// const [message, setMessage] = useState(null); - -// const handleFileChange = (e, setFile) => { -// setFile(e.target.files[0]); -// }; - -// const handleUpload = async () => { -// if (!modelFile || !scalerFile) { -// setMessage("Please select both model and scaler files."); -// return; -// } -// setUploading(true); -// setMessage(null); - -// const formData = new FormData(); -// formData.append("model", modelFile); -// formData.append("scaler", scalerFile); - -// try { -// await axios.post("http://localhost:8000/upload", formData, { -// headers: { "Content-Type": "multipart/form-data" } -// }); -// setMessage("Model and scaler uploaded successfully!"); -// } catch (error) { -// setMessage("Upload failed. Please try again."); -// } finally { -// setUploading(false); -// } -// }; - -// return ( -// <div> -// <h2>Model Management</h2> -// <input type="file" onChange={(e) => handleFileChange(e, setModelFile)} /> -// <input type="file" onChange={(e) => handleFileChange(e, setScalerFile)} /> -// <button onClick={handleUpload} disabled={uploading}>{uploading ? "Uploading..." : "Upload Model & Scaler"}</button> -// {message && <p>{message}</p>} -// </div> -// ); -// } - - - -// first iteration -// export default function PredictionDashboard() { -// const [inputs, setInputs] = useState({ -// AmountServer: "", -// Coolingdefect: "", -// InterarrivalTime: "", -// defekteModulanzahl: "" -// }); - -// const [prediction, setPrediction] = useState(null); -// const [loading, setLoading] = useState(false); -// const [error, setError] = useState(null); - -// const handleChange = (e) => { -// setInputs({ ...inputs, [e.target.name]: e.target.value }); -// }; - -// const handleSubmit = async (e) => { -// e.preventDefault(); -// setError(null); // Reset previous error -// setLoading(true); // Start loading - -// // Input validation -// if (inputs.AmountServer <= 0 || inputs.CoolingDefect < 0 || inputs.InterarrivalTime <= 0 || inputs.DefekteModulanzahl < 0) { -// setError("Please enter valid positive values."); -// setLoading(false); // Stop loading -// return; -// } - -// try { -// // Add a timestamp to avoid request caching -// const response = await axios.post("http://localhost:8000/predict", { -// AmountServer: Number(inputs.AmountServer), -// Coolingdefect: Number(inputs.Coolingdefect), -// InterarrivalTime: Number(inputs.InterarrivalTime), -// defekteModulanzahl: Number(inputs.defekteModulanzahl), -// timestamp: new Date().getTime() // Add timestamp to prevent caching -// }); - -// setPrediction(response.data); -// } catch (error) { -// console.error("Error making prediction:", error); -// setError("Failed to get prediction. Please try again later."); -// } finally { -// setLoading(false); // Stop loading -// } -// }; - -// return ( -// <div className="dashboard"> -// {/* Sidebar */} -// <nav className="sidebar"> -// <h2>ML Dashboard</h2> -// <ul> -// <li><a href="#">Home</a></li> -// <li><a href="#">Predictions</a></li> -// <li><a href="#">Settings</a></li> -// </ul> -// </nav> - -// {/* Main Content */} -// <div className="main-content"> -// <h2>Machine Learning Prediction</h2> -// <form className="prediction-form" onSubmit={handleSubmit}> -// <input -// type="number" -// name="AmountServer" -// placeholder="Amount of Servers [6-10]" -// onChange={handleChange} -// required -// /> -// <input -// type="number" -// step="0.01" -// name="Coolingdefect" -// placeholder="Cooling Defect [0-18]" -// onChange={handleChange} -// required -// /> -// <input -// type="number" -// step="0.01" -// name="InterarrivalTime" -// placeholder="Interarrival Time [4300-5500]" -// onChange={handleChange} -// required -// /> -// <input -// type="number" -// name="defekteModulanzahl" -// placeholder="Defekte Modulanzahl [0-11]" -// onChange={handleChange} -// required -// /> -// <button type="submit" disabled={loading}> -// {loading ? "Loading..." : "Predict"} -// </button> -// </form> - -// {error && ( -// <div className="error"> -// <p>{error}</p> -// </div> -// )} + </div> + </Router> + ); +} -// {prediction && !loading && ( -// <div className="prediction-results"> -// <h3>Prediction Results:</h3> -// <pre>{JSON.stringify(prediction, null, 2)}</pre> -// </div> -// )} -// </div> -// </div> -// ); -// } +export default App; \ No newline at end of file diff --git a/frontend/src/components/home.jsx b/frontend/src/components/home.jsx new file mode 100644 index 0000000000000000000000000000000000000000..0bc44876fb90e9f327321eac15bb16bf0c4c2237 --- /dev/null +++ b/frontend/src/components/home.jsx @@ -0,0 +1,428 @@ +import { useState, useEffect } from "react"; +import axios from "axios"; + +const API_BASE_URL = "http://localhost:8000"; // Update if your backend is running elsewhere + +const ModelManager = () => { + const [models, setModels] = useState([]); + const [selectedModel, setSelectedModel] = useState(""); + const [inputParams, setInputParams] = useState([]); + const [outputParams, setOutputParams] = useState([]); + const [inputValues, setInputValues] = useState({}); + const [predictions, setPredictions] = useState(null); + const [file, setFile] = useState(null); + + // Fetch available models + const fetchModels = async () => { + try { + const res = await axios.get(`${API_BASE_URL}/models`); + setModels(res.data.models); + } catch (err) { + console.error("Error fetching models:", err); + } + }; + + useEffect(() => { + fetchModels(); + }, []); + + // Upload a new model + const handleUpload = async () => { + if (!file) { + alert("Please select a model file to upload."); + return; + } + const formData = new FormData(); + formData.append("file", file); + + try { + const res = await axios.post(`${API_BASE_URL}/upload`, formData, { + headers: { "Content-Type": "multipart/form-data" }, + }); + alert(res.data.message); + fetchModels(); // Refresh models list after upload + } catch (error) { + console.error("Error uploading model:", error); + } + }; + + // Load a selected model + const handleLoadModel = async () => { + if (!selectedModel) { + alert("Please select a model."); + return; + } + try { + const res = await axios.post(`${API_BASE_URL}/load_model`, { + model_name: selectedModel, + }, { + headers: { "Content-Type": "application/json" }, + }); + alert(res.data.message); + } catch (error) { + console.error("Error loading model:", error.response?.data || error.message); + } + }; + + // Handle input parameter changes + const handleInputChange = (param, value) => { + setInputValues({ ...inputValues, [param]: parseFloat(value) || 0 }); + }; + + // Make a prediction + const handlePredict = async () => { + if (!selectedModel) { + alert("Please select a model first."); + return; + } + if (inputParams.length === 0 || outputParams.length === 0) { + alert("Define both input and output parameters."); + return; + } + + try { + const res = await axios.post(`${API_BASE_URL}/predict`, { + model_name: selectedModel, + input_data: inputValues, + input_params: inputParams, + output_params: outputParams, + }); + setPredictions(res.data.prediction); + } catch (error) { + console.error("Error making prediction:", error); + } + }; + + return ( + <div className="container mx-auto p-6"> + <h2 className="text-2xl font-semibold mb-6">Model Manager</h2> + + {/* Upload Section */} + <div className="bg-white p-4 rounded-lg shadow-md mb-6"> + <h3 className="text-lg font-medium mb-4">Upload New Model</h3> + <input type="file" onChange={(e) => setFile(e.target.files[0])} className="block mb-4 p-2 border rounded-md" /> + <button onClick={handleUpload} className="bg-blue-500 text-white px-4 py-2 rounded-lg hover:bg-blue-600 transition-colors"> + Upload Model + </button> + </div> + + {/* Model Selection */} + <div className="bg-white p-4 rounded-lg shadow-md mb-6"> + <h3 className="text-lg font-medium mb-4">Select Model</h3> + <select + value={selectedModel} + onChange={(e) => setSelectedModel(e.target.value)} + className="block mb-4 p-2 border rounded-md w-full" + > + <option value="">-- Select a model --</option> + {models.map((model, idx) => ( + <option key={idx} value={model}>{model}</option> + ))} + </select> + <button onClick={handleLoadModel} className="bg-green-500 text-white px-4 py-2 rounded-lg hover:bg-green-600 transition-colors"> + Load Model + </button> + </div> + + {/* Input Parameter Definition */} + <div className="bg-white p-4 rounded-lg shadow-md mb-6"> + <h3 className="text-lg font-medium mb-4">Define Input Parameters</h3> + <input + type="text" + placeholder="Comma-separated inputs (e.g., AmountServer,Coolingdefect)" + className="block mb-4 p-2 border rounded-md w-full" + value={inputParams.join(", ")} + onChange={(e) => setInputParams(e.target.value.split(",").map(p => p.trim()))} + /> + </div> + + {/* Output Parameter Definition */} + <div className="bg-white p-4 rounded-lg shadow-md mb-6"> + <h3 className="text-lg font-medium mb-4">Define Output Parameters</h3> + <input + type="text" + placeholder="Comma-separated outputs (e.g., Throughput,OEE)" + className="block mb-4 p-2 border rounded-md w-full" + value={outputParams.join(", ")} + onChange={(e) => setOutputParams(e.target.value.split(",").map(p => p.trim()))} + /> + </div> + + {/* Input Values */} + <div className="bg-white p-4 rounded-lg shadow-md mb-6"> + <h3 className="text-lg font-medium mb-4">Enter Input Values</h3> + {inputParams.map((param, idx) => ( + <div key={idx} className="mb-4"> + <label className="block text-sm font-medium mb-2">{param}</label> + <input + type="number" + placeholder={`Enter ${param}`} + className="block p-2 border rounded-md w-full" + onChange={(e) => handleInputChange(param, e.target.value)} + /> + </div> + ))} + </div> + + {/* Prediction Button */} + <div className="text-center mb-6"> + <button onClick={handlePredict} className="bg-purple-500 text-white px-6 py-2 rounded-lg hover:bg-purple-600 transition-colors"> + Make Prediction + </button> + </div> + + {/* Prediction Results */} + {predictions && ( + <div className="bg-white p-4 rounded-lg shadow-md"> + <h3 className="text-lg font-medium mb-4">Prediction Results:</h3> + <pre className="whitespace-pre-wrap break-words">{JSON.stringify(predictions, null, 2)}</pre> + </div> + )} + </div> + ); +}; + +export default ModelManager; + + + +// second iteration +// import "./App.css"; +// import { useState } from "react"; +// import axios from "axios"; + +// export default function App() { +// const [page, setPage] = useState("predict"); + +// return ( +// <div className="dashboard"> +// {/* Sidebar Navigation */} +// <nav className="sidebar"> +// <h2>ML Dashboard</h2> +// <ul> +// <li><button onClick={() => setPage("predict")}>Prediction</button></li> +// <li><button onClick={() => setPage("model")}>Model Management</button></li> +// </ul> +// </nav> + +// {/* Dynamic Content */} +// <div className="main-content"> +// {page === "predict" ? <PredictionDashboard /> : <ModelManagement />} +// </div> +// </div> +// ); +// } + +// function PredictionDashboard() { +// const [inputs, setInputs] = useState({ +// AmountServer: "", +// Coolingdefect: "", +// InterarrivalTime: "", +// defekteModulanzahl: "" +// }); +// const [prediction, setPrediction] = useState(null); +// const [loading, setLoading] = useState(false); +// const [error, setError] = useState(null); + +// const handleChange = (e) => { +// setInputs({ ...inputs, [e.target.name]: e.target.value }); +// }; + +// const handleSubmit = async (e) => { +// e.preventDefault(); +// setError(null); +// setLoading(true); + +// try { +// const response = await axios.post("http://localhost:8000/predict", { +// AmountServer: Number(inputs.AmountServer), +// Coolingdefect: Number(inputs.Coolingdefect), +// InterarrivalTime: Number(inputs.InterarrivalTime), +// defekteModulanzahl: Number(inputs.defekteModulanzahl) +// }); +// setPrediction(response.data); +// } catch (error) { +// setError("Failed to get prediction. Please try again later."); +// } finally { +// setLoading(false); +// } +// }; + +// return ( +// <div> +// <h2>Machine Learning Prediction</h2> +// <form onSubmit={handleSubmit}> +// <input type="number" name="AmountServer" placeholder="Amount of Servers" onChange={handleChange} required /> +// <input type="number" name="Coolingdefect" placeholder="Cooling Defect" onChange={handleChange} required /> +// <input type="number" name="InterarrivalTime" placeholder="Interarrival Time" onChange={handleChange} required /> +// <input type="number" name="defekteModulanzahl" placeholder="Defekte Modulanzahl" onChange={handleChange} required /> +// <button type="submit" disabled={loading}>{loading ? "Loading..." : "Predict"}</button> +// </form> +// {error && <p className="error">{error}</p>} +// {prediction && <pre>{JSON.stringify(prediction, null, 2)}</pre>} +// </div> +// ); +// } + +// function ModelManagement() { +// const [modelFile, setModelFile] = useState(null); +// const [scalerFile, setScalerFile] = useState(null); +// const [uploading, setUploading] = useState(false); +// const [message, setMessage] = useState(null); + +// const handleFileChange = (e, setFile) => { +// setFile(e.target.files[0]); +// }; + +// const handleUpload = async () => { +// if (!modelFile || !scalerFile) { +// setMessage("Please select both model and scaler files."); +// return; +// } +// setUploading(true); +// setMessage(null); + +// const formData = new FormData(); +// formData.append("model", modelFile); +// formData.append("scaler", scalerFile); + +// try { +// await axios.post("http://localhost:8000/upload", formData, { +// headers: { "Content-Type": "multipart/form-data" } +// }); +// setMessage("Model and scaler uploaded successfully!"); +// } catch (error) { +// setMessage("Upload failed. Please try again."); +// } finally { +// setUploading(false); +// } +// }; + +// return ( +// <div> +// <h2>Model Management</h2> +// <input type="file" onChange={(e) => handleFileChange(e, setModelFile)} /> +// <input type="file" onChange={(e) => handleFileChange(e, setScalerFile)} /> +// <button onClick={handleUpload} disabled={uploading}>{uploading ? "Uploading..." : "Upload Model & Scaler"}</button> +// {message && <p>{message}</p>} +// </div> +// ); +// } + + + +// first iteration +// export default function PredictionDashboard() { +// const [inputs, setInputs] = useState({ +// AmountServer: "", +// Coolingdefect: "", +// InterarrivalTime: "", +// defekteModulanzahl: "" +// }); + +// const [prediction, setPrediction] = useState(null); +// const [loading, setLoading] = useState(false); +// const [error, setError] = useState(null); + +// const handleChange = (e) => { +// setInputs({ ...inputs, [e.target.name]: e.target.value }); +// }; + +// const handleSubmit = async (e) => { +// e.preventDefault(); +// setError(null); // Reset previous error +// setLoading(true); // Start loading + +// // Input validation +// if (inputs.AmountServer <= 0 || inputs.CoolingDefect < 0 || inputs.InterarrivalTime <= 0 || inputs.DefekteModulanzahl < 0) { +// setError("Please enter valid positive values."); +// setLoading(false); // Stop loading +// return; +// } + +// try { +// // Add a timestamp to avoid request caching +// const response = await axios.post("http://localhost:8000/predict", { +// AmountServer: Number(inputs.AmountServer), +// Coolingdefect: Number(inputs.Coolingdefect), +// InterarrivalTime: Number(inputs.InterarrivalTime), +// defekteModulanzahl: Number(inputs.defekteModulanzahl), +// timestamp: new Date().getTime() // Add timestamp to prevent caching +// }); + +// setPrediction(response.data); +// } catch (error) { +// console.error("Error making prediction:", error); +// setError("Failed to get prediction. Please try again later."); +// } finally { +// setLoading(false); // Stop loading +// } +// }; + +// return ( +// <div className="dashboard"> +// {/* Sidebar */} +// <nav className="sidebar"> +// <h2>ML Dashboard</h2> +// <ul> +// <li><a href="#">Home</a></li> +// <li><a href="#">Predictions</a></li> +// <li><a href="#">Settings</a></li> +// </ul> +// </nav> + +// {/* Main Content */} +// <div className="main-content"> +// <h2>Machine Learning Prediction</h2> +// <form className="prediction-form" onSubmit={handleSubmit}> +// <input +// type="number" +// name="AmountServer" +// placeholder="Amount of Servers [6-10]" +// onChange={handleChange} +// required +// /> +// <input +// type="number" +// step="0.01" +// name="Coolingdefect" +// placeholder="Cooling Defect [0-18]" +// onChange={handleChange} +// required +// /> +// <input +// type="number" +// step="0.01" +// name="InterarrivalTime" +// placeholder="Interarrival Time [4300-5500]" +// onChange={handleChange} +// required +// /> +// <input +// type="number" +// name="defekteModulanzahl" +// placeholder="Defekte Modulanzahl [0-11]" +// onChange={handleChange} +// required +// /> +// <button type="submit" disabled={loading}> +// {loading ? "Loading..." : "Predict"} +// </button> +// </form> + +// {error && ( +// <div className="error"> +// <p>{error}</p> +// </div> +// )} + +// {prediction && !loading && ( +// <div className="prediction-results"> +// <h3>Prediction Results:</h3> +// <pre>{JSON.stringify(prediction, null, 2)}</pre> +// </div> +// )} +// </div> +// </div> +// ); +// } diff --git a/frontend/src/components/trainmodel.jsx b/frontend/src/components/trainmodel.jsx new file mode 100644 index 0000000000000000000000000000000000000000..5444aee4f7e89a40ad50af329de2139ae3f69b3c --- /dev/null +++ b/frontend/src/components/trainmodel.jsx @@ -0,0 +1,24 @@ +import React, { useState, useEffect } from "react"; +import axios from "axios"; + + +const ModelTraining = () => { + const [randomNumber, setRandomNumber] = useState(null); + + const generateRandomNumber = (min, max) => { + const random = Math.floor(Math.random() * (max - min + 1)) + min; + setRandomNumber(random); + }; + + return ( + <div> + <h1>Random Number Generator</h1> + <button onClick={() => generateRandomNumber(1, 100)}> + Generate Random Number (1-100) + </button> + {randomNumber !== null && <p>Your random number is: {randomNumber}</p>} + </div> + ); +}; + +export default ModelTraining; \ No newline at end of file diff --git a/frontend/src/index.css b/frontend/src/index.css index 76d4ae469a10fdb4bf73d21c14d478ff54305f54..a461c505f1f0c24ab12240ac3f7fa374dfa237fb 100644 --- a/frontend/src/index.css +++ b/frontend/src/index.css @@ -1,68 +1 @@ -:root { - font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif; - line-height: 1.5; - font-weight: 400; - - color-scheme: light dark; - color: rgba(255, 255, 255, 0.87); - background-color: #242424; - - font-synthesis: none; - text-rendering: optimizeLegibility; - -webkit-font-smoothing: antialiased; - -moz-osx-font-smoothing: grayscale; - } - - a { - font-weight: 500; - color: #646cff; - text-decoration: inherit; - } - a:hover { - color: #535bf2; - } - - body { - margin: 0; - display: flex; - place-items: center; - min-width: 320px; - min-height: 100vh; - } - - h1 { - font-size: 3.2em; - line-height: 1.1; - } - - button { - border-radius: 8px; - border: 1px solid transparent; - padding: 0.6em 1.2em; - font-size: 1em; - font-weight: 500; - font-family: inherit; - background-color: #1a1a1a; - cursor: pointer; - transition: border-color 0.25s; - } - button:hover { - border-color: #646cff; - } - button:focus, - button:focus-visible { - outline: 4px auto -webkit-focus-ring-color; - } - - @media (prefers-color-scheme: light) { - :root { - color: #213547; - background-color: #ffffff; - } - a:hover { - color: #747bff; - } - button { - background-color: #f9f9f9; - } - } \ No newline at end of file +@import "tailwindcss"; \ No newline at end of file diff --git a/frontend/src/main.jsx b/frontend/src/main.jsx index abab2146c5dfa67198f9ef32a9a2df3d6e34977a..9347e151da218fed723f9b839c9b43f8118bd509 100644 --- a/frontend/src/main.jsx +++ b/frontend/src/main.jsx @@ -1,10 +1,10 @@ import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' -import ModelManager from './App.jsx' +import App from './App.jsx' createRoot(document.getElementById('root')).render( <StrictMode> - <ModelManager/> + <App /> </StrictMode>, ) \ No newline at end of file diff --git a/frontend/src/pages/trainmodel.jsx b/frontend/src/pages/trainmodel.jsx deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/frontend/src/pages/home.jsx b/frontend/tailwind.config.js similarity index 100% rename from frontend/src/pages/home.jsx rename to frontend/tailwind.config.js diff --git a/frontend/vite.config.js b/frontend/vite.config.js index 2328e170b0c2fd09a25fe72a20557c661702546e..fc4e9bba70fbf7c282f7da72cf01dcbf61678a60 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.js @@ -1,7 +1,11 @@ import { defineConfig } from 'vite' import react from '@vitejs/plugin-react-swc' +import tailwindcss from '@tailwindcss/vite' // https://vite.dev/config/ export default defineConfig({ - plugins: [react()], + plugins: [ + react(), + tailwindcss(), + ], })