Merge develop into feature/1484-star-prompt
- Resolve conflicts in Sidebar component - Keep StarPrompt above support section
This commit is contained in:
commit
ee9981864e
51 files changed
+906
-440
No files matched your search
@@ -16,7 +16,7 @@ Mention the issue number(s) this PR addresses (e.g., #123).
|
||||
- [ ] I have labelled the PR correctly.
|
||||
- [ ] The issue I am working on is assigned to me.
|
||||
- [ ] I didn't use any hardcoded values (otherwise it will not scale, and will make it difficult to maintain consistency across the application).
|
||||
- [ ] I made sure font sizes, color choices etc are all referenced from the theme.
|
||||
- [ ] I made sure font sizes, color choices etc are all referenced from the theme. I have no hardcoded dimensions.
|
||||
- [ ] My PR is granular and targeted to one specific feature.
|
||||
- [ ] I took a screenshot or a video and attached to this PR if there is a UI change.
|
||||
|
||||
@@ -107,7 +107,7 @@ If you have any questions, suggestions or comments, please use our [Discord chan
|
||||
|
||||
## 🤝 Contributing
|
||||
|
||||
We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 3700+ stars and attracted 45+ contributors from around the globe.
|
||||
We pride ourselves on building strong connections with contributors at every level. Despite being a young project, Checkmate has already earned 4.4K+ stars and attracted 50+ contributors from around the globe.
|
||||
|
||||
Our repo is starred by employees from **Google, Microsoft, Intel, Cisco, Tencent, Electronic Arts, ByteDance, JP Morgan Chase, Deloitte, Accenture, Foxconn, Broadcom, China Telecom, Barclays, Capgemini, Wipro, Cloudflare, Dassault Systèmes and NEC**, so don’t hold back — jump in, contribute and learn with us!
|
||||
|
||||
|
||||
Generated
+63
-59
@@ -12,12 +12,12 @@
|
||||
"@emotion/styled": "^11.13.0",
|
||||
"@fontsource/roboto": "^5.0.13",
|
||||
"@hello-pangea/dnd": "^18.0.0",
|
||||
"@mui/icons-material": "6.4.6",
|
||||
"@mui/lab": "6.0.0-beta.29",
|
||||
"@mui/material": "6.4.6",
|
||||
"@mui/icons-material": "6.4.7",
|
||||
"@mui/lab": "6.0.0-beta.30",
|
||||
"@mui/material": "6.4.7",
|
||||
"@mui/x-charts": "^7.5.1",
|
||||
"@mui/x-data-grid": "7.27.2",
|
||||
"@mui/x-date-pickers": "7.27.1",
|
||||
"@mui/x-data-grid": "7.27.3",
|
||||
"@mui/x-date-pickers": "7.27.3",
|
||||
"@reduxjs/toolkit": "2.6.0",
|
||||
"@solana/wallet-adapter-base": "0.9.23",
|
||||
"@solana/wallet-adapter-material-ui": "0.16.34",
|
||||
@@ -33,8 +33,8 @@
|
||||
"immutability-helper": "^3.1.1",
|
||||
"joi": "17.13.3",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"maplibre-gl": "5.1.1",
|
||||
"mui-color-input": "^5.0.1",
|
||||
"maplibre-gl": "5.2.0",
|
||||
"mui-color-input": "^6.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
@@ -2688,10 +2688,13 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/@fontsource/roboto": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.1.1.tgz",
|
||||
"integrity": "sha512-XwVVXtERDQIM7HPUIbyDe0FP4SRovpjF7zMI8M7pbqFp3ahLJsJTd18h+E6pkar6UbV3btbwkKjYARr5M+SQow==",
|
||||
"license": "Apache-2.0"
|
||||
"version": "5.2.5",
|
||||
"resolved": "https://registry.npmjs.org/@fontsource/roboto/-/roboto-5.2.5.tgz",
|
||||
"integrity": "sha512-70r2UZ0raqLn5W+sPeKhqlf8wGvUXFWlofaDlcbt/S3d06+17gXKr3VNqDODB0I1ASme3dGT5OJj9NABt7OTZQ==",
|
||||
"license": "OFL-1.1",
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/ayuhito"
|
||||
}
|
||||
},
|
||||
"node_modules/@fractalwagmi/popup-connection": {
|
||||
"version": "1.1.1",
|
||||
@@ -3513,9 +3516,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/core-downloads-tracker": {
|
||||
"version": "6.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.6.tgz",
|
||||
"integrity": "sha512-rho5Q4IscbrVmK9rCrLTJmjLjfH6m/NcqKr/mchvck0EIXlyYUB9+Z0oVmkt/+Mben43LMRYBH8q/Uzxj/c4Vw==",
|
||||
"version": "6.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.4.7.tgz",
|
||||
"integrity": "sha512-XjJrKFNt9zAKvcnoIIBquXyFyhfrHYuttqMsoDS7lM7VwufYG4fAPw4kINjBFg++fqXM2BNAuWR9J7XVIuKIKg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"type": "opencollective",
|
||||
@@ -3523,9 +3526,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/icons-material": {
|
||||
"version": "6.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.6.tgz",
|
||||
"integrity": "sha512-rGJBvIQQbQAlyKYljHQ8wAQS/K2/uYwvemcpygnAmCizmCI4zSF9HQPuiG8Ql4YLZ6V/uKjA3WHIYmF/8sV+pQ==",
|
||||
"version": "6.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@mui/icons-material/-/icons-material-6.4.7.tgz",
|
||||
"integrity": "sha512-Rk8cs9ufQoLBw582Rdqq7fnSXXZTqhYRbpe1Y5SAz9lJKZP3CIdrj0PfG8HJLGw1hrsHFN/rkkm70IDzhJsG1g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.0"
|
||||
@@ -3538,7 +3541,7 @@
|
||||
"url": "https://opencollective.com/mui-org"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@mui/material": "^6.4.6",
|
||||
"@mui/material": "^6.4.7",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
},
|
||||
@@ -3549,14 +3552,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/lab": {
|
||||
"version": "6.0.0-beta.29",
|
||||
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.29.tgz",
|
||||
"integrity": "sha512-C5nTm9xfzVi4Zz4DLrZq1Kn+cQ2fGZmSJxyoUbOu+jDPlLZrUpSsgxEkkbjJB5dasiv6fgGKhO3EaKKyfobLAQ==",
|
||||
"version": "6.0.0-beta.30",
|
||||
"resolved": "https://registry.npmjs.org/@mui/lab/-/lab-6.0.0-beta.30.tgz",
|
||||
"integrity": "sha512-ayDYkzTlkm5cnDGa10bvuFygX+2b9Hm1T4QZYMqV8+nSx3frKE0TLAbE7/qQ4vInOO5E4aOkHVBwZjyO+UbMTA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.0",
|
||||
"@mui/base": "5.0.0-beta.69",
|
||||
"@mui/system": "^6.4.6",
|
||||
"@mui/system": "^6.4.7",
|
||||
"@mui/types": "^7.2.21",
|
||||
"@mui/utils": "^6.4.6",
|
||||
"clsx": "^2.1.1",
|
||||
@@ -3572,8 +3575,8 @@
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@mui/material": "^6.4.6",
|
||||
"@mui/material-pigment-css": "^6.4.6",
|
||||
"@mui/material": "^6.4.7",
|
||||
"@mui/material-pigment-css": "^6.4.7",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
@@ -3594,14 +3597,14 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/material": {
|
||||
"version": "6.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.6.tgz",
|
||||
"integrity": "sha512-6UyAju+DBOdMogfYmLiT3Nu7RgliorimNBny1pN/acOjc+THNFVE7hlxLyn3RDONoZJNDi/8vO4AQQr6dLAXqA==",
|
||||
"version": "6.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@mui/material/-/material-6.4.7.tgz",
|
||||
"integrity": "sha512-K65StXUeGAtFJ4ikvHKtmDCO5Ab7g0FZUu2J5VpoKD+O6Y3CjLYzRi+TMlI3kaL4CL158+FccMoOd/eaddmeRQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.0",
|
||||
"@mui/core-downloads-tracker": "^6.4.6",
|
||||
"@mui/system": "^6.4.6",
|
||||
"@mui/core-downloads-tracker": "^6.4.7",
|
||||
"@mui/system": "^6.4.7",
|
||||
"@mui/types": "^7.2.21",
|
||||
"@mui/utils": "^6.4.6",
|
||||
"@popperjs/core": "^2.11.8",
|
||||
@@ -3622,7 +3625,7 @@
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@mui/material-pigment-css": "^6.4.6",
|
||||
"@mui/material-pigment-css": "^6.4.7",
|
||||
"@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react": "^17.0.0 || ^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^17.0.0 || ^18.0.0 || ^19.0.0"
|
||||
@@ -3710,9 +3713,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/system": {
|
||||
"version": "6.4.6",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.6.tgz",
|
||||
"integrity": "sha512-FQjWwPec7pMTtB/jw5f9eyLynKFZ6/Ej9vhm5kGdtmts1z5b7Vyn3Rz6kasfYm1j2TfrfGnSXRvvtwVWxjpz6g==",
|
||||
"version": "6.4.7",
|
||||
"resolved": "https://registry.npmjs.org/@mui/system/-/system-6.4.7.tgz",
|
||||
"integrity": "sha512-7wwc4++Ak6tGIooEVA9AY7FhH2p9fvBMORT4vNLMAysH3Yus/9B9RYMbrn3ANgsOyvT3Z7nE+SP8/+3FimQmcg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.26.0",
|
||||
@@ -3858,9 +3861,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-data-grid": {
|
||||
"version": "7.27.2",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.27.2.tgz",
|
||||
"integrity": "sha512-Nj6PWp6WN0gkps676j1Jai3mP1fQotJbqMTvllU7LwQp09S/RE/uJicYdqfeWQLxtQipWm5dnAY63gzsTiAHGA==",
|
||||
"version": "7.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-data-grid/-/x-data-grid-7.27.3.tgz",
|
||||
"integrity": "sha512-7zbDbFrhV6ODjyn3ImOZG34nbMbCvmHgqYTYP273TNAj8hMy4BiLyiKFFZTzVddIj3KQ6qLzBpByhqifGgEDOg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.25.7",
|
||||
@@ -3896,9 +3899,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@mui/x-date-pickers": {
|
||||
"version": "7.27.1",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.27.1.tgz",
|
||||
"integrity": "sha512-2YPhTM9TM39dmIkEQdSB6P6NASePB9LuhXXKQqq0PX4FXGymYEPz/acQXkk617zwfxJJaDhJZ6g8SAv5pklTJQ==",
|
||||
"version": "7.27.3",
|
||||
"resolved": "https://registry.npmjs.org/@mui/x-date-pickers/-/x-date-pickers-7.27.3.tgz",
|
||||
"integrity": "sha512-igfKTPC4ZVCmS5j/NXcXBtj/hHseQHzRpCpIB1PMnJGhMdRYXnz8qZz5XhlNBKlzJVXkGu6Uil+obZpCLNj1xg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@babel/runtime": "^7.25.7",
|
||||
@@ -9186,9 +9189,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.8.1",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.1.tgz",
|
||||
"integrity": "sha512-NN+fvwH/kV01dYUQ3PTOZns4LWtWhOFCAhQ/pHb88WQ1hNe5V/dvFwc4VJcDL11LT9xSX0QtsR8sWUuyOuOq7g==",
|
||||
"version": "1.8.2",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.2.tgz",
|
||||
"integrity": "sha512-ls4GYBm5aig9vWx8AWDSGLpnpDQRtWAfrjU+EuytuODrFBkqesN2RkOQCBzrA1RQNHw1SmRMSDDDSwzNAYQ6Rg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
@@ -11412,9 +11415,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/eslint-plugin-react-hooks": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.1.0.tgz",
|
||||
"integrity": "sha512-mpJRtPgHN2tNAvZ35AMfqeB3Xqeo273QxrHJsbBEPWODRM4r0yB6jfoROqKEYrOn27UtRPpcpHc2UqyBSuUNTw==",
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-5.2.0.tgz",
|
||||
"integrity": "sha512-+f15FfK64YQwZdJNELETdn5ibXEUQmW1DZL6KXhNnc2heoy/sg9VJJeT7n8TlMWouzWqSWavFkIhHyIbIAEapg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
@@ -14110,9 +14113,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/maplibre-gl": {
|
||||
"version": "5.1.1",
|
||||
"resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.1.1.tgz",
|
||||
"integrity": "sha512-0Z6ODzyFu/grwT6K1eIBpv6MZE4xnJD1AV+Yq1hPzOh/YCY36r9BlSaU7d7n2/HJOaoKOy0b2YF8cS4dD+iEVQ==",
|
||||
"version": "5.2.0",
|
||||
"resolved": "https://registry.npmjs.org/maplibre-gl/-/maplibre-gl-5.2.0.tgz",
|
||||
"integrity": "sha512-9zZKD0M80qtDsqBet+EDuAhoCeA/cnAuZAA0p3hcGKGbyjM/SH+R6wQvnBEgvJz9UhDynnkoKdUwhI+fUkHoXQ==",
|
||||
"license": "BSD-3-Clause",
|
||||
"dependencies": {
|
||||
"@mapbox/geojson-rewind": "^0.5.2",
|
||||
@@ -14845,19 +14848,20 @@
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/mui-color-input": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mui-color-input/-/mui-color-input-5.0.1.tgz",
|
||||
"integrity": "sha512-50Ws4vhg4UPQSZEZDCNc7vyUBSb9x1bK+bO1o0wxJvQYgeSyg2r7mYDlavpCh+ZvisgBL/98y0GVN6M9901JWg==",
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mui-color-input/-/mui-color-input-6.0.0.tgz",
|
||||
"integrity": "sha512-F9n/RqPLq7fnPWeF53/qssbIHvart7clpOfE+f+dsM+3ddfawwg98+PEhzv3cdAYlJLE9zfVViVTsp/ckUUd3g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@ctrl/tinycolor": "^4.1.0"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@emotion/react": "^11.5.0",
|
||||
"@emotion/styled": "^11.3.0",
|
||||
"@emotion/react": "^11.13.0",
|
||||
"@emotion/styled": "^11.13.0",
|
||||
"@mui/material": "^6.0.0",
|
||||
"@types/react": "^18.0.0",
|
||||
"react": "^18.0.0",
|
||||
"react-dom": "^18.0.0"
|
||||
"@types/react": "^18.0.0 || ^19.0.0",
|
||||
"react": "^18.0.0 || ^19.0.0",
|
||||
"react-dom": "^18.0.0 || ^19.0.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"@types/react": {
|
||||
@@ -15639,9 +15643,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/prettier": {
|
||||
"version": "3.5.2",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.2.tgz",
|
||||
"integrity": "sha512-lc6npv5PH7hVqozBR7lkBNOGXV9vMwROAPlumdBkX0wTbbzPu/U1hk5yL8p2pt4Xoc+2mkT8t/sow2YrV/M5qg==",
|
||||
"version": "3.5.3",
|
||||
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.5.3.tgz",
|
||||
"integrity": "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
|
||||
+7
-7
@@ -16,11 +16,11 @@
|
||||
"@fontsource/roboto": "^5.0.13",
|
||||
"@hello-pangea/dnd": "^18.0.0",
|
||||
"@mui/x-charts": "^7.5.1",
|
||||
"@mui/x-data-grid": "7.27.2",
|
||||
"@mui/x-date-pickers": "7.27.1",
|
||||
"@mui/icons-material": "6.4.6",
|
||||
"@mui/lab": "6.0.0-beta.29",
|
||||
"@mui/material": "6.4.6",
|
||||
"@mui/x-data-grid": "7.27.3",
|
||||
"@mui/x-date-pickers": "7.27.3",
|
||||
"@mui/icons-material": "6.4.7",
|
||||
"@mui/lab": "6.0.0-beta.30",
|
||||
"@mui/material": "6.4.7",
|
||||
"@reduxjs/toolkit": "2.6.0",
|
||||
"@solana/wallet-adapter-base": "0.9.23",
|
||||
"@solana/wallet-adapter-material-ui": "0.16.34",
|
||||
@@ -36,8 +36,8 @@
|
||||
"i18next": "^24.2.2",
|
||||
"immutability-helper": "^3.1.1",
|
||||
"joi": "17.13.3",
|
||||
"maplibre-gl": "5.1.1",
|
||||
"mui-color-input": "^5.0.1",
|
||||
"maplibre-gl": "5.2.0",
|
||||
"mui-color-input": "^6.0.0",
|
||||
"react": "18.3.1",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-dnd-html5-backend": "^16.0.1",
|
||||
|
||||
+13
-2
@@ -12,17 +12,28 @@ import { logger } from "./Utils/Logger"; // Import the logger
|
||||
import { networkService } from "./main";
|
||||
import { Routes } from "./Routes";
|
||||
import WalletProvider from "./Components/WalletProvider";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { setLanguage } from "./Features/UI/uiSlice";
|
||||
|
||||
function App() {
|
||||
const mode = useSelector((state) => state.ui.mode);
|
||||
const { authToken } = useSelector((state) => state.auth);
|
||||
const dispatch = useDispatch();
|
||||
const { i18n } = useTranslation();
|
||||
|
||||
useEffect(() => {
|
||||
if (authToken) {
|
||||
dispatch(getAppSettings({ authToken }));
|
||||
dispatch(getAppSettings({ authToken })).then((action) => {
|
||||
if (action.payload && action.payload.success) {
|
||||
const { language } = action.payload.data;
|
||||
if (language) {
|
||||
dispatch(setLanguage(language));
|
||||
i18n.changeLanguage(language);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}, [dispatch, authToken]);
|
||||
}, [dispatch, authToken, i18n]);
|
||||
|
||||
// Cleanup
|
||||
useEffect(() => {
|
||||
|
||||
@@ -36,8 +36,8 @@ const Alert = ({ variant, title, body, isToast, hasIcon = true, onClick }) => {
|
||||
*/
|
||||
|
||||
const text = theme.palette.secondary.contrastText;
|
||||
const bg = theme.palette.secondary.main;
|
||||
const border = theme.palette.secondary.contrastText;
|
||||
const border = theme.palette.alert.contrastText;
|
||||
const bg = theme.palette.alert.main;
|
||||
const icon = icons[variant];
|
||||
|
||||
return (
|
||||
|
||||
@@ -62,6 +62,7 @@ const CustomGauge = ({ progress = 0, radius = 70, strokeWidth = 15, threshold =
|
||||
className="radial-chart"
|
||||
width={radius}
|
||||
height={radius}
|
||||
sx={{ backgroundColor: theme.palette.primary.main, borderRadius: "50%" }}
|
||||
>
|
||||
<svg
|
||||
viewBox={`0 0 ${totalSize} ${totalSize}`}
|
||||
@@ -70,7 +71,7 @@ const CustomGauge = ({ progress = 0, radius = 70, strokeWidth = 15, threshold =
|
||||
>
|
||||
<circle
|
||||
className="radial-chart-base"
|
||||
stroke={theme.palette.primary.lowContrast} // CAIO_REVIEW
|
||||
stroke={theme.palette.secondary.light} // CAIO_REVIEW
|
||||
strokeWidth={strokeWidth}
|
||||
fill="none"
|
||||
cx={totalSize / 2} // Center the circle
|
||||
@@ -98,8 +99,8 @@ const CustomGauge = ({ progress = 0, radius = 70, strokeWidth = 15, threshold =
|
||||
top: "50%",
|
||||
left: "50%",
|
||||
transform: "translate(-50%, -50%)",
|
||||
...theme.typography.body2,
|
||||
fill: theme.typography.body2.color,
|
||||
...theme.typography.h2,
|
||||
fill: theme.typography.h2.color,
|
||||
}}
|
||||
>
|
||||
{`${progressWithinRange.toFixed(1)}%`}
|
||||
|
||||
@@ -3,6 +3,45 @@ import { Box, Stack, Tooltip, Typography } from "@mui/material";
|
||||
import { formatDateWithTz } from "../../../Utils/timeUtils";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
/**
|
||||
* A customizable Bar component that renders a colored bar with optional children.
|
||||
* @component
|
||||
*
|
||||
* @param {string} width The width of the bar (e.g., "100px").
|
||||
* @param {string} height The height of the bar (e.g., "50px").
|
||||
* @param {string} backgroundColor The background color of the bar (e.g., "#FF5733").
|
||||
* @param {string} [borderRadius] Optional border radius for the bar (e.g., "8px").
|
||||
* @param {node} children The content to be rendered inside the bar.
|
||||
* @returns {JSX.Element} The Bar component.
|
||||
*/
|
||||
|
||||
const Bar = ({ width, height, backgroundColor, borderRadius, children }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Box
|
||||
position="relative"
|
||||
width={width}
|
||||
height={height}
|
||||
backgroundColor={backgroundColor}
|
||||
sx={{
|
||||
borderRadius: borderRadius || theme.spacing(1.5),
|
||||
}}
|
||||
>
|
||||
{children}
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
Bar.propTypes = {
|
||||
width: PropTypes.string.isRequired,
|
||||
height: PropTypes.string.isRequired,
|
||||
backgroundColor: PropTypes.string.isRequired,
|
||||
borderRadius: PropTypes.string,
|
||||
children: PropTypes.node,
|
||||
};
|
||||
|
||||
/* TODO add prop validation and jsdocs */
|
||||
const StatusPageBarChart = ({ checks = [] }) => {
|
||||
@@ -10,6 +49,11 @@ const StatusPageBarChart = ({ checks = [] }) => {
|
||||
const [animate, setAnimate] = useState(false);
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
|
||||
const barWidth = {
|
||||
xs: "calc(60% / 25)",
|
||||
xl: "calc(40% / 25)",
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setAnimate(true);
|
||||
}, []);
|
||||
@@ -41,15 +85,11 @@ const StatusPageBarChart = ({ checks = [] }) => {
|
||||
/* TODO what is the purpose of this box? */
|
||||
// CAIO_REVIEW the purpose of this box is to make sure there are always at least 25 bars
|
||||
// even if there are less than 25 checks
|
||||
<Box
|
||||
<Bar
|
||||
key={`${check}-${index}`}
|
||||
position="relative"
|
||||
width="calc(30vw / 25)"
|
||||
width={barWidth}
|
||||
height="100%"
|
||||
backgroundColor={theme.palette.primary.lowContrast}
|
||||
sx={{
|
||||
borderRadius: theme.spacing(1.5),
|
||||
}}
|
||||
/>
|
||||
) : (
|
||||
<Tooltip
|
||||
@@ -140,14 +180,10 @@ const StatusPageBarChart = ({ checks = [] }) => {
|
||||
},
|
||||
}}
|
||||
>
|
||||
<Box
|
||||
position="relative"
|
||||
width="calc(30vw / 25)"
|
||||
<Bar
|
||||
width={barWidth}
|
||||
height="100%"
|
||||
backgroundColor={theme.palette.primary.lowContrast} // CAIO_REVIEW
|
||||
sx={{
|
||||
borderRadius: theme.spacing(1.5),
|
||||
}}
|
||||
backgroundColor={theme.palette.primary.lowContrast}
|
||||
>
|
||||
<Box
|
||||
position="absolute"
|
||||
@@ -164,7 +200,7 @@ const StatusPageBarChart = ({ checks = [] }) => {
|
||||
transition: "height 600ms cubic-bezier(0.4, 0, 0.2, 1)",
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
</Bar>
|
||||
</Tooltip>
|
||||
)
|
||||
)}
|
||||
|
||||
@@ -13,6 +13,7 @@ export const tooltipDateFormatLookup = (dateRange) => {
|
||||
|
||||
export const tickDateFormatLookup = (dateRange) => {
|
||||
const tickFormatLookup = {
|
||||
recent: "h:mm A",
|
||||
day: "h:mm A",
|
||||
week: "MM/D, h:mm A",
|
||||
month: "ddd. M/D",
|
||||
|
||||
@@ -18,6 +18,7 @@ import "./index.css";
|
||||
* @param {string} props.items.name - The display name of each item.
|
||||
* @param {function} props.onChange - The function to handle onChange event.
|
||||
* @param {object} props.sx - The custom styles object for MUI Select component.
|
||||
* @param {number} props.maxWidth - Maximum width in pixels for the select component. Enables responsive text truncation.
|
||||
* @returns {JSX.Element}
|
||||
*
|
||||
* @example
|
||||
@@ -50,6 +51,7 @@ const Select = ({
|
||||
sx,
|
||||
name = "",
|
||||
labelControlSpacing = 2,
|
||||
maxWidth,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const itemStyles = {
|
||||
@@ -59,6 +61,13 @@ const Select = ({
|
||||
margin: theme.spacing(2),
|
||||
};
|
||||
|
||||
const responsiveMaxWidth = {
|
||||
xs: `${maxWidth * 0.5}px`,
|
||||
sm: `${maxWidth * 0.75}px`,
|
||||
md: `${maxWidth * 0.9}px`,
|
||||
lg: `${maxWidth}px`,
|
||||
};
|
||||
|
||||
return (
|
||||
<Stack
|
||||
gap={theme.spacing(labelControlSpacing)}
|
||||
@@ -87,6 +96,7 @@ const Select = ({
|
||||
sx={{
|
||||
fontSize: 13,
|
||||
minWidth: "125px",
|
||||
...(maxWidth && { maxWidth: responsiveMaxWidth }),
|
||||
"& fieldset": {
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
@@ -99,6 +109,22 @@ const Select = ({
|
||||
},
|
||||
...sx,
|
||||
}}
|
||||
renderValue={(selected) => {
|
||||
const selectedItem = items.find((item) => item._id === selected);
|
||||
const displayName = selectedItem ? selectedItem.name : placeholder;
|
||||
return (
|
||||
<Typography
|
||||
sx={{
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
title={displayName}
|
||||
>
|
||||
{displayName}
|
||||
</Typography>
|
||||
);
|
||||
}}
|
||||
>
|
||||
{placeholder && (
|
||||
<MenuItem
|
||||
@@ -146,6 +172,11 @@ Select.propTypes = {
|
||||
onBlur: PropTypes.func,
|
||||
sx: PropTypes.object,
|
||||
labelControlSpacing: PropTypes.number,
|
||||
/**
|
||||
* Maximum width in pixels. Used to control text truncation and element width.
|
||||
* Responsive breakpoints will be calculated as percentages of this value.
|
||||
*/
|
||||
maxWidth: PropTypes.number,
|
||||
};
|
||||
|
||||
export default Select;
|
||||
@@ -14,7 +14,6 @@ const LanguageSelector = () => {
|
||||
i18n.changeLanguage(newLang);
|
||||
};
|
||||
|
||||
// i18n instance'ından mevcut dilleri al
|
||||
const languages = Object.keys(i18n.options.resources || {});
|
||||
|
||||
return (
|
||||
|
||||
@@ -20,6 +20,13 @@ const MonitorTimeFrameHeader = ({
|
||||
if (hasDateRange) {
|
||||
timeFramePicker = (
|
||||
<ButtonGroup sx={{ height: 32 }}>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "recent").toString()}
|
||||
onClick={() => setDateRange("recent")}
|
||||
>
|
||||
Recent
|
||||
</Button>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "day").toString()}
|
||||
@@ -55,7 +62,14 @@ const MonitorTimeFrameHeader = ({
|
||||
>
|
||||
<Typography variant="body2">
|
||||
Showing statistics for past{" "}
|
||||
{dateRange === "day" ? "24 hours" : dateRange === "week" ? "7 days" : "30 days"}.
|
||||
{dateRange === "recent"
|
||||
? "2 hours"
|
||||
: dateRange === "day"
|
||||
? "24 hours"
|
||||
: dateRange === "week"
|
||||
? "7 days"
|
||||
: "30 days"}
|
||||
.
|
||||
</Typography>
|
||||
{timeFramePicker}
|
||||
</Stack>
|
||||
|
||||
@@ -65,34 +65,28 @@ const menu = [
|
||||
{ name: "Status pages", path: "status", icon: <StatusPages /> },
|
||||
{ name: "Maintenance", path: "maintenance", icon: <Maintenance /> },
|
||||
// { name: "Integrations", path: "integrations", icon: <Integrations /> },
|
||||
{
|
||||
name: "Account",
|
||||
icon: <Account />,
|
||||
nested: [
|
||||
{ name: "Profile", path: "account/profile", icon: <UserSvg /> },
|
||||
{ name: "Password", path: "account/password", icon: <LockSvg /> },
|
||||
{ name: "Team", path: "account/team", icon: <TeamSvg /> },
|
||||
],
|
||||
},
|
||||
{
|
||||
name: "Settings",
|
||||
icon: <Settings />,
|
||||
path: "settings",
|
||||
},
|
||||
];
|
||||
|
||||
const otherMenuItems = [
|
||||
{ name: "Support", path: "support", icon: <Support /> },
|
||||
{
|
||||
name: "Other",
|
||||
icon: <Folder />,
|
||||
nested: [
|
||||
{ name: "Support", path: "support", icon: <Support /> },
|
||||
{
|
||||
name: "Discussions",
|
||||
path: "discussions",
|
||||
icon: <Discussions />,
|
||||
},
|
||||
{ name: "Docs", path: "docs", icon: <Docs /> },
|
||||
{ name: "Changelog", path: "changelog", icon: <ChangeLog /> },
|
||||
],
|
||||
name: "Discussions",
|
||||
path: "discussions",
|
||||
icon: <Discussions />,
|
||||
},
|
||||
{ name: "Docs", path: "docs", icon: <Docs /> },
|
||||
{ name: "Changelog", path: "changelog", icon: <ChangeLog /> },
|
||||
];
|
||||
|
||||
const accountMenuItems = [
|
||||
{ name: "Profile", path: "account/profile", icon: <UserSvg /> },
|
||||
{ name: "Password", path: "account/password", icon: <LockSvg /> },
|
||||
{ name: "Team", path: "account/team", icon: <TeamSvg /> },
|
||||
];
|
||||
|
||||
/* TODO this could be a key in nested Path would be the link */
|
||||
@@ -134,12 +128,38 @@ function Sidebar() {
|
||||
(state) => state.ui.distributedUptimeEnabled
|
||||
);
|
||||
|
||||
const accountMenuItem = menu.find((item) => item.name === "Account");
|
||||
if (user.role?.includes("demo") && accountMenuItem) {
|
||||
accountMenuItem.nested = accountMenuItem.nested.filter((item) => {
|
||||
return item.name !== "Password";
|
||||
});
|
||||
}
|
||||
const renderAccountMenuItems = () => {
|
||||
let filteredAccountMenuItems = [...accountMenuItems];
|
||||
|
||||
// If the user is in demo mode, remove the "Password" option
|
||||
if (user.role?.includes("demo")) {
|
||||
filteredAccountMenuItems = filteredAccountMenuItems.filter(
|
||||
(item) => item.name !== "Password"
|
||||
);
|
||||
}
|
||||
|
||||
// If the user is NOT a superadmin, remove the "Team" option
|
||||
if (user.role && !user.role.includes("superadmin")) {
|
||||
filteredAccountMenuItems = filteredAccountMenuItems.filter(
|
||||
(item) => item.name !== "Team"
|
||||
);
|
||||
}
|
||||
|
||||
return filteredAccountMenuItems.map((item) => (
|
||||
<MenuItem
|
||||
key={item.name}
|
||||
onClick={() => navigate(item.path)}
|
||||
sx={{
|
||||
gap: theme.spacing(2),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
pl: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
{item.icon}
|
||||
{item.name}
|
||||
</MenuItem>
|
||||
));
|
||||
};
|
||||
|
||||
const openPopup = (event, id) => {
|
||||
setAnchorEl(event.currentTarget);
|
||||
@@ -261,7 +281,6 @@ function Sidebar() {
|
||||
>
|
||||
{collapsed ? <ArrowRight /> : <ArrowLeft />}
|
||||
</IconButton>
|
||||
|
||||
{/* TODO Alignment done using padding. Use single source of truth to that*/}
|
||||
<Stack
|
||||
pt={theme.spacing(6)}
|
||||
@@ -589,10 +608,61 @@ function Sidebar() {
|
||||
})}
|
||||
</List>
|
||||
</Box>
|
||||
<Divider sx={{ mt: "auto", borderColor: theme.palette.primary.lowContrast }} />
|
||||
|
||||
{!collapsed && <StarPrompt />}
|
||||
|
||||
|
||||
<List
|
||||
component="nav"
|
||||
disablePadding
|
||||
sx={{ px: theme.spacing(6) }}
|
||||
>
|
||||
{otherMenuItems.map((item) => {
|
||||
return item.path ? (
|
||||
<Tooltip
|
||||
key={item.path}
|
||||
placement="right"
|
||||
title={collapsed ? item.name : ""}
|
||||
slotProps={{
|
||||
popper: {
|
||||
modifiers: [
|
||||
{
|
||||
name: "offset",
|
||||
options: {
|
||||
offset: [0, -16],
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
}}
|
||||
disableInteractive
|
||||
>
|
||||
<ListItemButton
|
||||
className={
|
||||
location.pathname.startsWith(`/${item.path}`) ? "selected-path" : ""
|
||||
}
|
||||
onClick={() => {
|
||||
const url = URL_MAP[item.path];
|
||||
if (url) {
|
||||
window.open(url, "_blank", "noreferrer");
|
||||
} else {
|
||||
navigate(`/${item.path}`);
|
||||
}
|
||||
}}
|
||||
sx={{
|
||||
height: "37px",
|
||||
gap: theme.spacing(4),
|
||||
borderRadius: theme.shape.borderRadius,
|
||||
px: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
<ListItemIcon sx={{ minWidth: 0 }}>{item.icon} </ListItemIcon>
|
||||
<ListItemText>{item.name} </ListItemText>{" "}
|
||||
</ListItemButton>
|
||||
</Tooltip>
|
||||
) : null;
|
||||
})}
|
||||
</List>
|
||||
<Divider sx={{ mt: "auto", borderColor: theme.palette.primary.lowContrast }} />
|
||||
<Stack
|
||||
direction="row"
|
||||
height="50px"
|
||||
@@ -714,14 +784,19 @@ function Sidebar() {
|
||||
},
|
||||
}}
|
||||
sx={{
|
||||
ml: theme.spacing(8),
|
||||
ml: theme.spacing(4),
|
||||
}}
|
||||
>
|
||||
{collapsed && (
|
||||
<MenuItem sx={{ cursor: "default", minWidth: "50%" }}>
|
||||
<Box
|
||||
mb={theme.spacing(2)}
|
||||
sx={{ maxWidth: "50%", overflow: "hidden" }}
|
||||
sx={{
|
||||
minWidth: "50%",
|
||||
maxWidth: "max-content",
|
||||
overflow: "visible",
|
||||
whiteSpace: "nowrap",
|
||||
}}
|
||||
>
|
||||
<Typography
|
||||
component="span"
|
||||
@@ -730,13 +805,22 @@ function Sidebar() {
|
||||
sx={{
|
||||
display: "block",
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "hidden",
|
||||
textOverflow: "ellipsis",
|
||||
overflow: "visible",
|
||||
// wordBreak: "break-word",
|
||||
textOverflow: "clip",
|
||||
}}
|
||||
>
|
||||
{authState.user?.firstName} {authState.user?.lastName}
|
||||
</Typography>
|
||||
<Typography sx={{ textTransform: "capitalize", fontSize: 12 }}>
|
||||
<Typography
|
||||
sx={{
|
||||
textTransform: "capitalize",
|
||||
fontSize: 12,
|
||||
whiteSpace: "nowrap",
|
||||
overflow: "visible",
|
||||
// wordBreak: "break-word",
|
||||
}}
|
||||
>
|
||||
{authState.user?.role}
|
||||
</Typography>
|
||||
</Box>
|
||||
@@ -744,7 +828,8 @@ function Sidebar() {
|
||||
)}
|
||||
{/* TODO Do we need two dividers? */}
|
||||
{collapsed && <Divider />}
|
||||
<Divider />
|
||||
{/* <Divider /> */}
|
||||
{renderAccountMenuItems()}
|
||||
<MenuItem
|
||||
onClick={logout}
|
||||
sx={{
|
||||
|
||||
@@ -50,7 +50,7 @@ const StatBox = ({
|
||||
borderColor: theme.palette[themeColor].lowContrast,
|
||||
}
|
||||
: {
|
||||
background: `linear-gradient(340deg, ${theme.palette.tertiary.main} 20%, ${theme.palette.primary.main} 45%)`,
|
||||
background: `linear-gradient(340deg, ${theme.palette.tertiary.main} 10%, ${theme.palette.primary.main} 45%)`,
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
};
|
||||
|
||||
@@ -62,7 +62,7 @@ const StatBox = ({
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
};
|
||||
|
||||
const spanFixedStyles = { marginLeft: theme.spacing(2), fontSize: 15 };
|
||||
const spanFixedStyles = { marginLeft: theme.spacing(2), fontSize: 15, fontWeight: 600};
|
||||
const detailTextStyles = gradient
|
||||
? {
|
||||
color: theme.palette[themeColor].contrastText,
|
||||
@@ -120,7 +120,7 @@ const StatBox = ({
|
||||
)}
|
||||
<Stack>
|
||||
<Typography component="h2">{heading}</Typography>
|
||||
<Typography>{subHeading}</Typography>
|
||||
<Typography sx={{fontWeight: 500}}>{subHeading}</Typography>
|
||||
</Stack>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
@@ -5,6 +5,7 @@ const initialState = {
|
||||
isLoading: false,
|
||||
apiBaseUrl: "",
|
||||
logLevel: "debug",
|
||||
language: "",
|
||||
};
|
||||
|
||||
export const getAppSettings = createAsyncThunk(
|
||||
@@ -34,6 +35,7 @@ export const updateAppSettings = createAsyncThunk(
|
||||
const parsedSettings = {
|
||||
apiBaseUrl: settings.apiBaseUrl,
|
||||
logLevel: settings.logLevel,
|
||||
language: settings.language,
|
||||
clientHost: settings.clientHost,
|
||||
jwtSecret: settings.jwtSecret,
|
||||
dbType: settings.dbType,
|
||||
@@ -68,6 +70,7 @@ const handleGetSettingsFulfilled = (state, action) => {
|
||||
state.msg = action.payload.msg;
|
||||
state.apiBaseUrl = action.payload.data.apiBaseUrl;
|
||||
state.logLevel = action.payload.data.logLevel;
|
||||
state.language = action.payload.data.language;
|
||||
};
|
||||
const handleGetSettingsRejected = (state, action) => {
|
||||
state.isLoading = false;
|
||||
|
||||
@@ -53,8 +53,8 @@ const uiSlice = createSlice({
|
||||
state.timezone = action.payload.timezone;
|
||||
},
|
||||
setLanguage(state, action) {
|
||||
state.language = action.payload;
|
||||
},
|
||||
state.language = action.payload;
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
|
||||
@@ -57,10 +57,6 @@ const Account = ({ open = "profile" }) => {
|
||||
className="account"
|
||||
px={theme.spacing(20)}
|
||||
py={theme.spacing(12)}
|
||||
backgroundColor={theme.palette.primary.main}
|
||||
border={1}
|
||||
borderColor={theme.palette.primary.lowContrast}
|
||||
borderRadius={theme.shape.borderRadius}
|
||||
>
|
||||
<TabContext value={tab}>
|
||||
<Box
|
||||
|
||||
@@ -5,16 +5,14 @@ import "flag-icons/css/flag-icons.min.css";
|
||||
import { ColContainer } from "../../../../../Components/StandardContainer";
|
||||
|
||||
// Utils
|
||||
import { useMediaQuery } from "@mui/material";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import { safelyParseFloat } from "../../../../../Utils/utils";
|
||||
const DeviceTicker = ({ data, width = "100%", connectionStatus }) => {
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
|
||||
const statusColor = {
|
||||
up: theme.palette.success.main,
|
||||
down: theme.palette.error.main,
|
||||
undefined: theme.palette.warning.main,
|
||||
};
|
||||
|
||||
return (
|
||||
@@ -30,7 +28,7 @@ const DeviceTicker = ({ data, width = "100%", connectionStatus }) => {
|
||||
mb={theme.spacing(8)}
|
||||
sx={{ alignSelf: "center" }}
|
||||
>
|
||||
{connectionStatus === "up" ? "Connected" : "No connection"}
|
||||
{connectionStatus === "up" ? "Connected" : "Connecting..."}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<div
|
||||
|
||||
@@ -75,7 +75,7 @@ const DistributedUptimeMap = ({
|
||||
type: "Feature",
|
||||
geometry: {
|
||||
type: "Point",
|
||||
coordinates: [check.lng, check.lat],
|
||||
coordinates: [check._id.lng, check._id.lat],
|
||||
},
|
||||
properties: {
|
||||
color: theme.palette.accent.main,
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ const DistributedUptimeResponseAreaChart = ({ checks }) => {
|
||||
</defs>
|
||||
<XAxis
|
||||
stroke={theme.palette.primary.lowContrast}
|
||||
dataKey="_id"
|
||||
dataKey="_id.date"
|
||||
tick={<CustomTick />}
|
||||
minTickGap={0}
|
||||
axisLine={false}
|
||||
|
||||
+1
-1
@@ -67,7 +67,7 @@ const DistributedUptimeResponseBarChart = ({ checks }) => {
|
||||
/>
|
||||
<XAxis
|
||||
stroke={theme.palette.primary.lowContrast}
|
||||
dataKey="_id"
|
||||
dataKey="_id.date"
|
||||
tick={<CustomTick />}
|
||||
minTickGap={0}
|
||||
axisLine={false}
|
||||
|
||||
+1
-1
@@ -75,7 +75,7 @@ CustomToolTip.propTypes = {
|
||||
PropTypes.shape({
|
||||
value: PropTypes.number,
|
||||
payload: PropTypes.shape({
|
||||
_id: PropTypes.string,
|
||||
_id: PropTypes.oneOfType([PropTypes.string, PropTypes.object]),
|
||||
avgResponseTime: PropTypes.number,
|
||||
originalAvgResponseTime: PropTypes.number,
|
||||
}),
|
||||
|
||||
@@ -9,6 +9,11 @@ import PropTypes from "prop-types";
|
||||
|
||||
const StatBoxes = ({ monitor, lastUpdateTrigger }) => {
|
||||
const theme = useTheme();
|
||||
let timeSinceLastCheck = 0;
|
||||
|
||||
if (monitor?.lastCheckTimestamp) {
|
||||
timeSinceLastCheck = Date.now() - monitor?.lastCheckTimestamp;
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
@@ -32,7 +37,7 @@ const StatBoxes = ({ monitor, lastUpdateTrigger }) => {
|
||||
heading={"Last check"}
|
||||
subHeading={
|
||||
<LastUpdate
|
||||
lastUpdateTime={monitor?.timeSinceLastCheck ?? 0}
|
||||
lastUpdateTime={timeSinceLastCheck}
|
||||
suffix={"seconds ago"}
|
||||
/>
|
||||
}
|
||||
|
||||
@@ -1,50 +1,7 @@
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useState, useEffect } from "react";
|
||||
import { networkService } from "../../../../main";
|
||||
import { useSelector } from "react-redux";
|
||||
import { createToast } from "../../../../Utils/toastUtils";
|
||||
|
||||
const getRandomDevice = () => {
|
||||
const manufacturers = {
|
||||
Apple: ["iPhone 15 Pro Max", "iPhone 15", "iPhone 14 Pro", "iPhone 14", "iPhone 13"],
|
||||
Samsung: [
|
||||
"Galaxy S23 Ultra",
|
||||
"Galaxy S23+",
|
||||
"Galaxy S23",
|
||||
"Galaxy Z Fold5",
|
||||
"Galaxy Z Flip5",
|
||||
],
|
||||
Google: ["Pixel 8 Pro", "Pixel 8", "Pixel 7a", "Pixel 7", "Pixel 6a"],
|
||||
OnePlus: [
|
||||
"OnePlus 11",
|
||||
"OnePlus 10T",
|
||||
"OnePlus Nord 3",
|
||||
"OnePlus 10 Pro",
|
||||
"OnePlus Nord 2T",
|
||||
],
|
||||
Xiaomi: ["13 Pro", "13", "Redmi Note 12", "POCO F5", "Redmi 12"],
|
||||
Huawei: ["P60 Pro", "Mate X3", "Nova 11", "P50 Pro", "Mate 50"],
|
||||
Sony: ["Xperia 1 V", "Xperia 5 V", "Xperia 10 V", "Xperia Pro-I", "Xperia 1 IV"],
|
||||
Motorola: ["Edge 40 Pro", "Edge 40", "G84", "G54", "Razr 40 Ultra"],
|
||||
ASUS: [
|
||||
"ROG Phone 7",
|
||||
"Zenfone 10",
|
||||
"ROG Phone 6",
|
||||
"Zenfone 9",
|
||||
"ROG Phone 7 Ultimate",
|
||||
],
|
||||
};
|
||||
|
||||
const manufacturerNames = Object.keys(manufacturers);
|
||||
const randomManufacturer =
|
||||
manufacturerNames[Math.floor(Math.random() * manufacturerNames.length)];
|
||||
|
||||
const models = manufacturers[randomManufacturer];
|
||||
const randomModel = models[Math.floor(Math.random() * models.length)];
|
||||
|
||||
return {
|
||||
manufacturer: randomManufacturer,
|
||||
model: randomModel,
|
||||
};
|
||||
};
|
||||
const useSubscribeToDetails = ({ monitorId, isPublic, isPublished, dateRange }) => {
|
||||
const [isLoading, setIsLoading] = useState(true);
|
||||
const [connectionStatus, setConnectionStatus] = useState(undefined);
|
||||
@@ -52,9 +9,6 @@ const useSubscribeToDetails = ({ monitorId, isPublic, isPublished, dateRange })
|
||||
const [networkError, setNetworkError] = useState(false);
|
||||
const [monitor, setMonitor] = useState(undefined);
|
||||
const [lastUpdateTrigger, setLastUpdateTrigger] = useState(0);
|
||||
const [devices, setDevices] = useState(Array.from({ length: 5 }, getRandomDevice));
|
||||
|
||||
const prevDateRangeRef = useRef(dateRange);
|
||||
|
||||
useEffect(() => {
|
||||
if (typeof monitorId === "undefined") {
|
||||
@@ -65,6 +19,36 @@ const useSubscribeToDetails = ({ monitorId, isPublic, isPublished, dateRange })
|
||||
return;
|
||||
}
|
||||
|
||||
// Get initial data
|
||||
|
||||
const fetchInitialData = async () => {
|
||||
try {
|
||||
const res = await networkService.getDistributedUptimeDetails({
|
||||
monitorId,
|
||||
dateRange: dateRange,
|
||||
normalize: true,
|
||||
});
|
||||
const responseData = res?.data?.data;
|
||||
|
||||
if (typeof responseData === "undefined") {
|
||||
throw new Error("No data");
|
||||
}
|
||||
setConnectionStatus("up");
|
||||
setLastUpdateTrigger(Date.now());
|
||||
setMonitor(responseData);
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
setConnectionStatus("down");
|
||||
createToast({
|
||||
body: error.message,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchInitialData();
|
||||
|
||||
try {
|
||||
const cleanup = networkService.subscribeToDistributedUptimeDetails({
|
||||
monitorId,
|
||||
@@ -78,15 +62,7 @@ const useSubscribeToDetails = ({ monitorId, isPublic, isPublished, dateRange })
|
||||
setNetworkError(false);
|
||||
}
|
||||
setLastUpdateTrigger(Date.now());
|
||||
const latestChecksWithDevice = data?.monitor?.latestChecks.map((check, idx) => {
|
||||
check.device = devices[idx];
|
||||
return check;
|
||||
});
|
||||
const monitorWithDevice = {
|
||||
...data.monitor,
|
||||
latestChecks: latestChecksWithDevice,
|
||||
};
|
||||
setMonitor(monitorWithDevice);
|
||||
setMonitor(data.monitor);
|
||||
},
|
||||
onOpen: () => {
|
||||
setConnectionStatus("up");
|
||||
@@ -108,21 +84,11 @@ const useSubscribeToDetails = ({ monitorId, isPublic, isPublished, dateRange })
|
||||
retryCount,
|
||||
setConnectionStatus,
|
||||
networkError,
|
||||
devices,
|
||||
isLoading,
|
||||
isPublic,
|
||||
isPublished,
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const hasDateRangeChanged = prevDateRangeRef.current !== dateRange;
|
||||
prevDateRangeRef.current = dateRange; // Update the ref to the current dateRange
|
||||
|
||||
if (!hasDateRangeChanged) {
|
||||
setDevices(Array.from({ length: 5 }, getRandomDevice));
|
||||
}
|
||||
}, [dateRange]);
|
||||
|
||||
return [isLoading, networkError, connectionStatus, monitor, lastUpdateTrigger];
|
||||
};
|
||||
|
||||
|
||||
@@ -26,7 +26,7 @@ import { useNavigate } from "react-router-dom";
|
||||
const DistributedUptimeDetails = () => {
|
||||
const { monitorId } = useParams();
|
||||
// Local State
|
||||
const [dateRange, setDateRange] = useState("day");
|
||||
const [dateRange, setDateRange] = useState("recent");
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
|
||||
// Utils
|
||||
@@ -75,7 +75,6 @@ const DistributedUptimeDetails = () => {
|
||||
</Stack>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack
|
||||
direction="column"
|
||||
|
||||
@@ -20,7 +20,6 @@ const MonitorTable = ({ isLoading, monitors }) => {
|
||||
const theme = useTheme();
|
||||
const navigate = useNavigate();
|
||||
const { determineState } = useMonitorUtils();
|
||||
|
||||
const headers = [
|
||||
{
|
||||
id: "name",
|
||||
|
||||
@@ -5,7 +5,7 @@ import { useTheme } from "@emotion/react";
|
||||
import { useMonitorUtils } from "../../../../Hooks/useMonitorUtils";
|
||||
import { createToast } from "../../../../Utils/toastUtils";
|
||||
|
||||
const useSubscribeToMonitors = () => {
|
||||
const useSubscribeToMonitors = (page, rowsPerPage) => {
|
||||
// Redux
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
|
||||
@@ -20,13 +20,50 @@ const useSubscribeToMonitors = () => {
|
||||
const { getMonitorWithPercentage } = useMonitorUtils();
|
||||
|
||||
useEffect(() => {
|
||||
const fetchInitialData = async () => {
|
||||
try {
|
||||
const initialDataRes = await networkService.getDistributedUptimeMonitors({
|
||||
teamId: user.teamId,
|
||||
limit: 25,
|
||||
types: ["distributed_http"],
|
||||
page,
|
||||
rowsPerPage,
|
||||
});
|
||||
const responseData = initialDataRes?.data?.data;
|
||||
if (typeof responseData === "undefined") throw new Error("No data");
|
||||
|
||||
const { monitors, filteredMonitors, summary } = responseData;
|
||||
|
||||
const mappedMonitors = filteredMonitors?.map((monitor) =>
|
||||
getMonitorWithPercentage(monitor, theme)
|
||||
);
|
||||
|
||||
setMonitors(monitors);
|
||||
setMonitorsSummary(summary);
|
||||
setFilteredMonitors(mappedMonitors);
|
||||
} catch (error) {
|
||||
setNetworkError(true);
|
||||
createToast({
|
||||
body: error.message,
|
||||
});
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
};
|
||||
|
||||
fetchInitialData();
|
||||
|
||||
try {
|
||||
const cleanup = networkService.subscribeToDistributedUptimeMonitors({
|
||||
teamId: user.teamId,
|
||||
limit: 25,
|
||||
types: ["distributed_http"],
|
||||
page: 0,
|
||||
rowsPerPage: 10,
|
||||
types: [
|
||||
typeof import.meta.env.VITE_DEPIN_TESTING === "undefined"
|
||||
? "distributed_http"
|
||||
: "distributed_test",
|
||||
],
|
||||
page,
|
||||
rowsPerPage,
|
||||
filter: null,
|
||||
field: null,
|
||||
order: null,
|
||||
@@ -56,7 +93,7 @@ const useSubscribeToMonitors = () => {
|
||||
});
|
||||
setNetworkError(true);
|
||||
}
|
||||
}, [user, getMonitorWithPercentage, theme, isLoading]);
|
||||
}, [user, getMonitorWithPercentage, theme, isLoading, page, rowsPerPage]);
|
||||
return [isLoading, networkError, monitors, monitorsSummary, filteredMonitors];
|
||||
};
|
||||
export { useSubscribeToMonitors };
|
||||
@@ -3,10 +3,12 @@ import { Stack, Typography } from "@mui/material";
|
||||
import Breadcrumbs from "../../../Components/Breadcrumbs";
|
||||
import CreateMonitorHeader from "../../../Components/MonitorCreateHeader";
|
||||
import MonitorTable from "./Components/MonitorTable";
|
||||
import Pagination from "../../../Components/Table/TablePagination";
|
||||
import Fallback from "../../../Components/Fallback";
|
||||
import GenericFallback from "../../../Components/GenericFallback";
|
||||
|
||||
// Utils
|
||||
import { useState } from "react";
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useIsAdmin } from "../../../Hooks/useIsAdmin";
|
||||
import { useSubscribeToMonitors } from "./Hooks/useSubscribeToMonitors";
|
||||
@@ -16,11 +18,23 @@ const BREADCRUMBS = [{ name: `Distributed Uptime`, path: "/distributed-uptime" }
|
||||
|
||||
const DistributedUptimeMonitors = () => {
|
||||
// Local state
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const isAdmin = useIsAdmin();
|
||||
const [isLoading, networkError, monitors, monitorsSummary, filteredMonitors] =
|
||||
useSubscribeToMonitors();
|
||||
useSubscribeToMonitors(page, rowsPerPage);
|
||||
// Handlers
|
||||
const handleChangePage = (event, newPage) => {
|
||||
setPage(newPage);
|
||||
};
|
||||
|
||||
const handleChangeRowsPerPage = (event) => {
|
||||
setRowsPerPage(event.target.value);
|
||||
setPage(0);
|
||||
};
|
||||
|
||||
if (isLoading) {
|
||||
return <SkeletonLayout />;
|
||||
@@ -69,6 +83,14 @@ const DistributedUptimeMonitors = () => {
|
||||
isLoading={isLoading}
|
||||
monitors={filteredMonitors}
|
||||
/>
|
||||
<Pagination
|
||||
itemCount={monitorsSummary?.totalMonitors ?? 0}
|
||||
paginationLabel="monitors"
|
||||
page={page}
|
||||
rowsPerPage={rowsPerPage}
|
||||
handleChangePage={handleChangePage}
|
||||
handleChangeRowsPerPage={handleChangeRowsPerPage}
|
||||
/>
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -21,7 +21,7 @@ import { RowContainer } from "../../../Components/StandardContainer";
|
||||
|
||||
//Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useState, useRef } from "react";
|
||||
import { useState, useEffect, useRef } from "react";
|
||||
import { useParams } from "react-router-dom";
|
||||
import { useSubscribeToDetails } from "../../DistributedUptime/Details/Hooks/useSubscribeToDetails";
|
||||
import { useDUStatusPageFetchByUrl } from "./Hooks/useDUStatusPageFetchByUrl";
|
||||
@@ -31,6 +31,8 @@ import SubHeader from "../../../Components/Subheader";
|
||||
import { safelyParseFloat } from "../../../Utils/utils";
|
||||
import { useNavigate, useLocation } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { setMode } from "../../../Features/UI/uiSlice";
|
||||
|
||||
// Responsive
|
||||
import { useMediaQuery } from "@mui/material";
|
||||
@@ -40,16 +42,21 @@ const DistributedUptimeStatus = () => {
|
||||
const location = useLocation();
|
||||
const { t } = useTranslation();
|
||||
const isPublic = location.pathname.startsWith("/status/distributed/public");
|
||||
|
||||
const elementToCapture = useRef(null);
|
||||
|
||||
// Redux state
|
||||
const mode = useSelector((state) => state.ui.mode);
|
||||
const originalModeRef = useRef(null);
|
||||
|
||||
// Local State
|
||||
const [dateRange, setDateRange] = useState("day");
|
||||
const [dateRange, setDateRange] = useState("recent");
|
||||
const [isDeleteOpen, setIsDeleteOpen] = useState(false);
|
||||
const [timeFrame, setTimeFrame] = useState(30);
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const isSmallScreen = useMediaQuery(theme.breakpoints.down("sm"));
|
||||
const navigate = useNavigate();
|
||||
const dispatch = useDispatch();
|
||||
|
||||
const [
|
||||
statusPageIsLoading,
|
||||
@@ -85,6 +92,28 @@ const DistributedUptimeStatus = () => {
|
||||
};
|
||||
}
|
||||
|
||||
// Default to dark mode
|
||||
useEffect(() => {
|
||||
const cleanup = () => {
|
||||
if (originalModeRef.current === null) {
|
||||
originalModeRef.current = mode;
|
||||
}
|
||||
|
||||
if (isPublic) {
|
||||
dispatch(setMode(originalModeRef.current));
|
||||
}
|
||||
};
|
||||
|
||||
if (isPublic) {
|
||||
dispatch(setMode("dark"));
|
||||
}
|
||||
|
||||
window.addEventListener("beforeunload", cleanup);
|
||||
return () => {
|
||||
window.removeEventListener("beforeunload", cleanup);
|
||||
};
|
||||
}, [dispatch, isPublic]);
|
||||
|
||||
// Done loading, a status page doesn't exist
|
||||
if (!statusPageIsLoading && typeof statusPage === "undefined") {
|
||||
return (
|
||||
@@ -158,9 +187,10 @@ const DistributedUptimeStatus = () => {
|
||||
}}
|
||||
>
|
||||
{!isPublic && <Breadcrumbs list={BREADCRUMBS} />}
|
||||
|
||||
<ControlsHeader
|
||||
shouldShow={!isPublic}
|
||||
statusPage={statusPage}
|
||||
isPublic={isPublic}
|
||||
isDeleting={isDeleting}
|
||||
isDeleteOpen={isDeleteOpen}
|
||||
setIsDeleteOpen={setIsDeleteOpen}
|
||||
@@ -224,7 +254,7 @@ const DistributedUptimeStatus = () => {
|
||||
/>
|
||||
<InfoBox
|
||||
heading={isSmallScreen ? "UPT" : "UPT Burned"}
|
||||
subHeading={safelyParseFloat(monitor?.totalUptBurnt).toFixed(4)}
|
||||
subHeading={safelyParseFloat(monitor?.uptBurnt).toFixed(4)}
|
||||
img={UptLogo}
|
||||
alt="Upt Logo"
|
||||
sx={{ width: "50%" }}
|
||||
|
||||
@@ -27,8 +27,11 @@ const IncidentTable = ({
|
||||
//Local state
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(10);
|
||||
const selectedMonitorDetails = monitors?.[selectedMonitor];
|
||||
const selectedMonitorType = selectedMonitorDetails?.type;
|
||||
const { isLoading, networkError, checks, checksCount } = useChecksFetch({
|
||||
selectedMonitor,
|
||||
selectedMonitorType,
|
||||
filter,
|
||||
dateRange,
|
||||
page,
|
||||
|
||||
@@ -20,6 +20,13 @@ const OptionsHeader = ({
|
||||
const theme = useTheme();
|
||||
const monitorNames = typeof monitors !== "undefined" ? Object.values(monitors) : [];
|
||||
|
||||
// The stacks below which are three in number have the same style so
|
||||
const stackStyles = {
|
||||
direction: "row",
|
||||
alignItems: "center",
|
||||
gap: theme.spacing(6),
|
||||
};
|
||||
|
||||
if (!shouldRender) return <SkeletonLayout />;
|
||||
|
||||
return (
|
||||
@@ -27,17 +34,13 @@ const OptionsHeader = ({
|
||||
direction="row"
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Stack {...stackStyles}>
|
||||
<Typography
|
||||
display="inline-block"
|
||||
component="h1"
|
||||
color={theme.palette.primary.contrastTextSecondary}
|
||||
>
|
||||
Incidents for
|
||||
Incidents for:
|
||||
</Typography>
|
||||
<Select
|
||||
id="incidents-select-monitor"
|
||||
@@ -49,13 +52,10 @@ const OptionsHeader = ({
|
||||
backgroundColor: theme.palette.primary.main,
|
||||
color: theme.palette.primary.contrastTextSecondary,
|
||||
}}
|
||||
maxWidth={250}
|
||||
/>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Stack {...stackStyles}>
|
||||
<Typography
|
||||
display="inline-block"
|
||||
component="h1"
|
||||
@@ -63,14 +63,7 @@ const OptionsHeader = ({
|
||||
>
|
||||
Filter by:
|
||||
</Typography>
|
||||
<ButtonGroup
|
||||
sx={{
|
||||
ml: "auto",
|
||||
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(filter === "all").toString()}
|
||||
@@ -94,11 +87,7 @@ const OptionsHeader = ({
|
||||
</Button>
|
||||
</ButtonGroup>
|
||||
</Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(6)}
|
||||
>
|
||||
<Stack {...stackStyles}>
|
||||
<Typography
|
||||
display="inline-block"
|
||||
component="h1"
|
||||
@@ -106,14 +95,7 @@ const OptionsHeader = ({
|
||||
>
|
||||
Show:
|
||||
</Typography>
|
||||
<ButtonGroup
|
||||
sx={{
|
||||
ml: "auto",
|
||||
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
},
|
||||
}}
|
||||
>
|
||||
<ButtonGroup>
|
||||
<Button
|
||||
variant="group"
|
||||
filled={(dateRange === "hour").toString()}
|
||||
|
||||
@@ -2,7 +2,7 @@ import { useState, useEffect } from "react";
|
||||
import { networkService } from "../../../main";
|
||||
import { createToast } from "../../../Utils/toastUtils";
|
||||
import { useSelector } from "react-redux";
|
||||
const useChecksFetch = ({ selectedMonitor, filter, dateRange, page, rowsPerPage }) => {
|
||||
const useChecksFetch = ({ selectedMonitor, selectedMonitorType, filter, dateRange, page, rowsPerPage }) => {
|
||||
//Redux
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
|
||||
@@ -33,6 +33,7 @@ const useChecksFetch = ({ selectedMonitor, filter, dateRange, page, rowsPerPage
|
||||
res = await networkService.getChecksByMonitor({
|
||||
status: false,
|
||||
monitorId: selectedMonitor,
|
||||
type: selectedMonitorType,
|
||||
sortOrder: "desc",
|
||||
limit: null,
|
||||
dateRange,
|
||||
@@ -51,7 +52,7 @@ const useChecksFetch = ({ selectedMonitor, filter, dateRange, page, rowsPerPage
|
||||
}
|
||||
};
|
||||
fetchChecks();
|
||||
}, [user, dateRange, page, rowsPerPage, filter, selectedMonitor]);
|
||||
}, [user, dateRange, page, rowsPerPage, filter, selectedMonitor, selectedMonitorType]);
|
||||
return { isLoading, networkError, checks, checksCount };
|
||||
};
|
||||
|
||||
|
||||
@@ -25,9 +25,13 @@ const useMonitorsFetch = ({ teamId }) => {
|
||||
field: null,
|
||||
order: null,
|
||||
});
|
||||
if (res?.data?.data?.monitors?.length > 0) {
|
||||
const monitorLookup = res.data.data.monitors.reduce((acc, monitor) => {
|
||||
acc[monitor._id] = monitor;
|
||||
if (res?.data?.data?.filteredMonitors?.length > 0) {
|
||||
const monitorLookup = res.data.data.filteredMonitors.reduce((acc, monitor) => {
|
||||
acc[monitor._id] = {
|
||||
_id: monitor._id,
|
||||
name: monitor.name,
|
||||
type: monitor.type
|
||||
};
|
||||
return acc;
|
||||
}, {});
|
||||
setMonitors(monitorLookup);
|
||||
|
||||
@@ -9,6 +9,16 @@ import PropTypes from "prop-types";
|
||||
const Gauge = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo }) => {
|
||||
const theme = useTheme();
|
||||
|
||||
const valueStyle = {
|
||||
borderRadius: theme.spacing(2),
|
||||
backgroundColor: theme.palette.tertiary.main,
|
||||
width: "40%",
|
||||
mb: theme.spacing(2),
|
||||
mt: theme.spacing(2),
|
||||
pr: theme.spacing(2),
|
||||
textAlign: 'right'
|
||||
}
|
||||
|
||||
return (
|
||||
<BaseContainer>
|
||||
<Stack
|
||||
@@ -16,12 +26,21 @@ const Gauge = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo }) =>
|
||||
gap={theme.spacing(2)}
|
||||
alignItems="center"
|
||||
>
|
||||
<CustomGauge
|
||||
progress={value}
|
||||
radius={100}
|
||||
color={theme.palette.primary.main}
|
||||
/>
|
||||
<Typography component="h2">{heading}</Typography>
|
||||
<Box
|
||||
sx={{
|
||||
display: "flex",
|
||||
flexDirection: "column",
|
||||
alignItems: "center",
|
||||
width: "100%",
|
||||
backgroundColor: theme.palette.gradient.color1
|
||||
}}
|
||||
>
|
||||
<CustomGauge
|
||||
progress={value}
|
||||
radius={100}
|
||||
/>
|
||||
<Typography component="h2" sx={{ fontWeight: 600 }}>{heading}</Typography>
|
||||
</Box>
|
||||
<Box
|
||||
sx={{
|
||||
width: "100%",
|
||||
@@ -31,18 +50,20 @@ const Gauge = ({ value, heading, metricOne, valueOne, metricTwo, valueTwo }) =>
|
||||
<Stack
|
||||
justifyContent={"space-between"}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Typography>{metricOne}</Typography>
|
||||
<Typography>{valueOne}</Typography>
|
||||
<Typography sx={valueStyle}>{valueOne}</Typography>
|
||||
</Stack>
|
||||
<Stack
|
||||
justifyContent={"space-between"}
|
||||
direction="row"
|
||||
alignItems="center"
|
||||
gap={theme.spacing(2)}
|
||||
>
|
||||
<Typography>{metricTwo}</Typography>
|
||||
<Typography>{valueTwo}</Typography>
|
||||
<Typography sx={valueStyle}>{valueTwo}</Typography>
|
||||
</Stack>
|
||||
</Box>
|
||||
</Stack>
|
||||
|
||||
@@ -1,5 +1,4 @@
|
||||
import { useEffect, useState } from "react";
|
||||
import { useSelector } from "react-redux";
|
||||
import { networkService } from "../../../../main";
|
||||
|
||||
const useHardwareMonitorsFetch = ({ monitorId, dateRange }) => {
|
||||
|
||||
@@ -64,6 +64,7 @@ const useHardwareUtils = () => {
|
||||
return (
|
||||
<>
|
||||
{Number(MB.toFixed(0))}
|
||||
{space ? " " : ""}
|
||||
<Typography component="span">MB</Typography>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -24,11 +24,10 @@ const InfrastructureDetails = () => {
|
||||
// Redux state
|
||||
|
||||
// Local state
|
||||
const [dateRange, setDateRange] = useState("day");
|
||||
const [dateRange, setDateRange] = useState("recent");
|
||||
|
||||
// Utils
|
||||
const theme = useTheme();
|
||||
const isAdmin = useIsAdmin();
|
||||
const { monitorId } = useParams();
|
||||
|
||||
const { isLoading, networkError, monitor } = useHardwareMonitorsFetch({
|
||||
@@ -62,7 +61,7 @@ const InfrastructureDetails = () => {
|
||||
monitor={monitor}
|
||||
/>
|
||||
<GenericFallback>
|
||||
<Typography>No check history for htis monitor yet.</Typography>
|
||||
<Typography>No check history for this monitor yet.</Typography>
|
||||
</GenericFallback>
|
||||
</Stack>
|
||||
);
|
||||
|
||||
+141
-123
@@ -27,19 +27,24 @@ import {
|
||||
setTimezone,
|
||||
setMode,
|
||||
setDistributedUptimeEnabled,
|
||||
setLanguage,
|
||||
} from "../../Features/UI/uiSlice";
|
||||
import timezones from "../../Utils/timezones.json";
|
||||
import { useState, useEffect } from "react";
|
||||
import { networkService } from "../../main";
|
||||
import { settingsValidation } from "../../Validation/validation";
|
||||
import { updateAppSettings } from "../../Features/Settings/settingsSlice";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
// Constants
|
||||
const SECONDS_PER_DAY = 86400;
|
||||
|
||||
const Settings = () => {
|
||||
const theme = useTheme();
|
||||
const { t, i18n } = useTranslation();
|
||||
const isAdmin = useIsAdmin();
|
||||
const { user } = useSelector((state) => state.auth);
|
||||
const { language } = useSelector((state) => state.ui);
|
||||
const { checkTTL } = user;
|
||||
const { isLoading } = useSelector((state) => state.uptimeMonitors);
|
||||
const { isLoading: authIsLoading } = useSelector((state) => state.auth);
|
||||
@@ -119,10 +124,13 @@ const Settings = () => {
|
||||
});
|
||||
const updatedUser = { ...user, checkTTL: form.ttl };
|
||||
const action = await dispatch(update({ localData: updatedUser }));
|
||||
const settingsAction = await dispatch(
|
||||
updateAppSettings({ settings: { language: language } })
|
||||
);
|
||||
|
||||
if (action.payload.success) {
|
||||
if (action.payload.success && settingsAction.payload.success) {
|
||||
createToast({
|
||||
body: "Settings saved successfully",
|
||||
body: t("settingsSuccessSaved"),
|
||||
});
|
||||
} else {
|
||||
if (action.payload) {
|
||||
@@ -139,7 +147,7 @@ const Settings = () => {
|
||||
}
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
createToast({ body: "Failed to save settings" });
|
||||
createToast({ body: t("settingsFailedToSave") });
|
||||
} finally {
|
||||
setChecksIsLoading(false);
|
||||
}
|
||||
@@ -147,18 +155,16 @@ const Settings = () => {
|
||||
|
||||
const handleClearStats = async () => {
|
||||
try {
|
||||
const action = await dispatch(
|
||||
deleteMonitorChecksByTeamId({ teamId: user.teamId })
|
||||
);
|
||||
const action = await dispatch(deleteMonitorChecksByTeamId({ teamId: user.teamId }));
|
||||
|
||||
if (deleteMonitorChecksByTeamId.fulfilled.match(action)) {
|
||||
createToast({ body: "Stats cleared successfully" });
|
||||
createToast({ body: t("settingsStatsCleared") });
|
||||
} else {
|
||||
createToast({ body: "Failed to clear stats" });
|
||||
createToast({ body: t("settingsFailedToClearStats") });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
createToast({ body: "Failed to clear stats" });
|
||||
createToast({ body: t("settingsFailedToClearStats") });
|
||||
} finally {
|
||||
setIsOpen(deleteStatsMonitorsInitState);
|
||||
}
|
||||
@@ -168,13 +174,13 @@ const Settings = () => {
|
||||
try {
|
||||
const action = await dispatch(addDemoMonitors());
|
||||
if (addDemoMonitors.fulfilled.match(action)) {
|
||||
createToast({ body: "Successfully added demo monitors" });
|
||||
createToast({ body: t("settingsDemoMonitorsAdded") });
|
||||
} else {
|
||||
createToast({ body: "Failed to add demo monitors" });
|
||||
createToast({ body: t("settingsFailedToAddDemoMonitors") });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
createToast({ Body: "Failed to add demo monitors" });
|
||||
createToast({ Body: t("settingsFailedToAddDemoMonitors") });
|
||||
}
|
||||
};
|
||||
|
||||
@@ -182,18 +188,20 @@ const Settings = () => {
|
||||
try {
|
||||
const action = await dispatch(deleteAllMonitors());
|
||||
if (deleteAllMonitors.fulfilled.match(action)) {
|
||||
createToast({ body: "Successfully deleted all monitors" });
|
||||
createToast({ body: t("settingsMonitorsDeleted") });
|
||||
} else {
|
||||
createToast({ body: "Failed to add demo monitors" });
|
||||
createToast({ body: t("settingsFailedToDeleteMonitors") });
|
||||
}
|
||||
} catch (error) {
|
||||
logger.error(error);
|
||||
createToast({ Body: "Failed to delete all monitors" });
|
||||
createToast({ Body: t("settingsFailedToDeleteMonitors") });
|
||||
} finally {
|
||||
setIsOpen(deleteStatsMonitorsInitState);
|
||||
}
|
||||
};
|
||||
|
||||
const languages = Object.keys(i18n.options.resources || {});
|
||||
|
||||
return (
|
||||
<Box
|
||||
className="settings"
|
||||
@@ -209,16 +217,16 @@ const Settings = () => {
|
||||
>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">General settings</Typography>
|
||||
<Typography component="h1">{t("settingsGeneralSettings")}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(2) }}>
|
||||
<Typography component="span">Display timezone</Typography>- The timezone of
|
||||
the dashboard you publicly display.
|
||||
<Typography component="span">{t("settingsDisplayTimezone")}</Typography>-{" "}
|
||||
{t("settingsDisplayTimezoneDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<Select
|
||||
id="display-timezones"
|
||||
label="Display timezone"
|
||||
label={t("settingsDisplayTimezone")}
|
||||
value={timezone}
|
||||
onChange={(e) => {
|
||||
dispatch(setTimezone({ timezone: e.target.value }));
|
||||
@@ -229,15 +237,15 @@ const Settings = () => {
|
||||
</ConfigBox>
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">Appearance</Typography>
|
||||
<Typography component="h1">{t("settingsAppearance")}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(2) }}>
|
||||
Switch between light and dark mode.
|
||||
{t("settingsAppearanceDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<Select
|
||||
id="theme-mode"
|
||||
label="Theme Mode"
|
||||
label={t("settingsThemeMode")}
|
||||
value={mode}
|
||||
onChange={(e) => {
|
||||
dispatch(setMode(e.target.value));
|
||||
@@ -247,14 +255,24 @@ const Settings = () => {
|
||||
{ _id: "dark", name: "Dark" },
|
||||
]}
|
||||
></Select>
|
||||
<Select
|
||||
id="language"
|
||||
label={t("settingsLanguage")}
|
||||
value={language}
|
||||
onChange={(e) => {
|
||||
dispatch(setLanguage(e.target.value));
|
||||
i18n.changeLanguage(e.target.value);
|
||||
}}
|
||||
items={languages.map((lang) => ({ _id: lang, name: lang.toUpperCase() }))}
|
||||
></Select>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
{isAdmin && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">Distributed uptime</Typography>
|
||||
<Typography component="h1">{t("settingsDistributedUptime")}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(2) }}>
|
||||
Enable/disable distributed uptime monitoring.
|
||||
{t("settingsDistributedUptimeDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
@@ -266,109 +284,18 @@ const Settings = () => {
|
||||
dispatch(setDistributedUptimeEnabled(e.target.checked));
|
||||
}}
|
||||
/>
|
||||
{distributedUptimeEnabled === true ? "Enabled" : "Disabled"}
|
||||
{distributedUptimeEnabled === true
|
||||
? t("settingsEnabled")
|
||||
: t("settingsDisabled")}
|
||||
</Box>
|
||||
</ConfigBox>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">History and monitoring</Typography>
|
||||
<Typography component="h1">{t("settingsWallet")}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
Define here for how long you want to keep the data. You can also remove
|
||||
all past data.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<TextInput
|
||||
id="ttl"
|
||||
label="The days you want to keep monitoring history."
|
||||
optionalLabel="0 for infinite"
|
||||
value={form.ttl}
|
||||
onChange={handleChange}
|
||||
error={errors.ttl ? true : false}
|
||||
helperText={errors.ttl}
|
||||
/>
|
||||
<Box>
|
||||
<Typography>Clear all stats. This is irreversible.</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() =>
|
||||
setIsOpen({ ...deleteStatsMonitorsInitState, deleteStats: true })
|
||||
}
|
||||
sx={{ mt: theme.spacing(4) }}
|
||||
>
|
||||
Clear all stats
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Dialog
|
||||
open={isOpen.deleteStats}
|
||||
theme={theme}
|
||||
title="Do you want to clear all stats?"
|
||||
description="Once deleted, your monitors cannot be retrieved."
|
||||
onCancel={() => setIsOpen(deleteStatsMonitorsInitState)}
|
||||
confirmationButtonLabel="Yes, clear all stats"
|
||||
onConfirm={handleClearStats}
|
||||
isLoading={isLoading || authIsLoading || checksIsLoading}
|
||||
/>
|
||||
</ConfigBox>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">Demo monitors</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
Here you can add and remove demo monitors.
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<Box>
|
||||
<Typography>Add demo monitors</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
loading={isLoading || authIsLoading || checksIsLoading}
|
||||
onClick={handleInsertDemoMonitors}
|
||||
sx={{ mt: theme.spacing(4) }}
|
||||
>
|
||||
Add demo monitors
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography>Remove all monitors</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
loading={isLoading || authIsLoading || checksIsLoading}
|
||||
onClick={() =>
|
||||
setIsOpen({ ...deleteStatsMonitorsInitState, deleteMonitors: true })
|
||||
}
|
||||
sx={{ mt: theme.spacing(4) }}
|
||||
>
|
||||
Remove all monitors
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Dialog
|
||||
open={isOpen.deleteMonitors}
|
||||
theme={theme}
|
||||
title="Do you want to remove all monitors?"
|
||||
onCancel={() => setIsOpen(deleteStatsMonitorsInitState)}
|
||||
confirmationButtonLabel="Yes, clear all monitors"
|
||||
onConfirm={handleDeleteAllMonitors}
|
||||
isLoading={isLoading || authIsLoading || checksIsLoading}
|
||||
/>
|
||||
</ConfigBox>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">Wallet</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
Connect your wallet here. This is required for the Distributed Uptime
|
||||
monitor to connect to multiple nodes globally.
|
||||
{t("settingsWalletDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
@@ -382,15 +309,106 @@ const Settings = () => {
|
||||
</Box>
|
||||
</ConfigBox>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">{t("settingsHistoryAndMonitoring")}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
{t("settingsHistoryAndMonitoringDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<TextInput
|
||||
id="ttl"
|
||||
label={t("settingsTTLLabel")}
|
||||
optionalLabel={t("settingsTTLOptionalLabel")}
|
||||
value={form.ttl}
|
||||
onChange={handleChange}
|
||||
error={errors.ttl ? true : false}
|
||||
helperText={errors.ttl}
|
||||
/>
|
||||
<Box>
|
||||
<Typography>{t("settingsClearAllStats")}</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
onClick={() =>
|
||||
setIsOpen({ ...deleteStatsMonitorsInitState, deleteStats: true })
|
||||
}
|
||||
sx={{ mt: theme.spacing(4) }}
|
||||
>
|
||||
{t("settingsClearAllStatsButton")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Dialog
|
||||
open={isOpen.deleteStats}
|
||||
theme={theme}
|
||||
title={t("settingsClearAllStatsDialogTitle")}
|
||||
description={t("settingsClearAllStatsDialogDescription")}
|
||||
onCancel={() => setIsOpen(deleteStatsMonitorsInitState)}
|
||||
confirmationButtonLabel={t("settingsClearAllStatsDialogConfirm")}
|
||||
onConfirm={handleClearStats}
|
||||
isLoading={isLoading || authIsLoading || checksIsLoading}
|
||||
/>
|
||||
</ConfigBox>
|
||||
)}
|
||||
{isAdmin && (
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">{t("settingsDemoMonitors")}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2) }}>
|
||||
{t("settingsDemoMonitorsDescription")}
|
||||
</Typography>
|
||||
</Box>
|
||||
<Stack gap={theme.spacing(20)}>
|
||||
<Box>
|
||||
<Typography>{t("settingsAddDemoMonitors")}</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="accent"
|
||||
loading={isLoading || authIsLoading || checksIsLoading}
|
||||
onClick={handleInsertDemoMonitors}
|
||||
sx={{ mt: theme.spacing(4) }}
|
||||
>
|
||||
{t("settingsAddDemoMonitorsButton")}
|
||||
</Button>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography>{t("settingsRemoveAllMonitors")}</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
color="error"
|
||||
loading={isLoading || authIsLoading || checksIsLoading}
|
||||
onClick={() =>
|
||||
setIsOpen({ ...deleteStatsMonitorsInitState, deleteMonitors: true })
|
||||
}
|
||||
sx={{ mt: theme.spacing(4) }}
|
||||
>
|
||||
{t("settingsRemoveAllMonitorsButton")}
|
||||
</Button>
|
||||
</Box>
|
||||
</Stack>
|
||||
<Dialog
|
||||
open={isOpen.deleteMonitors}
|
||||
theme={theme}
|
||||
title={t("settingsRemoveAllMonitorsDialogTitle")}
|
||||
onCancel={() => setIsOpen(deleteStatsMonitorsInitState)}
|
||||
confirmationButtonLabel={t("settingsRemoveAllMonitorsDialogConfirm")}
|
||||
onConfirm={handleDeleteAllMonitors}
|
||||
isLoading={isLoading || authIsLoading || checksIsLoading}
|
||||
/>
|
||||
</ConfigBox>
|
||||
)}
|
||||
|
||||
<ConfigBox>
|
||||
<Box>
|
||||
<Typography component="h1">About</Typography>
|
||||
<Typography component="h1">{t("settingsAbout")}</Typography>
|
||||
</Box>
|
||||
<Box>
|
||||
<Typography component="h2">Checkmate {version}</Typography>
|
||||
<Typography sx={{ mt: theme.spacing(2), mb: theme.spacing(6), opacity: 0.6 }}>
|
||||
Developed by Bluewave Labs.
|
||||
{t("settingsDevelopedBy")}
|
||||
</Typography>
|
||||
<Link
|
||||
level="secondary"
|
||||
@@ -411,7 +429,7 @@ const Settings = () => {
|
||||
sx={{ px: theme.spacing(12), mt: theme.spacing(20) }}
|
||||
onClick={handleSave}
|
||||
>
|
||||
Save
|
||||
{t("settingsSave")}
|
||||
</Button>
|
||||
</Stack>
|
||||
</Stack>
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import ConfigBox from "../../../../../Components/ConfigBox";
|
||||
import PropTypes from "prop-types";
|
||||
import { useTheme } from "@emotion/react";
|
||||
|
||||
// This can be used to add any extra/additional section/stacks on top of existing sections on the tab
|
||||
const ConfigStack = ({ title, description, children }) => {
|
||||
const theme = useTheme();
|
||||
return (
|
||||
<ConfigBox>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Typography component="h2">{title}</Typography>
|
||||
<Typography component="p">{description}</Typography>
|
||||
</Stack>
|
||||
{children}
|
||||
</ConfigBox>
|
||||
);
|
||||
};
|
||||
|
||||
ConfigStack.propTypes = {
|
||||
title: PropTypes.string.isRequired, // Title must be a string and is required
|
||||
description: PropTypes.string.isRequired, // Description must be a string and is required
|
||||
children: PropTypes.node.isRequired,
|
||||
};
|
||||
|
||||
export default ConfigStack;
|
||||
@@ -1,13 +1,13 @@
|
||||
// Components
|
||||
import { Stack, Typography, Button } from "@mui/material";
|
||||
import { Stack, Typography } from "@mui/material";
|
||||
import { TabPanel } from "@mui/lab";
|
||||
import ConfigBox from "../../../../../Components/ConfigBox";
|
||||
import MonitorList from "../MonitorList";
|
||||
import Search from "../../../../../Components/Inputs/Search";
|
||||
import Checkbox from "../../../../../Components/Inputs/Checkbox";
|
||||
// Utils
|
||||
import { useState } from "react";
|
||||
import { useTheme } from "@emotion/react";
|
||||
import ConfigStack from "./ConfigStack";
|
||||
const Content = ({
|
||||
tabValue,
|
||||
form,
|
||||
@@ -34,14 +34,10 @@ const Content = ({
|
||||
return (
|
||||
<TabPanel value={tabValue}>
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<ConfigBox>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Typography component="h2">Status page servers</Typography>
|
||||
<Typography component="p">
|
||||
You can add any number of servers that you monitor to your status page. You
|
||||
can also reorder them for the best viewing experience.
|
||||
</Typography>
|
||||
</Stack>
|
||||
<ConfigStack
|
||||
title="Status page servers"
|
||||
description="You can add any number of servers that you monitor to your status page. You can also reorder them for the best viewing experience."
|
||||
>
|
||||
<Stack>
|
||||
<Stack
|
||||
direction="row"
|
||||
@@ -72,13 +68,12 @@ const Content = ({
|
||||
setSelectedMonitors={handleMonitorsChange}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>{" "}
|
||||
<ConfigBox>
|
||||
<Stack gap={theme.spacing(6)}>
|
||||
<Typography component="h2">Features</Typography>
|
||||
<Typography component="p">Show more details on the status page</Typography>
|
||||
</Stack>
|
||||
<Stack sx={{ margin: theme.spacing(6) }}>
|
||||
</ConfigStack>
|
||||
<ConfigStack
|
||||
title="Features"
|
||||
description="Show more details on the status page"
|
||||
>
|
||||
<Stack>
|
||||
<Checkbox
|
||||
id="showCharts"
|
||||
name="showCharts"
|
||||
@@ -94,9 +89,8 @@ const Content = ({
|
||||
onChange={handleFormChange}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
</ConfigStack>
|
||||
</Stack>
|
||||
<Stack gap={theme.spacing(6)}></Stack>
|
||||
</TabPanel>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -73,6 +73,8 @@ const TabSettings = ({
|
||||
label="Your status page address"
|
||||
value={form.url}
|
||||
onChange={handleFormChange}
|
||||
helperText={errors["url"]}
|
||||
error={errors["url"] ? true : false}
|
||||
/>
|
||||
</Stack>
|
||||
</ConfigBox>
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
import { Box, Stack, Typography, Button } from "@mui/material";
|
||||
import Image from "../../../../../Components/Image";
|
||||
import SettingsIcon from "../../../../../assets/icons/settings-bold.svg?react";
|
||||
|
||||
import ThemeSwitch from "../../../../../Components/ThemeSwitch";
|
||||
//Utils
|
||||
import { useTheme } from "@mui/material/styles";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
@@ -76,6 +76,7 @@ Controls.propTypes = {
|
||||
|
||||
const ControlsHeader = ({
|
||||
statusPage,
|
||||
isPublic,
|
||||
isDeleting,
|
||||
isDeleteOpen,
|
||||
setIsDeleteOpen,
|
||||
@@ -105,7 +106,16 @@ const ControlsHeader = ({
|
||||
maxHeight={"50px"}
|
||||
base64={statusPage?.logo?.data}
|
||||
/>
|
||||
<Typography variant="h1">{statusPage?.companyName}</Typography>
|
||||
<Typography
|
||||
variant="h1"
|
||||
overflow="hidden"
|
||||
textOverflow="ellipsis"
|
||||
sx={{
|
||||
maxWidth: { xs: "200px", sm: "100%" },
|
||||
}}
|
||||
>
|
||||
{statusPage?.companyName}
|
||||
</Typography>
|
||||
</Stack>
|
||||
<Controls
|
||||
isDeleting={isDeleting}
|
||||
@@ -114,6 +124,7 @@ const ControlsHeader = ({
|
||||
url={url}
|
||||
type={type}
|
||||
/>
|
||||
{isPublic && <ThemeSwitch />}
|
||||
</Stack>
|
||||
);
|
||||
};
|
||||
@@ -121,6 +132,7 @@ const ControlsHeader = ({
|
||||
ControlsHeader.propTypes = {
|
||||
url: PropTypes.string,
|
||||
statusPage: PropTypes.object,
|
||||
isPublic: PropTypes.bool,
|
||||
isDeleting: PropTypes.bool,
|
||||
isDeleteOpen: PropTypes.bool.isRequired,
|
||||
setIsDeleteOpen: PropTypes.func.isRequired,
|
||||
|
||||
@@ -51,6 +51,7 @@ const StatusPages = () => {
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Stack gap={theme.spacing(10)}>
|
||||
<Breadcrumbs list={BREADCRUMBS} />
|
||||
|
||||
@@ -34,14 +34,15 @@ const UptimeDetails = () => {
|
||||
const uiTimezone = useSelector((state) => state.ui.timezone);
|
||||
|
||||
// Local state
|
||||
const [dateRange, setDateRange] = useState("day");
|
||||
const [dateRange, setDateRange] = useState("recent");
|
||||
const [hoveredUptimeData, setHoveredUptimeData] = useState(null);
|
||||
const [hoveredIncidentsData, setHoveredIncidentsData] = useState(null);
|
||||
const [page, setPage] = useState(0);
|
||||
const [rowsPerPage, setRowsPerPage] = useState(5);
|
||||
|
||||
// Utils
|
||||
const dateFormat = dateRange === "day" ? "MMM D, h A" : "MMM D";
|
||||
const dateFormat =
|
||||
dateRange === "day" || dateRange === "recent" ? "MMM D, h A" : "MMM D";
|
||||
const { monitorId } = useParams();
|
||||
const theme = useTheme();
|
||||
const isAdmin = useIsAdmin();
|
||||
|
||||
@@ -567,6 +567,7 @@ class NetworkService {
|
||||
|
||||
async getChecksByMonitor(config) {
|
||||
const params = new URLSearchParams();
|
||||
if (config.type) params.append("type", config.type);
|
||||
if (config.sortOrder) params.append("sortOrder", config.sortOrder);
|
||||
if (config.limit) params.append("limit", config.limit);
|
||||
if (config.dateRange) params.append("dateRange", config.dateRange);
|
||||
@@ -807,6 +808,31 @@ class NetworkService {
|
||||
);
|
||||
}
|
||||
|
||||
getDistributedUptimeMonitors(config) {
|
||||
const { teamId, limit, types, page, rowsPerPage, filter, field, order } = config;
|
||||
|
||||
const params = new URLSearchParams();
|
||||
|
||||
if (limit) params.append("limit", limit);
|
||||
if (types) {
|
||||
types.forEach((type) => {
|
||||
params.append("type", type);
|
||||
});
|
||||
}
|
||||
if (page) params.append("page", page);
|
||||
if (rowsPerPage) params.append("rowsPerPage", rowsPerPage);
|
||||
if (filter) params.append("filter", filter);
|
||||
if (field) params.append("field", field);
|
||||
if (order) params.append("order", order);
|
||||
|
||||
if (this.eventSource) {
|
||||
this.eventSource.close();
|
||||
}
|
||||
|
||||
const url = `${this.axiosInstance.defaults.baseURL}/distributed-uptime/monitors/${teamId}/initial?${params.toString()}`;
|
||||
return this.axiosInstance.get(url);
|
||||
}
|
||||
|
||||
subscribeToDistributedUptimeMonitors(config) {
|
||||
const {
|
||||
teamId,
|
||||
@@ -872,6 +898,15 @@ class NetworkService {
|
||||
};
|
||||
}
|
||||
|
||||
getDistributedUptimeDetails(config) {
|
||||
const params = new URLSearchParams();
|
||||
const { monitorId, onUpdate, onOpen, onError, dateRange, normalize } = config;
|
||||
if (dateRange) params.append("dateRange", dateRange);
|
||||
if (normalize) params.append("normalize", normalize);
|
||||
const url = `${this.axiosInstance.defaults.baseURL}/distributed-uptime/monitors/details/${monitorId}/initial?${params.toString()}`;
|
||||
return this.axiosInstance.get(url);
|
||||
}
|
||||
|
||||
subscribeToDistributedUptimeDetails(config) {
|
||||
const params = new URLSearchParams();
|
||||
const { monitorId, onUpdate, onOpen, onError, dateRange, normalize } = config;
|
||||
|
||||
@@ -40,7 +40,6 @@ const paletteColors = {
|
||||
blueGray150: "#667085",
|
||||
blueGray200: "#475467",
|
||||
blueGray400: "#344054",
|
||||
blueGray900: "#1c2130",
|
||||
blueBlueWave: "#1570EF",
|
||||
blue700: "#4E5BA6",
|
||||
purple300: "#664EFF",
|
||||
@@ -89,11 +88,14 @@ const newColors = {
|
||||
gray100: "#F3F3F3",
|
||||
gray200: "#EFEFEF",
|
||||
gray500: "#A2A3A3",
|
||||
gray900: "#1c1c1c",
|
||||
blueGray50: "#E8F0FE",
|
||||
blueGray500: "#475467",
|
||||
blueGray600: "#344054",
|
||||
blueGray800: "#1C2130",
|
||||
blueGray900: "#515151",
|
||||
blueBlueWave: "#1570EF",
|
||||
lightBlueWave: "#CDE2FF",
|
||||
/* I changed green 100 and green 700. Need to change red and warning as well, and refactor the object following the structure */
|
||||
green100: "#67cd78",
|
||||
green200: "#4B9B77",
|
||||
@@ -171,6 +173,10 @@ const newSemanticColors = {
|
||||
light: newColors.gray200,
|
||||
dark: "#313131" /* newColors.blueGray600 */,
|
||||
},
|
||||
light: {
|
||||
light: newColors.lightBlueWave,
|
||||
dark: newColors.lightBlueWave,
|
||||
},
|
||||
contrastText: {
|
||||
light: newColors.blueGray600,
|
||||
dark: newColors.gray200,
|
||||
@@ -282,7 +288,7 @@ const newSemanticColors = {
|
||||
},
|
||||
chatbot: {
|
||||
background: {
|
||||
light: "#112B2B",
|
||||
light: newColors.gray200,
|
||||
dark: "#112B2B",
|
||||
},
|
||||
textAccent: {
|
||||
@@ -290,6 +296,16 @@ const newSemanticColors = {
|
||||
dark: "#C084FC",
|
||||
},
|
||||
},
|
||||
alert: {
|
||||
main: {
|
||||
light: newColors.gray200,
|
||||
dark: newColors.gray900,
|
||||
},
|
||||
contrastText: {
|
||||
light: newColors.blueGray600,
|
||||
dark: newColors.blueGray900,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export { typographyLevels, semanticColors as colors, newSemanticColors };
|
||||
@@ -382,6 +382,18 @@ const baseTheme = (palette) => ({
|
||||
}),
|
||||
},
|
||||
},
|
||||
MuiButtonGroup: {
|
||||
styleOverrides: {
|
||||
root: ({ theme }) => ({
|
||||
ml: "auto",
|
||||
"& .MuiButtonBase-root, & .MuiButtonBase-root:hover": {
|
||||
borderColor: theme.palette.primary.lowContrast,
|
||||
width: "auto",
|
||||
whiteSpace: "nowrap",
|
||||
},
|
||||
}),
|
||||
},
|
||||
},
|
||||
},
|
||||
shape: {
|
||||
borderRadius: 2,
|
||||
|
||||
+24
-14
@@ -3,8 +3,8 @@
|
||||
* @param {string} str String whose first letter is to be capitalized
|
||||
* @returns A string with first letter capitalized
|
||||
*/
|
||||
export const capitalizeFirstLetter = (str) => {
|
||||
if (str === null || str === undefined) {
|
||||
export const capitalizeFirstLetter = (str) => {
|
||||
if (str === null || str === undefined) {
|
||||
return "";
|
||||
}
|
||||
if (typeof str !== "string") {
|
||||
@@ -12,7 +12,7 @@ export const capitalizeFirstLetter = (str) => {
|
||||
}
|
||||
if (str.length === 0) {
|
||||
return "";
|
||||
}
|
||||
}
|
||||
return str.charAt(0).toUpperCase() + str.slice(1);
|
||||
};
|
||||
|
||||
@@ -22,15 +22,25 @@ export const capitalizeFirstLetter = (str) => {
|
||||
* @returns A string with first letter lower cased
|
||||
*/
|
||||
|
||||
export const toLowerCaseFirstLetter = (str) => {
|
||||
export const toLowerCaseFirstLetter = (str) => {
|
||||
if (str === null || str === undefined) {
|
||||
return "";
|
||||
}
|
||||
if (typeof str !== "string") {
|
||||
throw new TypeError("Input must be a string");
|
||||
}
|
||||
if (str.length === 0) {
|
||||
return "";
|
||||
}
|
||||
return str.charAt(0).toLowerCase() + str.slice(1);
|
||||
};
|
||||
return "";
|
||||
}
|
||||
if (typeof str !== "string") {
|
||||
throw new TypeError("Input must be a string");
|
||||
}
|
||||
if (str.length === 0) {
|
||||
return "";
|
||||
}
|
||||
return str.charAt(0).toLowerCase() + str.slice(1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Checks if a string is null, undefined, or empty (including strings with only whitespace).
|
||||
* @param {string} str - The string to check.
|
||||
* @returns {boolean} - Returns true if the string is null, undefined, or empty.
|
||||
*/
|
||||
export const isEmpty = (str) => {
|
||||
// Check if string is null, undefined, or empty (including whitespace only)
|
||||
return str === null || typeof str === "undefined" || str.trim().length === 0;
|
||||
};
|
||||
@@ -222,7 +222,14 @@ const statusPageValidation = joi.object({
|
||||
.string()
|
||||
.trim()
|
||||
.messages({ "string.empty": "Company name is required." }),
|
||||
url: joi.string().trim().messages({ "string.empty": "URL is required." }),
|
||||
url: joi
|
||||
.string()
|
||||
.pattern(/^[a-zA-Z0-9_-]+$/) // Only allow alphanumeric, underscore, and hyphen
|
||||
.required()
|
||||
.messages({
|
||||
"string.pattern.base":
|
||||
"URL can only contain letters, numbers, underscores, and hyphens",
|
||||
}),
|
||||
timezone: joi.string().trim().messages({ "string.empty": "Timezone is required." }),
|
||||
color: joi.string().trim().messages({ "string.empty": "Color is required." }),
|
||||
theme: joi.string(),
|
||||
|
||||
+43
-1
@@ -65,5 +65,47 @@
|
||||
"distributedStatusSubHeaderText": "Powered by millions devices worldwide, view a system performance by global region, country or city",
|
||||
"distributedRightCategoryTitle": "Monitor",
|
||||
"distributedStatusServerMonitors": "Server Monitors",
|
||||
"distributedStatusServerMonitorsDescription": "Monitor status of related servers"
|
||||
"distributedStatusServerMonitorsDescription": "Monitor status of related servers",
|
||||
|
||||
"settingsGeneralSettings": "General settings",
|
||||
"settingsDisplayTimezone": "Display timezone",
|
||||
"settingsDisplayTimezoneDescription": "The timezone of the dashboard you publicly display.",
|
||||
"settingsAppearance": "Appearance",
|
||||
"settingsAppearanceDescription": "Switch between light and dark mode, or change user interface language",
|
||||
"settingsThemeMode": "Theme Mode",
|
||||
"settingsLanguage": "Language",
|
||||
"settingsDistributedUptime": "Distributed uptime",
|
||||
"settingsDistributedUptimeDescription": "Enable/disable distributed uptime monitoring.",
|
||||
"settingsEnabled": "Enabled",
|
||||
"settingsDisabled": "Disabled",
|
||||
"settingsHistoryAndMonitoring": "History and monitoring",
|
||||
"settingsHistoryAndMonitoringDescription": "Define here for how long you want to keep the data. You can also remove all past data.",
|
||||
"settingsTTLLabel": "The days you want to keep monitoring history.",
|
||||
"settingsTTLOptionalLabel": "0 for infinite",
|
||||
"settingsClearAllStats": "Clear all stats. This is irreversible.",
|
||||
"settingsClearAllStatsButton": "Clear all stats",
|
||||
"settingsClearAllStatsDialogTitle": "Do you want to clear all stats?",
|
||||
"settingsClearAllStatsDialogDescription": "Once deleted, your monitors cannot be retrieved.",
|
||||
"settingsClearAllStatsDialogConfirm": "Yes, clear all stats",
|
||||
"settingsDemoMonitors": "Demo monitors",
|
||||
"settingsDemoMonitorsDescription": "Here you can add and remove demo monitors.",
|
||||
"settingsAddDemoMonitors": "Add demo monitors",
|
||||
"settingsAddDemoMonitorsButton": "Add demo monitors",
|
||||
"settingsRemoveAllMonitors": "Remove all monitors",
|
||||
"settingsRemoveAllMonitorsButton": "Remove all monitors",
|
||||
"settingsRemoveAllMonitorsDialogTitle": "Do you want to remove all monitors?",
|
||||
"settingsRemoveAllMonitorsDialogConfirm": "Yes, clear all monitors",
|
||||
"settingsWallet": "Wallet",
|
||||
"settingsWalletDescription": "Connect your wallet here. This is required for the Distributed Uptime monitor to connect to multiple nodes globally.",
|
||||
"settingsAbout": "About",
|
||||
"settingsDevelopedBy": "Developed by Bluewave Labs.",
|
||||
"settingsSave": "Save",
|
||||
"settingsSuccessSaved": "Settings saved successfully",
|
||||
"settingsFailedToSave": "Failed to save settings",
|
||||
"settingsStatsCleared": "Stats cleared successfully",
|
||||
"settingsFailedToClearStats": "Failed to clear stats",
|
||||
"settingsDemoMonitorsAdded": "Successfully added demo monitors",
|
||||
"settingsFailedToAddDemoMonitors": "Failed to add demo monitors",
|
||||
"settingsMonitorsDeleted": "Successfully deleted all monitors",
|
||||
"settingsFailedToDeleteMonitors": "Failed to delete all monitors"
|
||||
}
|
||||
+44
-1
@@ -58,5 +58,48 @@
|
||||
"authRegisterBySigningUp": "Kayıt olarak, aşağıdaki şartları kabul ediyorsunuz:",
|
||||
"distributedStatusHeaderText": "Gerçek zamanlı, Gerçek cihazlar kapsamı",
|
||||
"distributedStatusSubHeaderText": "Dünya çapında milyonlarca cihaz tarafından desteklenen sistem performansını küresel bölgeye, ülkeye veya şehre göre görüntüleyin",
|
||||
"distributedRightCategoryTitle": "Monitör"
|
||||
"distributedRightCatagoryTitle": "Monitör",
|
||||
"distributedRightCatagoryDescription": "Mainnet Beta Kümesi",
|
||||
|
||||
"settingsGeneralSettings": "Genel ayarlar",
|
||||
"settingsDisplayTimezone": "Görüntüleme saat dilimi",
|
||||
"settingsDisplayTimezoneDescription": "Herkese açık olarak görüntülediğiniz kontrol panelinin saat dilimi.",
|
||||
"settingsAppearance": "Görünüm",
|
||||
"settingsAppearanceDescription": "Açık ve koyu mod arasında geçiş yapın veya kullanıcı arayüzü dilini değiştirin",
|
||||
"settingsThemeMode": "Tema",
|
||||
"settingsLanguage": "Dil",
|
||||
"settingsDistributedUptime": "Dağıtılmış çalışma süresi",
|
||||
"settingsDistributedUptimeDescription": "Dağıtılmış çalışma süresi izlemeyi etkinleştirin/devre dışı bırakın.",
|
||||
"settingsEnabled": "Etkin",
|
||||
"settingsDisabled": "Devre dışı",
|
||||
"settingsHistoryAndMonitoring": "Geçmiş ve izleme",
|
||||
"settingsHistoryAndMonitoringDescription": "Verileri ne kadar süreyle saklamak istediğinizi burada tanımlayın. Ayrıca tüm geçmiş verileri kaldırabilirsiniz.",
|
||||
"settingsTTLLabel": "İzleme geçmişini saklamak istediğiniz gün sayısı.",
|
||||
"settingsTTLOptionalLabel": "Sınırsız için 0",
|
||||
"settingsClearAllStats": "Tüm istatistikleri temizle. Bu geri alınamaz.",
|
||||
"settingsClearAllStatsButton": "Tüm istatistikleri temizle",
|
||||
"settingsClearAllStatsDialogTitle": "Tüm istatistikleri temizlemek istiyor musunuz?",
|
||||
"settingsClearAllStatsDialogDescription": "Silindikten sonra, monitörleriniz geri alınamaz.",
|
||||
"settingsClearAllStatsDialogConfirm": "Evet, tüm istatistikleri temizle",
|
||||
"settingsDemoMonitors": "Demo monitörler",
|
||||
"settingsDemoMonitorsDescription": "Burada demo monitörler ekleyebilir ve kaldırabilirsiniz.",
|
||||
"settingsAddDemoMonitors": "Demo monitörler ekle",
|
||||
"settingsAddDemoMonitorsButton": "Demo monitörler ekle",
|
||||
"settingsRemoveAllMonitors": "Tüm monitörleri kaldır",
|
||||
"settingsRemoveAllMonitorsButton": "Tüm monitörleri kaldır",
|
||||
"settingsRemoveAllMonitorsDialogTitle": "Tüm monitörleri kaldırmak istiyor musunuz?",
|
||||
"settingsRemoveAllMonitorsDialogConfirm": "Evet, tüm monitörleri temizle",
|
||||
"settingsWallet": "Cüzdan",
|
||||
"settingsWalletDescription": "Cüzdanınızı buradan bağlayın. Bu, Dağıtılmış Çalışma Süresi monitörünün küresel olarak birden çok düğüme bağlanması için gereklidir.",
|
||||
"settingsAbout": "Hakkında",
|
||||
"settingsDevelopedBy": "Bluewave Labs tarafından geliştirilmiştir.",
|
||||
"settingsSave": "Kaydet",
|
||||
"settingsSuccessSaved": "Ayarlar başarıyla kaydedildi",
|
||||
"settingsFailedToSave": "Ayarlar kaydedilemedi",
|
||||
"settingsStatsCleared": "İstatistikler başarıyla temizlendi",
|
||||
"settingsFailedToClearStats": "İstatistikler temizlenemedi",
|
||||
"settingsDemoMonitorsAdded": "Demo monitörler başarıyla eklendi",
|
||||
"settingsFailedToAddDemoMonitors": "Demo monitörler eklenemedi",
|
||||
"settingsMonitorsDeleted": "Tüm monitörler başarıyla silindi",
|
||||
"settingsFailedToDeleteMonitors": "Monitörler silinemedi"
|
||||
}
|
||||
Reference in new issue
Block a user