diff --git a/package-lock.json b/package-lock.json index d934401..c8d60c1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -15,6 +15,8 @@ "@emotion/styled": "^11.11.0", "@mui/icons-material": "^6.0.0", "@mui/material": "^6.0.0", + "@mui/x-charts": "^8.28.2", + "@mui/x-data-grid": "^8.28.2", "@types/pidusage": "^2.0.5", "@types/prismjs": "^1.26.6", "axios": "^1.14.0", @@ -26,6 +28,7 @@ "react-dom": "^18.2.0", "react-i18next": "^13.0.0", "react-simple-code-editor": "^0.14.1", + "recharts": "^3.8.1", "sweetalert2": "^11.26.24" }, "devDependencies": { @@ -1838,6 +1841,353 @@ } } }, + "node_modules/@mui/x-charts": { + "version": "8.28.2", + "resolved": "https://registry.npmjs.org/@mui/x-charts/-/x-charts-8.28.2.tgz", + "integrity": "sha512-xvQto+uVcwwWitZjzwmHaw1KZDsIju687kLSyOe3qsg4JYldgT+WWguBFQREF7Tsw7PFGAPahDAzbsTNuIkkTA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "@mui/x-charts-vendor": "8.26.0", + "@mui/x-internal-gestures": "0.4.0", + "@mui/x-internals": "8.26.0", + "bezier-easing": "^2.1.0", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", + "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/x-charts-vendor": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@mui/x-charts-vendor/-/x-charts-vendor-8.26.0.tgz", + "integrity": "sha512-R//+WSWvsLJRTjTRN90EKX9sgRzAb4HQBvtUA3cTQpkGrmEjmatD4BJAm3IdRdkSagf6yKWF+ypESctyRhbwnA==", + "license": "MIT AND ISC", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@types/d3-array": "^3.2.2", + "@types/d3-color": "^3.1.3", + "@types/d3-format": "^3.0.4", + "@types/d3-interpolate": "^3.0.4", + "@types/d3-path": "^3.1.1", + "@types/d3-scale": "^4.0.9", + "@types/d3-shape": "^3.1.7", + "@types/d3-time": "^3.0.4", + "@types/d3-time-format": "^4.0.3", + "@types/d3-timer": "^3.0.2", + "d3-array": "^3.2.4", + "d3-color": "^3.1.0", + "d3-format": "^3.1.0", + "d3-interpolate": "^3.0.1", + "d3-path": "^3.1.0", + "d3-scale": "^4.0.2", + "d3-shape": "^3.2.0", + "d3-time": "^3.1.0", + "d3-time-format": "^4.1.0", + "d3-timer": "^3.0.1", + "flatqueue": "^3.0.0", + "internmap": "^2.0.3" + } + }, + "node_modules/@mui/x-charts/node_modules/@mui/types": { + "version": "7.4.12", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.12.tgz", + "integrity": "sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-charts/node_modules/@mui/utils": { + "version": "7.3.9", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.9.tgz", + "integrity": "sha512-U6SdZaGbfb65fqTsH3V5oJdFj9uYwyLE2WVuNvmbggTSDBb8QHrFsqY8BN3taK9t3yJ8/BPHD/kNvLNyjwM7Yw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@mui/types": "^7.4.12", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-data-grid": { + "version": "8.28.2", + "resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-8.28.2.tgz", + "integrity": "sha512-nqVhOtXOfPcKtoULuZuUp9Hdgzcn4xzqjYwqxZkTPnzKGZqkXPGaupy6n+9ssyYg7jg9ot+zNUJB0sbsvlDY/g==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "@mui/x-internals": "8.26.0", + "@mui/x-virtualizer": "0.3.4", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.9.0", + "@emotion/styled": "^11.8.1", + "@mui/material": "^5.15.14 || ^6.0.0 || ^7.0.0", + "@mui/system": "^5.15.14 || ^6.0.0 || ^7.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/x-data-grid/node_modules/@mui/types": { + "version": "7.4.12", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.12.tgz", + "integrity": "sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-data-grid/node_modules/@mui/utils": { + "version": "7.3.9", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.9.tgz", + "integrity": "sha512-U6SdZaGbfb65fqTsH3V5oJdFj9uYwyLE2WVuNvmbggTSDBb8QHrFsqY8BN3taK9t3yJ8/BPHD/kNvLNyjwM7Yw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@mui/types": "^7.4.12", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-internal-gestures": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/@mui/x-internal-gestures/-/x-internal-gestures-0.4.0.tgz", + "integrity": "sha512-i0W6v9LoiNY8Yf1goOmaygtz/ncPJGBedhpDfvNg/i8BvzPwJcBaeW4rqPucJfVag9KQ8MSssBBrvYeEnrQmhw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4" + } + }, + "node_modules/@mui/x-internals": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/@mui/x-internals/-/x-internals-8.26.0.tgz", + "integrity": "sha512-B9OZau5IQUvIxwpJZhoFJKqRpmWf5r0yMmSXjQuqb5WuqM755EuzWJOenY48denGoENzMLT8hQpA0hRTeU2IPA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "reselect": "^5.1.1", + "use-sync-external-store": "^1.6.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@mui/x-internals/node_modules/@mui/types": { + "version": "7.4.12", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.12.tgz", + "integrity": "sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-internals/node_modules/@mui/utils": { + "version": "7.3.9", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.9.tgz", + "integrity": "sha512-U6SdZaGbfb65fqTsH3V5oJdFj9uYwyLE2WVuNvmbggTSDBb8QHrFsqY8BN3taK9t3yJ8/BPHD/kNvLNyjwM7Yw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@mui/types": "^7.4.12", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-virtualizer": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/@mui/x-virtualizer/-/x-virtualizer-0.3.4.tgz", + "integrity": "sha512-b80Wt08bT+KVDunurdd4es76PdYaazoNGpGde3Ki7jGVaSpoljMYSRVlCtifei+4cOUD3NKJhG4e3av/czTekg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.4", + "@mui/utils": "^7.3.5", + "@mui/x-internals": "8.26.0" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/@mui/x-virtualizer/node_modules/@mui/types": { + "version": "7.4.12", + "resolved": "https://registry.npmjs.org/@mui/types/-/types-7.4.12.tgz", + "integrity": "sha512-iKNAF2u9PzSIj40CjvKJWxFXJo122jXVdrmdh0hMYd+FR+NuJMkr/L88XwWLCRiJ5P1j+uyac25+Kp6YC4hu6w==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/x-virtualizer/node_modules/@mui/utils": { + "version": "7.3.9", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-7.3.9.tgz", + "integrity": "sha512-U6SdZaGbfb65fqTsH3V5oJdFj9uYwyLE2WVuNvmbggTSDBb8QHrFsqY8BN3taK9t3yJ8/BPHD/kNvLNyjwM7Yw==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.28.6", + "@mui/types": "^7.4.12", + "@types/prop-types": "^15.7.15", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^19.2.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, "node_modules/@nodelib/fs.scandir": { "version": "2.1.5", "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", @@ -1910,6 +2260,42 @@ "url": "https://opencollective.com/popperjs" } }, + "node_modules/@reduxjs/toolkit": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@reduxjs/toolkit/-/toolkit-2.11.2.tgz", + "integrity": "sha512-Kd6kAHTA6/nUpp8mySPqj3en3dm0tdMIgbttnQ1xFMVpufoj+ADi8pXLBsd4xzTRHQa7t/Jv8W5UnCuW4kuWMQ==", + "license": "MIT", + "dependencies": { + "@standard-schema/spec": "^1.0.0", + "@standard-schema/utils": "^0.3.0", + "immer": "^11.0.0", + "redux": "^5.0.1", + "redux-thunk": "^3.1.0", + "reselect": "^5.1.0" + }, + "peerDependencies": { + "react": "^16.9.0 || ^17.0.0 || ^18 || ^19", + "react-redux": "^7.2.1 || ^8.1.3 || ^9.0.0" + }, + "peerDependenciesMeta": { + "react": { + "optional": true + }, + "react-redux": { + "optional": true + } + } + }, + "node_modules/@reduxjs/toolkit/node_modules/immer": { + "version": "11.1.4", + "resolved": "https://registry.npmjs.org/immer/-/immer-11.1.4.tgz", + "integrity": "sha512-XREFCPo6ksxVzP4E0ekD5aMdf8WMwmdNaz6vuvxgI40UaEiu6q3p8X52aU6GdyvLY3XXX/8R7JOTXStz/nBbRw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -2318,6 +2704,18 @@ "url": "https://github.com/sindresorhus/is?sponsor=1" } }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, + "node_modules/@standard-schema/utils": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/@standard-schema/utils/-/utils-0.3.0.tgz", + "integrity": "sha512-e7Mew686owMaPJVNNLs55PUvgz371nKgwsc4vxE49zsODpJEnxgxRo2y/OKrqueavXgZNMDVj3DdHFlaSAeU8g==", + "license": "MIT" + }, "node_modules/@szmarczak/http-timer": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/@szmarczak/http-timer/-/http-timer-4.0.6.tgz", @@ -2397,6 +2795,81 @@ "@types/responselike": "^1.0.0" } }, + "node_modules/@types/d3-array": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", + "integrity": "sha512-hOLWVbm7uRza0BYXpIIW5pxfrKe0W+D5lrFiAEYR+pb6w3N2SwSMaJbXdUfSEv+dT4MfHBLtn5js0LAWaO6otw==", + "license": "MIT" + }, + "node_modules/@types/d3-color": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/@types/d3-color/-/d3-color-3.1.3.tgz", + "integrity": "sha512-iO90scth9WAbmgv7ogoq57O9YpKmFBbmoEoCHDB2xMBY0+/KVrqAaCDyCE16dUspeOvIxFFRI+0sEtqDqy2b4A==", + "license": "MIT" + }, + "node_modules/@types/d3-ease": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-ease/-/d3-ease-3.0.2.tgz", + "integrity": "sha512-NcV1JjO5oDzoK26oMzbILE6HW7uVXOHLQvHshBUW4UMdZGfiY6v5BeQwh9a9tCzv+CeefZQHJt5SRgK154RtiA==", + "license": "MIT" + }, + "node_modules/@types/d3-format": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-format/-/d3-format-3.0.4.tgz", + "integrity": "sha512-fALi2aI6shfg7vM5KiR1wNJnZ7r6UuggVqtDA+xiEdPZQwy/trcQaHnwShLuLdta2rTymCNpxYTiMZX/e09F4g==", + "license": "MIT" + }, + "node_modules/@types/d3-interpolate": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-interpolate/-/d3-interpolate-3.0.4.tgz", + "integrity": "sha512-mgLPETlrpVV1YRJIglr4Ez47g7Yxjl1lj7YKsiMCb27VJH9W8NVM6Bb9d8kkpG/uAQS5AmbA48q2IAolKKo1MA==", + "license": "MIT", + "dependencies": { + "@types/d3-color": "*" + } + }, + "node_modules/@types/d3-path": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@types/d3-path/-/d3-path-3.1.1.tgz", + "integrity": "sha512-VMZBYyQvbGmWyWVea0EHs/BwLgxc+MKi1zLDCONksozI4YJMcTt8ZEuIR4Sb1MMTE8MMW49v0IwI5+b7RmfWlg==", + "license": "MIT" + }, + "node_modules/@types/d3-scale": { + "version": "4.0.9", + "resolved": "https://registry.npmjs.org/@types/d3-scale/-/d3-scale-4.0.9.tgz", + "integrity": "sha512-dLmtwB8zkAeO/juAMfnV+sItKjlsw2lKdZVVy6LRr0cBmegxSABiLEpGVmSJJ8O08i4+sGR6qQtb6WtuwJdvVw==", + "license": "MIT", + "dependencies": { + "@types/d3-time": "*" + } + }, + "node_modules/@types/d3-shape": { + "version": "3.1.8", + "resolved": "https://registry.npmjs.org/@types/d3-shape/-/d3-shape-3.1.8.tgz", + "integrity": "sha512-lae0iWfcDeR7qt7rA88BNiqdvPS5pFVPpo5OfjElwNaT2yyekbM0C9vK+yqBqEmHr6lDkRnYNoTBYlAgJa7a4w==", + "license": "MIT", + "dependencies": { + "@types/d3-path": "*" + } + }, + "node_modules/@types/d3-time": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/d3-time/-/d3-time-3.0.4.tgz", + "integrity": "sha512-yuzZug1nkAAaBlBBikKZTgzCeA+k1uy4ZFwWANOfKw5z5LRhV0gNA7gNkKm7HoK+HRN0wX3EkxGk0fpbWhmB7g==", + "license": "MIT" + }, + "node_modules/@types/d3-time-format": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/@types/d3-time-format/-/d3-time-format-4.0.3.tgz", + "integrity": "sha512-5xg9rC+wWL8kdDj153qZcsJ0FWiFt0J5RB6LYUNZjwSnesfblqrI/bJ1wBdJ8OQfncgbJG5+2F+qfqnqyzYxyg==", + "license": "MIT" + }, + "node_modules/@types/d3-timer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@types/d3-timer/-/d3-timer-3.0.2.tgz", + "integrity": "sha512-Ps3T8E8dZDam6fUyNiMkekK3XUsaUEik+idO9/YjPtfj2qruF8tFBXS7XhtE4iIXBLxhmLjP3SXpLhVf21I9Lw==", + "license": "MIT" + }, "node_modules/@types/debug": { "version": "4.1.13", "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", @@ -2539,6 +3012,12 @@ "@types/node": "*" } }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.6.tgz", + "integrity": "sha512-zFDAD+tlpf2r4asuHEj0XH6pY6i0g5NeAHPn+15wk3BV6JA69eERFXC1gyGThDkVa1zCyKr5jox1+2LbV/AMLg==", + "license": "MIT" + }, "node_modules/@types/verror": { "version": "1.10.11", "resolved": "https://registry.npmjs.org/@types/verror/-/verror-1.10.11.tgz", @@ -3184,6 +3663,12 @@ "node": ">=6.0.0" } }, + "node_modules/bezier-easing": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/bezier-easing/-/bezier-easing-2.1.0.tgz", + "integrity": "sha512-gbIqZ/eslnUFC1tjEvtz0sgx+xTK20wDnYMIA27VA04R7w6xxXQPZDbibjA9DTWZRA2CXtwHykkVzlCaAJAZig==", + "license": "MIT" + }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -3924,6 +4409,127 @@ "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "license": "MIT" }, + "node_modules/d3-array": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz", + "integrity": "sha512-tdQAmyA18i4J7wprpYq8ClcxZy3SC31QMeByyCFyRt7BVHdREQZ5lpzoe5mFEYZUWe+oq8HBvk9JjpibyEV4Jg==", + "license": "ISC", + "dependencies": { + "internmap": "1 - 2" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-color": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-color/-/d3-color-3.1.0.tgz", + "integrity": "sha512-zg/chbXyeBtMQ1LbD/WSoW2DpC3I0mpmPdW+ynRTj/x2DAWYrIY7qeZIHidozwV24m4iavr15lNwIwLxRmOxhA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-ease": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-ease/-/d3-ease-3.0.1.tgz", + "integrity": "sha512-wR/XK3D3XcLIZwpbvQwQ5fK+8Ykds1ip7A2Txe0yxncXSdq1L9skcG7blcedkOX+ZcgxGAmLX1FrRGbADwzi0w==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-format": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/d3-format/-/d3-format-3.1.2.tgz", + "integrity": "sha512-AJDdYOdnyRDV5b6ArilzCPPwc1ejkHcoyFarqlPqT7zRYjhavcT3uSrqcMvsgh2CgoPbK3RCwyHaVyxYcP2Arg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-interpolate": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-interpolate/-/d3-interpolate-3.0.1.tgz", + "integrity": "sha512-3bYs1rOD33uo8aqJfKP3JWPAibgw8Zm2+L9vBKEHJ2Rg+viTR7o5Mmv5mZcieN+FRYaAOWX5SJATX6k1PWz72g==", + "license": "ISC", + "dependencies": { + "d3-color": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-path": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-path/-/d3-path-3.1.0.tgz", + "integrity": "sha512-p3KP5HCf/bvjBSSKuXid6Zqijx7wIfNW+J/maPs+iwR35at5JCbLUT0LzF1cnjbCHWhqzQTIN2Jpe8pRebIEFQ==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-scale": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/d3-scale/-/d3-scale-4.0.2.tgz", + "integrity": "sha512-GZW464g1SH7ag3Y7hXjf8RoUuAFIqklOAq3MRl4OaWabTFJY9PN/E1YklhXLh+OQ3fM9yS2nOkCoS+WLZ6kvxQ==", + "license": "ISC", + "dependencies": { + "d3-array": "2.10.0 - 3", + "d3-format": "1 - 3", + "d3-interpolate": "1.2.0 - 3", + "d3-time": "2.1.1 - 3", + "d3-time-format": "2 - 4" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-shape": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/d3-shape/-/d3-shape-3.2.0.tgz", + "integrity": "sha512-SaLBuwGm3MOViRq2ABk3eLoxwZELpH6zhl3FbAoJ7Vm1gofKx6El1Ib5z23NUEhF9AsGl7y+dzLe5Cw2AArGTA==", + "license": "ISC", + "dependencies": { + "d3-path": "^3.1.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/d3-time/-/d3-time-3.1.0.tgz", + "integrity": "sha512-VqKjzBLejbSMT4IgbmVgDjpkYrNWUYJnbCGo874u7MMKIWsILRX+OpX/gTk8MqjpT1A/c6HY2dCA77ZN0lkQ2Q==", + "license": "ISC", + "dependencies": { + "d3-array": "2 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-time-format": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/d3-time-format/-/d3-time-format-4.1.0.tgz", + "integrity": "sha512-dJxPBlzC7NugB2PDLwo9Q8JiTR3M3e4/XANkreKSUxF8vvXKqm1Yfq4Q5dl8budlunRVlUUaDUgFt7eA8D6NLg==", + "license": "ISC", + "dependencies": { + "d3-time": "1 - 3" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/d3-timer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/d3-timer/-/d3-timer-3.0.1.tgz", + "integrity": "sha512-ndfJ/JxxMd3nw31uyKoY2naivF+r29V+Lc0svZxe1JvvIRmi8hUsrMvdOwgS1o6uBHmiz91geQ0ylPP0aj1VUA==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -3995,6 +4601,12 @@ } } }, + "node_modules/decimal.js-light": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/decimal.js-light/-/decimal.js-light-2.5.1.tgz", + "integrity": "sha512-qIMFpTMZmny+MMIitAB6D7iVPEorVw6YQRWkvarTkT4tBeSLLiHzcwj6q0MmYSFCiVpiqPJTJEYIrpcPzVEIvg==", + "license": "MIT" + }, "node_modules/decompress": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/decompress/-/decompress-4.2.1.tgz", @@ -4911,6 +5523,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-toolkit": { + "version": "1.45.1", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.45.1.tgz", + "integrity": "sha512-/jhoOj/Fx+A+IIyDNOvO3TItGmlMKhtX8ISAHKE90c4b/k1tqaqEZ+uUqfpU8DMnW5cgNJv606zS55jGvza0Xw==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/es6-error": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", @@ -5292,6 +5914,12 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.4", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", + "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==", + "license": "MIT" + }, "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", @@ -5453,6 +6081,12 @@ "node": "^10.12.0 || >=12.0.0" } }, + "node_modules/flatqueue": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/flatqueue/-/flatqueue-3.0.0.tgz", + "integrity": "sha512-y1deYaVt+lIc/d2uIcWDNd0CrdQTO5xoCjeFdhX0kSXvm2Acm0o+3bAOiYklTEoRyzwio3sv3/IiBZdusbAe2Q==", + "license": "ISC" + }, "node_modules/flatted": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", @@ -6195,6 +6829,16 @@ "node": ">= 4" } }, + "node_modules/immer": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/immer/-/immer-10.2.0.tgz", + "integrity": "sha512-d/+XTN3zfODyjr89gM3mPq1WNX2B8pYsu7eORitdwyA2sBubnTl3laYlBk4sXY5FUa5qTZGBDPJICVbvqzjlbw==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/immer" + } + }, "node_modules/import-fresh": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", @@ -6254,6 +6898,15 @@ "node": ">= 0.4" } }, + "node_modules/internmap": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/internmap/-/internmap-2.0.3.tgz", + "integrity": "sha512-5Hh7Y1wQbvY5ooGgPbDaL5iYLAPzMTUrjMulskHLH6wnv/A+1q5rgEaiuqEjB+oxGXIVZs1FF+R/KPN3ZSQYYg==", + "license": "ISC", + "engines": { + "node": ">=12" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -7977,6 +8630,29 @@ "integrity": "sha512-W+EWGn2v0ApPKgKKCy/7s7WHXkboGcsrXE+2joLyVxkbyVQfO3MUEaUQDHoSmb8TFFrSKYa9mw64WZHNHSDzYA==", "license": "MIT" }, + "node_modules/react-redux": { + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-9.2.0.tgz", + "integrity": "sha512-ROY9fvHhwOD9ySfrF0wmvu//bKCQ6AeZZq1nJNtbDC+kk5DuSuNX/n6YWYF/SYy7bSba4D4FSz8DJeKY/S/r+g==", + "license": "MIT", + "dependencies": { + "@types/use-sync-external-store": "^0.0.6", + "use-sync-external-store": "^1.4.0" + }, + "peerDependencies": { + "@types/react": "^18.2.25 || ^19", + "react": "^18.0 || ^19", + "redux": "^5.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, "node_modules/react-refresh": { "version": "0.17.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", @@ -8071,6 +8747,51 @@ "node": ">=8.10.0" } }, + "node_modules/recharts": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/recharts/-/recharts-3.8.1.tgz", + "integrity": "sha512-mwzmO1s9sFL0TduUpwndxCUNoXsBw3u3E/0+A+cLcrSfQitSG62L32N69GhqUrrT5qKcAE3pCGVINC6pqkBBQg==", + "license": "MIT", + "workspaces": [ + "www" + ], + "dependencies": { + "@reduxjs/toolkit": "^1.9.0 || 2.x.x", + "clsx": "^2.1.1", + "decimal.js-light": "^2.5.1", + "es-toolkit": "^1.39.3", + "eventemitter3": "^5.0.1", + "immer": "^10.1.1", + "react-redux": "8.x.x || 9.x.x", + "reselect": "5.1.1", + "tiny-invariant": "^1.3.3", + "use-sync-external-store": "^1.2.2", + "victory-vendor": "^37.0.2" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-dom": "^16.0.0 || ^17.0.0 || ^18.0.0 || ^19.0.0", + "react-is": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, + "node_modules/redux": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-5.0.1.tgz", + "integrity": "sha512-M9/ELqF6fy8FwmkpnF0S3YKOqMyoWJ4+CS5Efg2ct3oY9daQvd/Pc71FpGZsVsbl3Cpb+IIcjBDUnnyBdQbq4w==", + "license": "MIT" + }, + "node_modules/redux-thunk": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/redux-thunk/-/redux-thunk-3.1.0.tgz", + "integrity": "sha512-NW2r5T6ksUKXCabzhL9z+h206HQw/NJkcLm1GPImRQ8IzfXwRGqjVhKJGauHirT0DAuyy6hjdnMZaRoAcy0Klw==", + "license": "MIT", + "peerDependencies": { + "redux": "^5.0.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -8125,6 +8846,12 @@ "node": ">=0.10.0" } }, + "node_modules/reselect": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/reselect/-/reselect-5.1.1.tgz", + "integrity": "sha512-K/BG6eIky/SBpzfHZv/dd+9JBFiS4SWV7FIujVyJRux6e45+73RaUHXLmIR1f7WOMaQ0U1km6qwklRQxpJJY0w==", + "license": "MIT" + }, "node_modules/resolve": { "version": "1.22.11", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.11.tgz", @@ -9121,6 +9848,12 @@ "integrity": "sha512-w89qg7PI8wAdvX60bMDP+bFoD5Dvhm9oLheFp5O4a2QF0cSBGsBX4qZmadPMvVqlLJBBci+WqGGOAPvcDeNSVg==", "license": "MIT" }, + "node_modules/tiny-invariant": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/tiny-invariant/-/tiny-invariant-1.3.3.tgz", + "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", + "license": "MIT" + }, "node_modules/tmp": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", @@ -9380,6 +10113,15 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.6.0.tgz", + "integrity": "sha512-Pp6GSwGP/NrPIrxVFAIkOQeyw8lFenOHijQWkUTrDvrF4ALqylP2C/KCkeS9dpUM3KvYRQhna5vt7IL95+ZQ9w==", + "license": "MIT", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" + } + }, "node_modules/utf8-byte-length": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/utf8-byte-length/-/utf8-byte-length-1.0.5.tgz", @@ -9409,6 +10151,28 @@ "node": ">=0.6.0" } }, + "node_modules/victory-vendor": { + "version": "37.3.6", + "resolved": "https://registry.npmjs.org/victory-vendor/-/victory-vendor-37.3.6.tgz", + "integrity": "sha512-SbPDPdDBYp+5MJHhBCAyI7wKM3d5ivekigc2Dk2s7pgbZ9wIgIBYGVw4zGHBml/qTFbexrofXW6Gu4noGxrOwQ==", + "license": "MIT AND ISC", + "dependencies": { + "@types/d3-array": "^3.0.3", + "@types/d3-ease": "^3.0.0", + "@types/d3-interpolate": "^3.0.1", + "@types/d3-scale": "^4.0.2", + "@types/d3-shape": "^3.1.0", + "@types/d3-time": "^3.0.0", + "@types/d3-timer": "^3.0.0", + "d3-array": "^3.1.6", + "d3-ease": "^3.0.1", + "d3-interpolate": "^3.0.1", + "d3-scale": "^4.0.2", + "d3-shape": "^3.1.0", + "d3-time": "^3.0.0", + "d3-timer": "^3.0.1" + } + }, "node_modules/vite": { "version": "5.4.21", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", diff --git a/package.json b/package.json index 19ac63c..f7e90ae 100644 --- a/package.json +++ b/package.json @@ -27,6 +27,8 @@ "@emotion/styled": "^11.11.0", "@mui/icons-material": "^6.0.0", "@mui/material": "^6.0.0", + "@mui/x-charts": "^8.28.2", + "@mui/x-data-grid": "^8.28.2", "@types/pidusage": "^2.0.5", "@types/prismjs": "^1.26.6", "axios": "^1.14.0", @@ -38,6 +40,7 @@ "react-dom": "^18.2.0", "react-i18next": "^13.0.0", "react-simple-code-editor": "^0.14.1", + "recharts": "^3.8.1", "sweetalert2": "^11.26.24" }, "devDependencies": { diff --git a/service_events.json b/service_events.json new file mode 100644 index 0000000..3b36791 --- /dev/null +++ b/service_events.json @@ -0,0 +1,95 @@ +[ + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "4evpwn0ju", + "timestamp": 1775130158656 + }, + { + "service": "mariadb:10.11.8", + "action": "exit", + "status": "failure", + "details": "Exit code: null", + "id": "9qdrb8lje", + "timestamp": 1775130078423 + }, + { + "service": "mariadb:10.11.8", + "action": "stop", + "status": "info", + "id": "20l26ok4a", + "timestamp": 1775130078407 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "yi01pzl6i", + "timestamp": 1775130047940 + }, + { + "service": "mariadb:11.4.2", + "action": "exit", + "status": "failure", + "details": "Exit code: 1", + "id": "kywi5m5pc", + "timestamp": 1775130047898 + }, + { + "service": "php:5.6.40", + "action": "start", + "status": "success", + "id": "54rhx8r9q", + "timestamp": 1775130043224 + }, + { + "service": "php:7.4.33", + "action": "start", + "status": "success", + "id": "rwf7gpo22", + "timestamp": 1775130043205 + }, + { + "service": "php:8.2.30", + "action": "start", + "status": "success", + "id": "v0ilik7d8", + "timestamp": 1775130043185 + }, + { + "service": "php:8.5.4", + "action": "start", + "status": "success", + "id": "981vuhhw7", + "timestamp": 1775130043168 + }, + { + "service": "mariadb:10.11.8", + "action": "start", + "status": "success", + "id": "rwx1q9oet", + "timestamp": 1775130042752 + }, + { + "service": "mariadb:11.4.2", + "action": "start", + "status": "success", + "id": "wh5q33p48", + "timestamp": 1775130042733 + }, + { + "service": "apache", + "action": "start", + "status": "success", + "id": "6tbgu0wb4", + "timestamp": 1775130042184 + }, + { + "service": "nginx", + "action": "start", + "status": "success", + "id": "mrd4fgm4i", + "timestamp": 1775130042115 + } +] \ No newline at end of file diff --git a/settings.json b/settings.json index 7c8e56e..9ca7e43 100644 --- a/settings.json +++ b/settings.json @@ -10,6 +10,9 @@ "cliEnvEnabled": true, "defaultPhpVersion": "php-7.4.33", "defaultMariaDbVersion": "mariadb-11.4.2", + "mariaDbTuning": { + "mariadb-10.11.8": "\nmax_connections=2720\ninnodb_buffer_pool_size=8960M\nkey_buffer_size=800M\nmax_allowed_packet=1024M" + }, "mysqlPort": "3306", "mysqlPorts": {} } \ No newline at end of file diff --git a/src/main/ipc/index.ts b/src/main/ipc/index.ts index 79fc7c7..da658f4 100644 --- a/src/main/ipc/index.ts +++ b/src/main/ipc/index.ts @@ -14,6 +14,7 @@ import path from 'path' import { mariaDbOperationService } from '../services/MariaDbOperationService' import { cliAliasService } from '../services/CliAliasService' import { cliBinService } from '../services/CliBinService' +import { serviceEventLogger } from '../services/ServiceEventLogger' function findExecutable(dir: string, name: string): string | null { if (!fs.existsSync(dir)) return null @@ -311,6 +312,15 @@ export function registerIpcHandlers(): void { return { success: false, message: 'Bu servis için reload desteklenmiyor.' } }) + ipcMain.handle('services:get-events', async () => { + return serviceEventLogger.getEvents() + }) + + ipcMain.handle('services:clear-events', async () => { + serviceEventLogger.clear() + return { success: true } + }) + ipcMain.handle('services:start', async (_event, serviceName: string) => { try { const settings = configService.getSettings() @@ -576,30 +586,6 @@ export function registerIpcHandlers(): void { return result }) - ipcMain.handle('php:config:get', async (_event, version: string) => { - const phpDir = path.join(configService.getBasePath(), 'bin', `php-${version}`) - const iniPath = path.join(phpDir, 'php.ini') - if (fs.existsSync(iniPath)) { - return { - content: fs.readFileSync(iniPath, 'utf8'), - path: iniPath - } - } - return { content: '', path: iniPath } - }) - - ipcMain.handle('php:config:save', async (_event, { version, content }) => { - const phpDir = path.join(configService.getBasePath(), 'bin', `php-${version}`) - const iniPath = path.join(phpDir, 'php.ini') - fs.writeFileSync(iniPath, content) - - const serviceName = `php:${version}` - if (processManager.isServiceRunning(serviceName)) { - await processManager.stopService(serviceName) - } - - return { success: true, message: 'Configuration saved.' } - }) // MariaDB Versions ipcMain.handle('mariadb:versions', async () => { diff --git a/src/main/services/ProcessManager.ts b/src/main/services/ProcessManager.ts index b5e9025..a00af6e 100644 --- a/src/main/services/ProcessManager.ts +++ b/src/main/services/ProcessManager.ts @@ -4,6 +4,7 @@ import { app } from 'electron' import fs from 'fs' import pidusage from 'pidusage' import { configService } from './ConfigService' +import { serviceEventLogger } from './ServiceEventLogger' import { t } from '../utils/i18n' export class ProcessManager { @@ -130,17 +131,21 @@ export class ProcessManager { console.log(`[PROCESS] Started ${name} with PID ${child.pid}`) this.addLog(name, `${t('common.running')} (PID: ${child.pid})`) + serviceEventLogger.log({ service: name, action: 'start', status: 'success' }) child.stdout?.on('data', (data) => this.addLog(name, data.toString())) child.stderr?.on('data', (data) => this.addLog(name, data.toString())) child.on('error', (err) => { this.addLog(name, `Failed to start: ${err.message}`) + serviceEventLogger.log({ service: name, action: 'start', status: 'failure', details: err.message }) this.processes.delete(name) }) child.on('exit', (code) => { + const status = code === 0 ? 'success' : 'failure' this.addLog(name, `${t('common.stopped')} (Code: ${code})`) + serviceEventLogger.log({ service: name, action: 'exit', status, details: `Exit code: ${code}` }) if (code !== 0) { this.checkErrorLogs(name) } @@ -152,6 +157,7 @@ export class ProcessManager { return true } catch (error: any) { this.addLog(name, `Spawn Error: ${error.message}`) + serviceEventLogger.log({ service: name, action: 'start', status: 'failure', details: error.message }) return false } } @@ -160,6 +166,7 @@ export class ProcessManager { const child = this.processes.get(name) if (child) { this.addLog(name, `${t('common.stopping')}...`) + serviceEventLogger.log({ service: name, action: 'stop', status: 'info' }) try { if (process.platform === 'win32') { child.kill() diff --git a/src/main/services/ServiceEventLogger.ts b/src/main/services/ServiceEventLogger.ts new file mode 100644 index 0000000..fbbfe13 --- /dev/null +++ b/src/main/services/ServiceEventLogger.ts @@ -0,0 +1,68 @@ +import fs from 'fs' +import path from 'path' +import { configService } from './ConfigService' + +export interface ServiceEvent { + id: string + service: string + action: 'start' | 'stop' | 'exit' | 'error' | 'restart' + timestamp: number + details?: string + status: 'success' | 'failure' | 'info' +} + +export class ServiceEventLogger { + private filePath: string + private events: ServiceEvent[] = [] + private maxEvents = 1000 + + constructor() { + this.filePath = path.join(configService.getBasePath(), 'service_events.json') + this.load() + } + + private load() { + try { + if (fs.existsSync(this.filePath)) { + const content = fs.readFileSync(this.filePath, 'utf8') + this.events = JSON.parse(content) + } + } catch (e) { + console.error('Failed to load service events:', e) + this.events = [] + } + } + + private save() { + try { + // Keep only the last N events + if (this.events.length > this.maxEvents) { + this.events = this.events.slice(-this.maxEvents) + } + fs.writeFileSync(this.filePath, JSON.stringify(this.events, null, 2)) + } catch (e) { + console.error('Failed to save service events:', e) + } + } + + log(event: Omit) { + const newEvent: ServiceEvent = { + ...event, + id: Math.random().toString(36).substr(2, 9), + timestamp: Date.now() + } + this.events.unshift(newEvent) // Add to top + this.save() + } + + getEvents() { + return this.events + } + + clear() { + this.events = [] + this.save() + } +} + +export const serviceEventLogger = new ServiceEventLogger() diff --git a/src/renderer/src/components/ServiceEventMonitor.tsx b/src/renderer/src/components/ServiceEventMonitor.tsx new file mode 100644 index 0000000..8291d05 --- /dev/null +++ b/src/renderer/src/components/ServiceEventMonitor.tsx @@ -0,0 +1,261 @@ +import { useState, useEffect, useMemo } from 'react' +import { + Box, + Typography, + IconButton, + Tooltip, + Stack, + Button, + Paper, + Divider, + Alert, + CircularProgress, + Chip +} from '@mui/material' +import { + Refresh as RefreshIcon, + DeleteSweep as ClearIcon, + Close as CloseIcon, + History as HistoryIcon, + CheckCircle as SuccessIcon, + Error as ErrorIcon, + Info as InfoIcon, + PlayArrow as StartIcon, + Stop as StopIcon +} from '@mui/icons-material' +import { DataGrid, GridColDef } from '@mui/x-data-grid' +import { BarChart } from '@mui/x-charts' +import { useTranslation } from 'react-i18next' +import Swal from 'sweetalert2' + +interface ServiceEvent { + id: string + service: string + action: 'start' | 'stop' | 'exit' | 'error' | 'restart' + timestamp: number + details?: string + status: 'success' | 'failure' | 'info' +} + +export default function ServiceEventMonitor({ onClose }: { onClose?: () => void }) { + const { t } = useTranslation() + const [events, setEvents] = useState([]) + const [loading, setLoading] = useState(true) + + const fetchEvents = async () => { + if (!window.api) return + setLoading(true) + try { + const data = await window.api.invoke('services:get-events') + setEvents(data) + } catch (error) { + console.error('Failed to fetch events:', error) + } + setLoading(false) + } + + useEffect(() => { + fetchEvents() + const interval = setInterval(fetchEvents, 5000) + return () => clearInterval(interval) + }, []) + + const handleClear = async () => { + const result = await Swal.fire({ + title: t('service_monitor.clear_confirm'), + icon: 'warning', + showCancelButton: true, + confirmButtonColor: '#d33', + confirmButtonText: t('common.yes_delete'), + cancelButtonText: t('common.cancel'), + background: '#1a1d21', + color: '#fff' + }) + + if (result.isConfirmed && window.api) { + await window.api.invoke('services:clear-events') + fetchEvents() + } + } + + // Prepare chart data + const chartData = useMemo(() => { + const services = Array.from(new Set(events.map(e => e.service))) + const data = services.map(s => { + const serviceEvents = events.filter(e => e.service === s) + return { + service: s, + start: serviceEvents.filter(e => e.action === 'start' && e.status === 'success').length, + stop: serviceEvents.filter(e => e.action === 'stop' || e.action === 'exit').length, + error: serviceEvents.filter(e => e.status === 'failure' || e.action === 'error').length + } + }) + return data + }, [events]) + + const columns: GridColDef[] = [ + { + field: 'timestamp', + headerName: t('service_monitor.columns.timestamp'), + width: 130, + valueGetter: (params) => new Date(params).toLocaleTimeString() + }, + { + field: 'service', + headerName: t('service_monitor.columns.service'), + width: 120, + renderCell: (params) => ( + + {params.value} + + ) + }, + { + field: 'action', + headerName: t('service_monitor.columns.action'), + width: 100, + renderCell: (params) => { + const action = params.value as string + const color = action === 'start' ? 'success' : action === 'stop' || action === 'exit' ? 'error' : 'info' + const Icon = action === 'start' ? StartIcon : action === 'stop' || action === 'exit' ? StopIcon : InfoIcon + return ( + } + label={t(`common.${action}`)} + color={color as any} + variant="outlined" + sx={{ fontSize: 10, height: 20 }} + /> + ) + } + }, + { + field: 'status', + headerName: t('service_monitor.columns.status'), + width: 90, + renderCell: (params) => { + const status = params.value as string + return status === 'success' ? ( + + ) : status === 'failure' ? ( + + ) : ( + + ) + } + }, + { + field: 'details', + headerName: t('service_monitor.columns.details'), + flex: 1, + renderCell: (params) => ( + + + {params.value || '-'} + + + ) + } + ] + + return ( + + {/* Header */} + + + + + + {t('service_monitor.title')} + + + {t('service_monitor.desc')} + + + + + + + + + + + {onClose && ( + + + + )} + + + + {/* Content */} + + {loading && events.length === 0 ? ( + + + + ) : events.length === 0 ? ( + + {t('service_monitor.no_events')} + + ) : ( + + {/* Chart Area */} + + + {t('service_monitor.chart_title')} + + + + + + + {/* Logs Area */} + + + + + )} + + + ) +} diff --git a/src/renderer/src/locales/en.json b/src/renderer/src/locales/en.json index 404ad2b..2456718 100644 --- a/src/renderer/src/locales/en.json +++ b/src/renderer/src/locales/en.json @@ -47,7 +47,9 @@ "stopping": "Stopping", "reset_confirm_title": "Are you sure?", "reset_confirm_text": "Do you want to reset {{service}} data? This operation deletes all data and performs a clean installation.", - "reset_btn": "Yes, Reset" + "reset_btn": "Yes, Reset", + "operating": "Operation in progress...", + "please_wait": "Please wait..." }, "dashboard": { "title": "Dashboard", @@ -305,5 +307,20 @@ "max_allowed_packet": "Max Allowed Packet", "max_allowed_packet_desc": "Maximum size of one packet or any generated/intermediate string." } + }, + "service_monitor": { + "title": "Service Monitor", + "desc": "Track the start, stop, and error history of services.", + "event_log": "Event Log", + "clear_confirm": "Are you sure you want to clear the entire event history?", + "chart_title": "Service Activity Chart", + "no_events": "No events recorded yet.", + "columns": { + "service": "Service", + "action": "Action", + "status": "Status", + "timestamp": "Time", + "details": "Details" + } } } \ No newline at end of file diff --git a/src/renderer/src/locales/tr.json b/src/renderer/src/locales/tr.json index ddf4261..df9d7ce 100644 --- a/src/renderer/src/locales/tr.json +++ b/src/renderer/src/locales/tr.json @@ -47,7 +47,9 @@ "stop_status": "Durduruluyor", "reset_confirm_title": "Emin misiniz?", "reset_confirm_text": "{{service}} verilerini sıfırlamak istiyor musunuz? Bu işlem tüm verileri siler ve temiz kurulum yapar.", - "reset_btn": "Evet, Sıfırla" + "reset_btn": "Evet, Sıfırla", + "operating": "İşlem devam ediyor...", + "please_wait": "Lütfen bekleyin..." }, "dashboard": { "title": "Özet", @@ -298,7 +300,22 @@ "key_buffer_size": "Anahtar Tampon Boyutu", "key_buffer_size_desc": "MyISAM tabloları için indeks blokları (tüm iş parçacıkları tarafından paylaşılır).", "max_allowed_packet": "Maksimum Paket Boyutu", - "max_allowed_packet_desc": "Bir paketin veya oluşturulan herhangi bir dizenin maksimum boyutu." + "max_allowed_packet_desc": "Tek bir SQL sorgusu veya sonucunun maksimum boyutu." + } + }, + "service_monitor": { + "title": "Servis İzleyici", + "desc": "Servislerin başlama, durma ve hata geçmişini takip edin.", + "event_log": "Olay Günlüğü", + "clear_confirm": "Tüm olay geçmişini silmek istediğinize emin misiniz?", + "chart_title": "Servis Aktivite Grafiği", + "no_events": "Henüz bir olay kaydedilmemiş.", + "columns": { + "service": "Servis", + "action": "Eylem", + "status": "Durum", + "timestamp": "Zaman", + "details": "Detay" } } } \ No newline at end of file