diff --git a/Client/package-lock.json b/Client/package-lock.json index 615da347b..689b4a090 100644 --- a/Client/package-lock.json +++ b/Client/package-lock.json @@ -13,7 +13,7 @@ "@fontsource/roboto": "^5.0.13", "@mui/icons-material": "^5.15.17", "@mui/lab": "^5.0.0-alpha.170", - "@mui/material": "^5.16.7", + "@mui/material": "6.1.8", "@mui/x-charts": "^7.5.1", "@mui/x-data-grid": "7.22.3", "@mui/x-date-pickers": "7.22.3", @@ -1135,9 +1135,9 @@ } }, "node_modules/@mui/core-downloads-tracker": { - "version": "5.16.7", - "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-5.16.7.tgz", - "integrity": "sha512-RtsCt4Geed2/v74sbihWzzRs+HsIQCfclHeORh5Ynu2fS4icIKozcSubwuG7vtzq2uW3fOR1zITSP84TNt2GoQ==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@mui/core-downloads-tracker/-/core-downloads-tracker-6.1.8.tgz", + "integrity": "sha512-TGAvzwUg9hybDacwfIGFjI2bXYXrIqky+vMfaeay8rvT56/PNAlvIDUJ54kpT5KRc9AWAihOvtDI7/LJOThOmQ==", "license": "MIT", "funding": { "type": "opencollective", @@ -1212,26 +1212,26 @@ } }, "node_modules/@mui/material": { - "version": "5.16.7", - "resolved": "https://registry.npmjs.org/@mui/material/-/material-5.16.7.tgz", - "integrity": "sha512-cwwVQxBhK60OIOqZOVLFt55t01zmarKJiJUWbk0+8s/Ix5IaUzAShqlJchxsIQ4mSrWqgcKCCXKtIlG5H+/Jmg==", + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@mui/material/-/material-6.1.8.tgz", + "integrity": "sha512-QZdQFnXct+7NXIzHgT3qt+sQiO7HYGZU2vymP9Xl9tUMXEOA/S1mZMMb7+WGZrk5TzNlU/kP/85K0da5V1jXoQ==", "license": "MIT", "dependencies": { - "@babel/runtime": "^7.23.9", - "@mui/core-downloads-tracker": "^5.16.7", - "@mui/system": "^5.16.7", - "@mui/types": "^7.2.15", - "@mui/utils": "^5.16.6", + "@babel/runtime": "^7.26.0", + "@mui/core-downloads-tracker": "^6.1.8", + "@mui/system": "^6.1.8", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.1.8", "@popperjs/core": "^2.11.8", - "@types/react-transition-group": "^4.4.10", - "clsx": "^2.1.0", + "@types/react-transition-group": "^4.4.11", + "clsx": "^2.1.1", "csstype": "^3.1.3", "prop-types": "^15.8.1", "react-is": "^18.3.1", "react-transition-group": "^4.4.5" }, "engines": { - "node": ">=12.0.0" + "node": ">=14.0.0" }, "funding": { "type": "opencollective", @@ -1240,9 +1240,10 @@ "peerDependencies": { "@emotion/react": "^11.5.0", "@emotion/styled": "^11.3.0", - "@types/react": "^17.0.0 || ^18.0.0", - "react": "^17.0.0 || ^18.0.0", - "react-dom": "^17.0.0 || ^18.0.0" + "@mui/material-pigment-css": "^6.1.8", + "@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" }, "peerDependenciesMeta": { "@emotion/react": { @@ -1251,6 +1252,140 @@ "@emotion/styled": { "optional": true }, + "@mui/material-pigment-css": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/@mui/private-theming": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@mui/private-theming/-/private-theming-6.1.8.tgz", + "integrity": "sha512-TuKl7msynCNCVvhX3c0ef1sF0Qb3VHcPs8XOGB/8bdOGBr/ynmIG1yTMjZeiFQXk8yN9fzK/FDEKMFxILNn3wg==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/utils": "^6.1.8", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/@mui/styled-engine": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@mui/styled-engine/-/styled-engine-6.1.8.tgz", + "integrity": "sha512-ZvEoT0U2nPLSLI+B4by4cVjaZnPT2f20f4JUPkyHdwLv65ZzuoHiTlwyhqX1Ch63p8bcJzKTHQVGisEoMK6PGA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@emotion/cache": "^11.13.1", + "@emotion/serialize": "^1.3.2", + "@emotion/sheet": "^1.4.0", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.4.1", + "@emotion/styled": "^11.3.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/@mui/system": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@mui/system/-/system-6.1.8.tgz", + "integrity": "sha512-i1kLfQoWxzFpXTBQIuPoA3xKnAnP3en4I2T8xIolovSolGQX5k8vGjw1JaydQS40td++cFsgCdEU458HDNTGUA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/private-theming": "^6.1.8", + "@mui/styled-engine": "^6.1.8", + "@mui/types": "^7.2.19", + "@mui/utils": "^6.1.8", + "clsx": "^2.1.1", + "csstype": "^3.1.3", + "prop-types": "^15.8.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@emotion/react": "^11.5.0", + "@emotion/styled": "^11.3.0", + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { + "@emotion/react": { + "optional": true + }, + "@emotion/styled": { + "optional": true + }, + "@types/react": { + "optional": true + } + } + }, + "node_modules/@mui/material/node_modules/@mui/utils": { + "version": "6.1.8", + "resolved": "https://registry.npmjs.org/@mui/utils/-/utils-6.1.8.tgz", + "integrity": "sha512-O2DWb1kz8hiANVcR7Z4gOB3SvPPsSQGUmStpyBDzde6dJIfBzgV9PbEQOBZd3EBsd1pB+Uv1z5LAJAbymmawrA==", + "license": "MIT", + "dependencies": { + "@babel/runtime": "^7.26.0", + "@mui/types": "^7.2.19", + "@types/prop-types": "^15.7.13", + "clsx": "^2.1.1", + "prop-types": "^15.8.1", + "react-is": "^18.3.1" + }, + "engines": { + "node": ">=14.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/mui-org" + }, + "peerDependencies": { + "@types/react": "^17.0.0 || ^18.0.0 || ^19.0.0", + "react": "^17.0.0 || ^18.0.0 || ^19.0.0" + }, + "peerDependenciesMeta": { "@types/react": { "optional": true } diff --git a/Client/package.json b/Client/package.json index 6a7d436a3..14362f890 100644 --- a/Client/package.json +++ b/Client/package.json @@ -16,7 +16,7 @@ "@fontsource/roboto": "^5.0.13", "@mui/icons-material": "^5.15.17", "@mui/lab": "^5.0.0-alpha.170", - "@mui/material": "^5.16.7", + "@mui/material": "6.1.8", "@mui/x-charts": "^7.5.1", "@mui/x-data-grid": "7.22.3", "@mui/x-date-pickers": "7.22.3", diff --git a/Client/src/App.jsx b/Client/src/App.jsx index 82c8203a8..62b6bd9ff 100644 --- a/Client/src/App.jsx +++ b/Client/src/App.jsx @@ -41,6 +41,7 @@ import { logger } from "./Utils/Logger"; // Import the logger import { networkService } from "./main"; import { Infrastructure } from "./Pages/Infrastructure"; import InfrastructureDetails from "./Pages/Infrastructure/Details"; +import Test from "./Pages/test"; function App() { const AdminCheckedRegister = withAdminCheck(Register); const MonitorsWithAdminProp = withAdminProp(Monitors); @@ -89,6 +90,11 @@ function App() { path="/" element={} > + } + /> + { + const theme = useTheme(); + return ( + + + {https ? "https" : "http"}:// + + + ); +}; + +export const PasswordEndAdornment = ({ fieldType, setFieldType }) => { + const theme = useTheme(); + return ( + + setFieldType(fieldType === "password" ? "text" : "password")} + sx={{ + color: theme.palette.border.dark, + padding: theme.spacing(1), + "&:focus-visible": { + outline: `2px solid ${theme.palette.primary.main}`, + outlineOffset: `2px`, + }, + "& .MuiTouchRipple-root": { + pointerEvents: "none", + display: "none", + }, + }} + > + {fieldType === "password" ? : } + + + ); +}; + +HttpAdornment.propTypes = { + https: PropTypes.bool.isRequired, +}; diff --git a/Client/src/Components/Inputs/TextInput/index.jsx b/Client/src/Components/Inputs/TextInput/index.jsx new file mode 100644 index 000000000..d97d1ebac --- /dev/null +++ b/Client/src/Components/Inputs/TextInput/index.jsx @@ -0,0 +1,130 @@ +import { Stack, TextField, Typography } from "@mui/material"; +import { useTheme } from "@emotion/react"; +import { forwardRef, useState, cloneElement } from "react"; +import PropTypes from "prop-types"; + +const getSx = (theme, type, maxWidth) => { + const sx = { + maxWidth: maxWidth, + }; + + if (type === "url") { + return { + ...sx, + "& .MuiInputBase-root": { padding: 0 }, + "& .MuiStack-root": { + borderTopLeftRadius: theme.shape.borderRadius, + borderBottomLeftRadius: theme.shape.borderRadius, + }, + }; + } + return sx; +}; + +const Required = () => { + const theme = useTheme(); + return ( + + * + + ); +}; + +const Optional = ({ optionalLabel }) => { + const theme = useTheme(); + return ( + + {optionalLabel || "(optional)"} + + ); +}; + +Optional.propTypes = { + optionalLabel: PropTypes.string, +}; + +const TextInput = forwardRef( + ( + { + type, + value, + placeholder, + isRequired, + isOptional, + optionalLabel, + onChange, + error = false, + helperText = null, + startAdornment = null, + endAdornment = null, + label = null, + maxWidth = "100%", + }, + ref + ) => { + const [fieldType, setFieldType] = useState(type); + const theme = useTheme(); + return ( + + + {label} + {isRequired && } + {isOptional && } + + + + ); + } +); + +TextInput.displayName = "TextInput"; + +TextInput.propTypes = { + type: PropTypes.string, + value: PropTypes.string, + placeholder: PropTypes.string, + isRequired: PropTypes.bool, + isOptional: PropTypes.bool, + optionalLabel: PropTypes.string, + onChange: PropTypes.func, + error: PropTypes.bool, + helperText: PropTypes.string, + startAdornment: PropTypes.node, + endAdornment: PropTypes.node, + label: PropTypes.string, + maxWidth: PropTypes.string, +}; + +export default TextInput; diff --git a/Client/src/Pages/test.jsx b/Client/src/Pages/test.jsx new file mode 100644 index 000000000..8f5bb464e --- /dev/null +++ b/Client/src/Pages/test.jsx @@ -0,0 +1,192 @@ +import { Stack, Typography } from "@mui/material"; +import Field from "../Components/Inputs/Field"; +import TextInput from "../Components/Inputs/TextInput"; +import { useState, useEffect, useRef } from "react"; +import { HttpAdornment } from "../Components/Inputs/TextInput/Adornments"; +import { PasswordEndAdornment } from "../Components/Inputs/TextInput/Adornments"; +const Test = () => { + const [originalValue, setOriginalValue] = useState(""); + const [originalError, setOriginalError] = useState(""); + + const [newValue, setNewValue] = useState(""); + const [newError, setNewError] = useState(""); + + const [thresholdValue, setThresholdValue] = useState(20); + const [thresholdError, setThresholdError] = useState(""); + + const [thresholdValue2, setThresholdValue2] = useState(20); + const [thresholdError2, setThresholdError2] = useState(""); + + const inputRef = useRef(null); + + useEffect(() => { + if (inputRef.current) { + inputRef.current.focus(); + } + }, []); + + const checkError = (value) => { + if (value !== "clear") { + return "This is an error"; + } + return ""; + }; + + const checkThresholdError = (value) => { + if (value !== 99) { + return "This is a threshold error"; + } + return ""; + }; + const checkThresholdError2 = (value) => { + if (value !== 99) { + return "This is a threshold error 2"; + } + return ""; + }; + + const handleOriginalValue = (e) => { + setOriginalError(checkError(e.target.value)); + setOriginalValue(e.target.value); + }; + + const handleNewValue = (e) => { + setNewError(checkError(e.target.value)); + setNewValue(e.target.value); + }; + + const handleThresholdValue = (e) => { + const parsedVal = parseInt(e.target.value); + setThresholdError(checkThresholdError(parsedVal)); + setThresholdValue(parsedVal); + }; + + const handleThresholdValue2 = (e) => { + const parsedVal = parseInt(e.target.value); + setThresholdError2(checkThresholdError2(parsedVal)); + setThresholdValue2(parsedVal); + }; + + return ( + + + This is a test page for the TextInput component. It is a rationalized Input + component. + + Type anything for an error. + Typing "clear" will clear the error for text based input + Typing "99" will clear the error for threshold based input + + + + + } + onChange={handleNewValue} + error={newError !== ""} + helperText={newError} + /> + + + } + onChange={handleNewValue} + error={newError !== ""} + helperText={newError} + ref={inputRef} + /> + + + + + + Short field for threshold. Easily show/hide error text + + + + {thresholdError} {thresholdError2} + + + ); +}; + +export default Test; diff --git a/Client/src/Utils/Theme/globalTheme.js b/Client/src/Utils/Theme/globalTheme.js index 7737d396e..0839cd0e8 100644 --- a/Client/src/Utils/Theme/globalTheme.js +++ b/Client/src/Utils/Theme/globalTheme.js @@ -209,6 +209,43 @@ const baseTheme = (palette) => ({ }), }, }, + MuiTextField: { + styleOverrides: { + root: ({ theme }) => ({ + "& fieldset": { + borderColor: theme.palette.border.dark, + borderRadius: theme.shape.borderRadius, + }, + "& .MuiInputBase-input": { + height: "var(--env-var-height-2)", + fontSize: "var(--env-var-font-size-medium)", + fontWeight: 400, + color: palette.text.secondary, // or any color from your palette + }, + "& .MuiInputBase-input.MuiOutlinedInput-input": { + padding: "0 var(--env-var-spacing-1-minus) !important", + }, + "& .MuiOutlinedInput-root": { + borderRadius: 4, + }, + "& .MuiOutlinedInput-notchedOutline": { + borderRadius: 4, + }, + "& .MuiFormHelperText-root": { + color: palette.error.main, + opacity: 0.8, + fontSize: "var(--env-var-font-size-medium)", + + marginLeft: 0, + }, + "& .MuiFormHelperText-root.Mui-error": { + opacity: 0.8, + fontSize: "var(--env-var-font-size-medium)", + color: palette.error.main, + }, + }), + }, + }, }, shape: { borderRadius: 2,