Merged PR 83: Frontend Upgrades - The frontend has been upgraded to leverage TypeScript, React 18, and Material UI 5, enhancing code quality, UI consistency, and leveraging the latest features of these technologies.

• Frontend Upgrades - The frontend has been upgraded to leverage TypeScript, React 18, and Material UI 5, enhancing code quality, UI consistency, and leveraging the latest features of these technologies.
      • Build process overhaul - I've switched from react-scripts to react-app-rewired. This gives me more control to tweak the webpack configuration as I need. Plus, it's now set up to handle CommonJS (CJS) modules.
      • TypeScript refactoring - Several components have been rewritten in TypeScript, improving type safety and predictability in our codebase.
master
Tudor Stanciu 2024-07-13 18:49:29 +00:00
commit e80d246fc0
104 changed files with 31721 additions and 11923 deletions

View File

@ -1,7 +1,7 @@
<Project> <Project>
<Import Project="dependencies.props" /> <Import Project="dependencies.props" />
<PropertyGroup> <PropertyGroup>
<Version>1.2.7</Version> <Version>1.3.0</Version>
<Authors>Tudor Stanciu</Authors> <Authors>Tudor Stanciu</Authors>
<Company>STA</Company> <Company>STA</Company>
<PackageTags>NetworkResurrector</PackageTags> <PackageTags>NetworkResurrector</PackageTags>

View File

@ -201,4 +201,15 @@
• Updated menu component to permanently display the selected item. • Updated menu component to permanently display the selected item.
</Content> </Content>
</Note> </Note>
<Note>
<Version>1.3.0</Version>
<Date>2024-03-31 23:24</Date>
<Content>
Major enhancements
• Unified repository structure - The backend and frontend codebases have been consolidated into a single repository, improving code management and cross-component consistency.
• Frontend Upgrades - The frontend has been upgraded to leverage TypeScript, React 18, and Material UI 5, enhancing code quality, UI consistency, and leveraging the latest features of these technologies.
• Build process overhaul - I've switched from react-scripts to react-app-rewired. This gives me more control to tweak the webpack configuration as I need. Plus, it's now set up to handle CommonJS (CJS) modules.
• TypeScript refactoring - Several components have been rewritten in TypeScript, improving type safety and predictability in our codebase.
</Content>
</Note>
</ReleaseNotes> </ReleaseNotes>

34
frontend/.eslintrc Normal file
View File

@ -0,0 +1,34 @@
{
"root": true,
"extends": [
"prettier",
"plugin:prettier/recommended",
"eslint:recommended",
"plugin:react/recommended",
"plugin:@typescript-eslint/eslint-recommended",
"plugin:@typescript-eslint/recommended"
],
"parser": "@typescript-eslint/parser",
"plugins": ["@typescript-eslint", "prettier", "react", "react-hooks"],
"rules": {
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"@typescript-eslint/no-non-null-assertion": "off",
"@typescript-eslint/ban-ts-comment": "off",
"@typescript-eslint/no-explicit-any": "off",
"no-debugger": "warn"
},
"ignorePatterns": ["**/public"],
"settings": {
"react": {
"version": "detect"
}
},
"env": {
"browser": true,
"node": true
},
"globals": {
"JSX": true
}
}

View File

@ -1,38 +0,0 @@
{
"env": {
"browser": true,
"es6": true,
"node": true
},
"extends": [
"eslint:recommended",
"plugin:react/recommended",
"plugin:jest/recommended"
],
"parser": "@babel/eslint-parser",
"parserOptions": {
"requireConfigFile": false,
"babelOptions": {
"presets": ["@babel/preset-react"]
},
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
},
"sourceType": "module"
},
"plugins": ["react", "react-hooks", "jest"],
"ignorePatterns": ["**/public"],
"rules": {
"indent": 0,
"linebreak-style": 0,
"quotes": 0,
"semi": 0,
"no-console": 0,
"no-debugger": "warn",
"react/display-name": "off",
"react-hooks/rules-of-hooks": "error",
"react-hooks/exhaustive-deps": "warn",
"no-unused-vars": [1, { "args": "after-used", "argsIgnorePattern": "^_" }]
}
}

8
frontend/.prettierrc Normal file
View File

@ -0,0 +1,8 @@
{
"bracketSpacing": true,
"arrowParens": "avoid",
"printWidth": 120,
"trailingComma": "none",
"singleQuote": false,
"endOfLine": "auto"
}

View File

@ -1,7 +0,0 @@
{
"trailingComma": "none",
"tabWidth": 2,
"semi": true,
"singleQuote": false,
"arrowParens": "avoid"
}

View File

@ -0,0 +1,41 @@
const getCacheIdentifier = require("react-dev-utils/getCacheIdentifier");
const shouldUseSourceMap = process.env.GENERATE_SOURCEMAP !== "false";
module.exports = function override(config, webpackEnv) {
console.log("overriding webpack config...");
const isEnvDevelopment = webpackEnv === "development";
const isEnvProduction = webpackEnv === "production";
const loaders = config.module.rules[1].oneOf;
loaders.splice(loaders.length - 1, 0, {
test: /\.(js|mjs|cjs)$/,
exclude: /@babel(?:\/|\\{1,2})runtime/,
loader: require.resolve("babel-loader"),
options: {
babelrc: false,
configFile: false,
compact: false,
presets: [[require.resolve("babel-preset-react-app/dependencies"), { helpers: true }]],
cacheDirectory: true,
// See #6846 for context on why cacheCompression is disabled
cacheCompression: false,
// @remove-on-eject-begin
cacheIdentifier: getCacheIdentifier(isEnvProduction ? "production" : isEnvDevelopment && "development", [
"babel-plugin-named-asset-import",
"babel-preset-react-app",
"react-dev-utils",
"react-scripts"
]),
// @remove-on-eject-end
// Babel sourcemaps are needed for debugging into node_modules
// code. Without the options below, debuggers like VSCode
// show incorrect code and set breakpoints on the wrong lines.
sourceMaps: shouldUseSourceMap,
inputSourceMap: shouldUseSourceMap
}
});
return config;
};

View File

@ -1,6 +0,0 @@
{
"compilerOptions": {
"baseUrl": "src"
},
"include": ["src"]
}

40760
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "network-resurrector-frontend", "name": "network-resurrector-frontend",
"version": "1.2.7", "version": "1.3.0",
"description": "Frontend component of Network resurrector system", "description": "Frontend component of Network resurrector system",
"author": { "author": {
"name": "Tudor Stanciu", "name": "Tudor Stanciu",
@ -9,41 +9,49 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://lab.code-rove.com/gitea/tudor.stanciu/network-resurrector-frontend" "url": "https://lab.code-rove.com/gitea/tudor.stanciu/network-resurrector"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
"@emotion/react": "^11.11.1",
"@emotion/styled": "^11.11.0",
"@flare/js-utils": "^1.1.0", "@flare/js-utils": "^1.1.0",
"@flare/tuitio-client-react": "^1.2.6", "@flare/tuitio-client-react": "^1.2.10",
"@material-ui/core": "^4.11.2", "@mui/icons-material": "^5.14.16",
"@material-ui/icons": "^4.11.2", "@mui/lab": "^5.0.0-alpha.169",
"@material-ui/lab": "^4.0.0-alpha.61", "@mui/material": "^5.14.16",
"axios": "^1.3.4", "axios": "^1.6.8",
"i18next": "^19.4.4", "i18next": "^22.4.15",
"i18next-browser-languagedetector": "^4.1.1", "i18next-browser-languagedetector": "^7.0.1",
"i18next-http-backend": "^1.4.0", "i18next-http-backend": "^2.2.0",
"moment": "^2.29.3", "moment": "^2.29.4",
"react": "^17.0.1", "react": "^18.2.0",
"react-dom": "^17.0.1", "react-dom": "^18.2.0",
"react-flags": "^0.1.18", "react-i18next": "^12.2.2",
"react-i18next": "^11.4.0",
"react-lazylog": "^4.5.3", "react-lazylog": "^4.5.3",
"react-router-dom": "^5.2.0", "react-router-dom": "^6.10.0",
"react-scripts": "4.0.1", "react-toastify": "^9.1.3",
"react-toastify": "^6.2.0" "react-world-flags": "^1.6.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/eslint-parser": "^7.16.5", "@babel/plugin-proposal-private-property-in-object": "^7.21.11",
"@testing-library/jest-dom": "^5.11.6", "@types/react": "^18.2.33",
"@testing-library/react": "^11.2.2", "@types/react-dom": "^18.2.14",
"@testing-library/user-event": "^12.5.0", "@types/react-world-flags": "^1.4.5",
"prettier": "^2.5.1" "eslint": "^8.34.0",
"eslint-config-prettier": "^8.6.0",
"eslint-plugin-prettier": "^4.2.1",
"eslint-plugin-react": "^7.32.2",
"eslint-plugin-react-hooks": "^4.6.0",
"prettier": "^2.8.4",
"react-app-rewired": "^2.2.1",
"typescript": "^4.9.5"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-app-rewired start",
"build": "react-scripts build", "build": "react-app-rewired build",
"test": "react-scripts test", "test": "react-app-rewired test",
"eject": "react-scripts eject" "eject": "react-app-rewired eject"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
@ -53,17 +61,14 @@
}, },
"browserslist": { "browserslist": {
"production": [ "production": [
">0.3%", ">0.2%",
"not dead", "not dead",
"not op_mini all" "not op_mini all"
], ],
"development": [ "development": [
"last 1 chrome version", "last 1 chrome version",
"last 1 firefox version", "last 1 firefox version",
"last 1 safari version", "last 1 safari version"
">0.3%",
"not dead",
"not op_mini all"
] ]
} }
} }

View File

@ -1,13 +0,0 @@
REACT v4:
https://v4.mui.com/getting-started/installation/
https://v4.mui.com/components/material-icons/
Theming:
https://v4.mui.com/customization/palette/ (Dark theme)
Add in settings:
- ping interval
- notifications
- test notification mechanism
- permissions
- permissions hierarchy

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 937 B

View File

@ -1,73 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import App from "./App";
import { BrowserRouter, Switch, Redirect, Route } from "react-router-dom";
import { useTuitioToken } from "@flare/tuitio-client-react";
import LoginContainer from "../features/login/components/LoginContainer";
const PrivateRoute = ({ component, ...rest }) => {
const { valid } = useTuitioToken();
return (
<Route
{...rest}
render={props =>
valid ? (
React.createElement(component, props)
) : (
<Redirect
to={{
pathname: "/login",
state: {
from: props.location
}
}}
/>
)
}
/>
);
};
PrivateRoute.propTypes = {
component: PropTypes.func.isRequired,
location: PropTypes.object
};
const PublicRoute = ({ component, ...rest }) => {
const { valid } = useTuitioToken();
return (
<Route
{...rest}
render={props =>
valid ? (
<Redirect
to={{
pathname: "/"
}}
/>
) : (
React.createElement(component, props)
)
}
/>
);
};
PublicRoute.propTypes = {
component: PropTypes.func.isRequired
};
const AppRouter = () => {
return (
<BrowserRouter basename={process.env.PUBLIC_URL || ""}>
<Switch>
<Route exact path="/" render={() => <Redirect to="/dashboard" />} />
<PublicRoute path="/login" component={LoginContainer} />
<PrivateRoute path="/" component={App} />
</Switch>
</BrowserRouter>
);
};
export default AppRouter;

View File

@ -0,0 +1,70 @@
import React, { useMemo } from "react";
import App from "./App";
import { BrowserRouter, Navigate, Route, Routes, useLocation } from "react-router-dom";
import { useTuitioToken } from "@flare/tuitio-client-react";
import LoginContainer from "../features/login/components/LoginContainer";
const PrivateRoute = ({ children }: { children: JSX.Element }): JSX.Element => {
const { valid } = useTuitioToken();
const location = useLocation();
return valid ? (
children
) : (
<Navigate
to="/login"
state={{
from: location.pathname,
search: location.search
}}
/>
);
};
const PublicRoute = ({ children }: { children: JSX.Element }): JSX.Element => {
const location = useLocation();
const { valid } = useTuitioToken();
const to = useMemo(() => {
if (location.state?.from) {
return location.state.from + location.state.search;
}
return "/";
}, [location.state?.from, location.state?.search]);
return valid ? (
<Navigate
to={to}
state={{
from: location.pathname
}}
/>
) : (
children
);
};
const AppRouter: React.FC = () => {
return (
<BrowserRouter basename={process.env.PUBLIC_URL || ""}>
<Routes>
<Route path="/" element={<Navigate to="/dashboard" />} />
<Route
path="/login"
element={
<PublicRoute>
<LoginContainer />
</PublicRoute>
}
/>
<Route
path="/*"
element={
<PrivateRoute>
<App />
</PrivateRoute>
}
/>
</Routes>
</BrowserRouter>
);
};
export default AppRouter;

View File

@ -1,9 +1,9 @@
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import Typography from "@material-ui/core/Typography"; import Typography from "@mui/material/Typography";
import { makeStyles } from "@material-ui/core/styles"; import { useTheme } from "@mui/material/styles";
const useStyles = makeStyles(theme => ({ const getStyles = theme => ({
panel: { panel: {
display: "flex" display: "flex"
}, },
@ -13,21 +13,19 @@ const useStyles = makeStyles(theme => ({
data: { data: {
fontWeight: theme.typography.fontWeightMedium fontWeight: theme.typography.fontWeightMedium
} }
})); });
const DataLabel = ({ label, data }) => { const DataLabel = ({ label, data }) => {
const classes = useStyles(); const theme = useTheme();
const lbl = useMemo( const lbl = useMemo(() => (label.endsWith(":") ? label : `${label}:`), [label]);
() => (label.endsWith(":") ? label : `${label}:`), const styles = getStyles(theme);
[label]
);
return ( return (
<div className={classes.panel}> <div style={styles.panel}>
<Typography variant="body2" className={classes.label}> <Typography variant="body2" sx={styles.label}>
{lbl} {lbl}
</Typography> </Typography>
<Typography variant="body2" className={classes.data}> <Typography variant="body2" sx={styles.data}>
{data} {data}
</Typography> </Typography>
</div> </div>

View File

@ -0,0 +1,12 @@
import React, { CSSProperties, useMemo } from "react";
import Flag, { FlagProps } from "react-world-flags";
const defaultStyle: CSSProperties = { height: "1.2rem", width: "2rem", objectFit: "cover" };
const FlagIcon: React.FC<FlagProps> = ({ code, style, ...rest }) => {
const localCode = useMemo(() => (code === "en" ? "gb" : code), [code]);
const localStyle = useMemo(() => ({ ...defaultStyle, ...style }), [style]);
return <Flag {...rest} code={localCode} style={localStyle} />;
};
export default FlagIcon;

View File

@ -1,8 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import ToggleButton from "@material-ui/lab/ToggleButton"; import { ToggleButtonGroup, ToggleButton } from "@mui/material";
import ToggleButtonGroup from "@material-ui/lab/ToggleButtonGroup"; import { Tooltip } from "@mui/material";
import { Tooltip } from "@material-ui/core";
const NavigationButtons = ({ tabs, onTabChange }) => { const NavigationButtons = ({ tabs, onTabChange }) => {
const [selected, setSelected] = useState(tabs[0].code); const [selected, setSelected] = useState(tabs[0].code);
@ -13,19 +12,9 @@ const NavigationButtons = ({ tabs, onTabChange }) => {
}; };
return ( return (
<ToggleButtonGroup <ToggleButtonGroup size="small" value={selected} exclusive onChange={handleTabSelection}>
size="small"
value={selected}
exclusive
onChange={handleTabSelection}
>
{tabs.map(tab => ( {tabs.map(tab => (
<ToggleButton <ToggleButton key={tab.code} value={tab.code} aria-label="navigation buttons" disabled={selected === tab.code}>
key={tab.code}
value={tab.code}
aria-label="navigation buttons"
disabled={selected === tab.code}
>
<Tooltip title={tab.tooltip}> <Tooltip title={tab.tooltip}>
<tab.icon color="primary" /> <tab.icon color="primary" />
</Tooltip> </Tooltip>
@ -36,9 +25,7 @@ const NavigationButtons = ({ tabs, onTabChange }) => {
}; };
NavigationButtons.propTypes = { NavigationButtons.propTypes = {
tabs: PropTypes.arrayOf( tabs: PropTypes.arrayOf(PropTypes.shape({ code: PropTypes.string.isRequired })).isRequired,
PropTypes.shape({ code: PropTypes.string.isRequired })
).isRequired,
onTabChange: PropTypes.func onTabChange: PropTypes.func
}; };

View File

@ -1,28 +1,24 @@
import React from "react"; import React from "react";
import { makeStyles } from "@material-ui/core/styles"; import { Alert, AlertTitle, Box } from "@mui/material";
import { Alert, AlertTitle } from "@material-ui/lab";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const useStyles = makeStyles(theme => ({
alert: {
width: "100%",
"& > * + *": {
marginTop: theme.spacing(1)
}
}
}));
const NotAllowed = () => { const NotAllowed = () => {
const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<div className={classes.alert}> <Box
sx={{
width: "100%",
"& > * + *": {
marginTop: 1
}
}}
>
<Alert variant="outlined" severity="error"> <Alert variant="outlined" severity="error">
<AlertTitle>{t("Announcements.NotAllowed.Title")}</AlertTitle> <AlertTitle>{t("Announcements.NotAllowed.Title")}</AlertTitle>
{t("Announcements.NotAllowed.Message")} {t("Announcements.NotAllowed.Message")}
</Alert> </Alert>
</div> </Box>
); );
}; };

View File

@ -1,14 +1,13 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/core/styles"; import { Typography, Box } from "@mui/material";
import { Typography } from "@material-ui/core";
const useStyles = makeStyles(theme => ({ const styles = {
box: { box: {
display: "flex", display: "flex",
justifyContent: "space-between", justifyContent: "space-between",
marginBottom: theme.spacing(1), marginBottom: 1,
marginTop: theme.spacing(0) marginTop: 0
}, },
title: { title: {
display: "flex", display: "flex",
@ -16,22 +15,22 @@ const useStyles = makeStyles(theme => ({
flexDirection: "column", flexDirection: "column",
minHeight: "40px" minHeight: "40px"
}, },
titleText: { textTransform: "uppercase" } titleText: {
})); textTransform: "uppercase"
}
};
const PageTitle = ({ text, toolBar, navigation }) => { const PageTitle = ({ text, toolBar, navigation }) => {
const classes = useStyles();
return ( return (
<div className={classes.box}> <Box sx={styles.box}>
{navigation && navigation} {navigation && navigation}
<div className={classes.title}> <Box sx={styles.title}>
<Typography className={classes.titleText} variant="h3" size="sm"> <Typography sx={styles.titleText} variant="h3" size="sm">
{text} {text}
</Typography> </Typography>
</div> </Box>
{toolBar && toolBar} {toolBar && toolBar}
</div> </Box>
); );
}; };

View File

@ -1,22 +1,20 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Typography } from "@material-ui/core"; import { Typography, Box } from "@mui/material";
import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles(theme => ({ const styles = {
paper: { paper: {
margin: theme.spacing(1) margin: 1
} }
})); };
const PaperTitle = ({ text }) => { const PaperTitle = ({ text }) => {
const classes = useStyles();
return ( return (
<div className={classes.paper}> <Box sx={styles.paper}>
<Typography variant="h5" gutterBottom> <Typography variant="h5" gutterBottom>
{text} {text}
</Typography> </Typography>
</div> </Box>
); );
}; };

View File

@ -1,4 +1,5 @@
import DataLabel from "./DataLabel"; import DataLabel from "./DataLabel";
import PaperTitle from "./PaperTitle"; import PaperTitle from "./PaperTitle";
import FlagIcon from "./FlagIcon";
export { DataLabel, PaperTitle }; export { DataLabel, PaperTitle, FlagIcon };

View File

@ -1,22 +1,16 @@
import React, { useState } from "react"; import React, { useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { import { InputAdornment, TextField, IconButton } from "@mui/material";
InputAdornment, import { Visibility, VisibilityOff, LockOutlined } from "@mui/icons-material";
TextField,
makeStyles,
IconButton
} from "@material-ui/core";
import { Visibility, VisibilityOff, LockOutlined } from "@material-ui/icons";
const useStyles = makeStyles(theme => ({ const styles = {
margin: { margin: {
margin: theme.spacing(1) margin: 1
} }
})); };
const PasswordField = ({ label, ...rest }) => { const PasswordField = ({ label, ...rest }) => {
const [showPassword, setShowPassword] = useState(false); const [showPassword, setShowPassword] = useState(false);
const classes = useStyles();
const handleClickShowPassword = () => { const handleClickShowPassword = () => {
setShowPassword(!showPassword); setShowPassword(!showPassword);
@ -28,7 +22,7 @@ const PasswordField = ({ label, ...rest }) => {
return ( return (
<TextField <TextField
className={classes.margin} sx={styles.margin}
label={label || "Password"} label={label || "Password"}
{...rest} {...rest}
type={showPassword ? "text" : "password"} type={showPassword ? "text" : "password"}

View File

@ -0,0 +1,19 @@
import React from "react";
import * as MuiIcons from "./list";
import { Icon as MuiIcon, IconProps } from "@mui/material";
interface Props extends IconProps {
code?: string | null;
fallback?: JSX.Element;
}
const DynamicIcon: React.FC<Props> = ({ code, fallback, ...res }) => {
if (code && code in MuiIcons) {
const Icon = MuiIcons[code as keyof typeof MuiIcons] as typeof MuiIcon;
return <Icon {...res} />;
} else {
return <>{fallback ?? ""}</>;
}
};
export default DynamicIcon;

View File

@ -0,0 +1,4 @@
import DynamicIcon from "./DynamicIcon";
export * from "./list";
export { DynamicIcon };

View File

@ -0,0 +1 @@
export { Home, Dashboard, Dns, DeviceHub, Build, Settings, FeaturedPlayList, Info } from "@mui/icons-material";

View File

@ -1,34 +0,0 @@
import React, { useState } from "react";
import { makeStyles } from "@material-ui/core/styles";
import AppRoutes from "./AppRoutes";
import TopBar from "./TopBar";
import Sidebar from "./Sidebar";
import styles from "./styles";
const useStyles = makeStyles(styles);
const AppLayout = () => {
const [open, setOpen] = useState(false);
const classes = useStyles();
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (
<div className={classes.root}>
<TopBar open={open} handleDrawerOpen={handleDrawerOpen} />
<Sidebar open={open} handleDrawerClose={handleDrawerClose} />
<main className={classes.content}>
<div className={classes.toolbar} />
<AppRoutes />
</main>
</div>
);
};
export default AppLayout;

View File

@ -0,0 +1,54 @@
import React, { useState } from "react";
import { styled } from "@mui/material/styles";
import AppRoutes from "./AppRoutes";
import TopBar from "./TopBar";
import Sidebar from "./SideBar";
import { drawerWidth } from "./constants";
import { Box } from "@mui/material";
const DrawerHeader = styled("div")(({ theme }) => ({
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar
}));
const AppLayout: React.FC = () => {
const [open, setOpen] = useState(false);
const handleDrawerOpen = () => {
setOpen(true);
};
const handleDrawerClose = () => {
setOpen(false);
};
return (
<Box sx={{ display: "flex", minHeight: "100vh" }}>
<TopBar open={open} onDrawerOpen={handleDrawerOpen} />
<Sidebar open={open} onDrawerOpen={handleDrawerOpen} onDrawerClose={handleDrawerClose} />
<Box
component="main"
sx={{
flexGrow: 1,
display: "flex",
flexDirection: "column",
paddingTop: 2,
paddingBottom: 1,
paddingLeft: 1,
paddingRight: 1,
width: `calc(100% - ${drawerWidth}px)`
}}
>
<DrawerHeader />
<AppRoutes />
</Box>
</Box>
);
};
export default AppLayout;

View File

@ -1,5 +1,5 @@
import React from "react"; import React from "react";
import { Route, Switch } from "react-router-dom"; import { Route, Routes } from "react-router-dom";
import PageNotFound from "./PageNotFound"; import PageNotFound from "./PageNotFound";
import NetworkContainer from "../../features/network/components/NetworkContainer"; import NetworkContainer from "../../features/network/components/NetworkContainer";
import SystemContainer from "../../features/system/SystemContainer"; import SystemContainer from "../../features/system/SystemContainer";
@ -8,17 +8,17 @@ import DashboardContainer from "../../features/dashboard/DashboardContainer";
import UserProfileContainer from "../../features/user/profile/card/UserProfileContainer"; import UserProfileContainer from "../../features/user/profile/card/UserProfileContainer";
import AboutContainer from "../../features/about/AboutContainer"; import AboutContainer from "../../features/about/AboutContainer";
const AppRoutes = () => { const AppRoutes: React.FC = () => {
return ( return (
<Switch> <Routes>
<Route exact path="/dashboard" component={DashboardContainer} /> <Route path="/dashboard" element={<DashboardContainer />} />
<Route exact path="/user-profile" component={UserProfileContainer} /> <Route path="/user-profile" element={<UserProfileContainer />} />
<Route exact path="/machines" component={NetworkContainer} /> <Route path="/machines" element={<NetworkContainer />} />
<Route exact path="/system" component={SystemContainer} /> <Route path="/system" element={<SystemContainer />} />
<Route exact path="/settings" component={SettingsContainer} /> <Route path="/settings" element={<SettingsContainer />} />
<Route exact path="/about" component={AboutContainer} /> <Route path="/about" element={<AboutContainer />} />
<Route component={PageNotFound} /> <Route path="/*" element={<PageNotFound />} />
</Switch> </Routes>
); );
}; };

View File

@ -1,9 +1,6 @@
import React from "react"; import React from "react";
import { IconButton } from "@material-ui/core"; import { IconButton } from "@mui/material";
import { import { Brightness2 as MoonIcon, WbSunny as SunIcon } from "@mui/icons-material";
Brightness2 as MoonIcon,
WbSunny as SunIcon
} from "@material-ui/icons";
import { useApplicationTheme } from "../../providers/ThemeProvider"; import { useApplicationTheme } from "../../providers/ThemeProvider";
const LightDarkToggle = () => { const LightDarkToggle = () => {
@ -12,11 +9,7 @@ const LightDarkToggle = () => {
const handleChange = () => onDarkModeChanged(!isDark); const handleChange = () => onDarkModeChanged(!isDark);
return ( return (
<IconButton <IconButton aria-label="light-dark-toggle" color="inherit" onClick={handleChange}>
aria-label="light-dark-toggle"
color="inherit"
onClick={handleChange}
>
{isDark ? <SunIcon /> : <MoonIcon />} {isDark ? <SunIcon /> : <MoonIcon />}
</IconButton> </IconButton>
); );

View File

@ -0,0 +1,84 @@
import React from "react";
import { SxProps, Theme } from "@mui/material/styles";
import List from "@mui/material/List";
import ListItem from "@mui/material/ListItem";
import ListItemButton from "@mui/material/ListItemButton";
import ListItemIcon from "@mui/material/ListItemIcon";
import ListItemText from "@mui/material/ListItemText";
import Badge from "@mui/material/Badge";
import { ExpandLess, ExpandMore } from "@mui/icons-material";
import { Collapse, Divider, Tooltip } from "@mui/material";
type MenuItemProps = {
open: boolean;
icon: React.ReactNode;
label: string;
onClick?: () => void;
subMenus?: MenuItemProps[];
sx?: SxProps<Theme>;
};
const MenuItem: React.FC<MenuItemProps> = ({ open, icon, label, onClick, subMenus, sx }) => {
const [openSubMenu, setOpenSubMenu] = React.useState(false);
const handleSubMenuToggle = () => {
setOpenSubMenu(!openSubMenu);
};
return (
<>
<ListItem disablePadding sx={{ display: "block" }}>
<Tooltip title={open ? undefined : label} placement="right" arrow>
<ListItemButton
sx={{
minHeight: 48,
justifyContent: open ? "initial" : "center",
px: 2.5,
...sx
}}
onClick={onClick ? onClick : handleSubMenuToggle}
>
<ListItemIcon
sx={{
minWidth: 0,
mr: open ? 3 : "auto",
justifyContent: "center"
}}
>
{open || !subMenus ? (
icon
) : (
<Badge badgeContent={openSubMenu ? <ExpandLess fontSize="small" /> : <ExpandMore fontSize="small" />}>
{icon}
</Badge>
)}
</ListItemIcon>
<ListItemText
primary={label}
sx={{ opacity: open ? 1 : 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}
/>
{subMenus && open && (
<ListItemIcon sx={{ minWidth: 0, ml: "auto" }}>
{openSubMenu ? <ExpandLess /> : <ExpandMore />}
</ListItemIcon>
)}
</ListItemButton>
</Tooltip>
</ListItem>
{subMenus && (
<Collapse in={openSubMenu} timeout="auto" unmountOnExit>
<List component="div" disablePadding>
{subMenus.map((subItem, index) => (
<React.Fragment key={index}>
<MenuItem key={index} {...subItem} sx={{ marginLeft: subItem.open ? 2 : 0 }} />
{index === subMenus.length - 1 && <Divider />}
</React.Fragment>
))}
</List>
</Collapse>
)}
</>
);
};
export default MenuItem;

View File

@ -1,28 +1,17 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { import { IconButton, Menu, MenuItem, Typography, ListItemIcon } from "@mui/material";
IconButton, import AccountCircle from "@mui/icons-material/AccountCircle";
Menu, import ExitToAppIcon from "@mui/icons-material/ExitToApp";
MenuItem, import AccountBoxIcon from "@mui/icons-material/AccountBox";
Typography, import SettingsIcon from "@mui/icons-material/Settings";
ListItemIcon import { useNavigate } from "react-router-dom";
} from "@material-ui/core";
import AccountCircle from "@material-ui/icons/AccountCircle";
import ExitToAppIcon from "@material-ui/icons/ExitToApp";
import AccountBoxIcon from "@material-ui/icons/AccountBox";
import SettingsIcon from "@material-ui/icons/Settings";
import { useHistory } from "react-router-dom";
import { useTuitioClient } from "@flare/tuitio-client-react"; import { useTuitioClient } from "@flare/tuitio-client-react";
import { useToast } from "../../hooks"; import { useToast } from "../../hooks";
import styles from "./styles";
import { makeStyles } from "@material-ui/core/styles";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const useStyles = makeStyles(styles);
const ProfileButton = () => { const ProfileButton = () => {
const history = useHistory(); const navigate = useNavigate();
const { error } = useToast(); const { error } = useToast();
const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
const { logout } = useTuitioClient({ const { logout } = useTuitioClient({
@ -69,28 +58,28 @@ const ProfileButton = () => {
> >
<MenuItem <MenuItem
onClick={() => { onClick={() => {
history.push("/user-profile"); navigate("/user-profile");
handleClose(); handleClose();
}} }}
> >
<ListItemIcon className={classes.menuItemIcon}> <ListItemIcon sx={{ minWidth: "26px" }}>
<AccountBoxIcon fontSize="small" /> <AccountBoxIcon fontSize="small" />
</ListItemIcon> </ListItemIcon>
<Typography variant="inherit">{t("User.Profile.Label")}</Typography> <Typography variant="inherit">{t("User.Profile.Label")}</Typography>
</MenuItem> </MenuItem>
<MenuItem <MenuItem
onClick={() => { onClick={() => {
history.push("/settings"); navigate("/settings");
handleClose(); handleClose();
}} }}
> >
<ListItemIcon className={classes.menuItemIcon}> <ListItemIcon sx={{ minWidth: "26px" }}>
<SettingsIcon fontSize="small" /> <SettingsIcon fontSize="small" />
</ListItemIcon> </ListItemIcon>
<Typography variant="inherit">{t("User.Settings")}</Typography> <Typography variant="inherit">{t("User.Settings")}</Typography>
</MenuItem> </MenuItem>
<MenuItem onClick={logout}> <MenuItem onClick={logout}>
<ListItemIcon className={classes.menuItemIcon}> <ListItemIcon sx={{ minWidth: "26px" }}>
<ExitToAppIcon fontSize="small" /> <ExitToAppIcon fontSize="small" />
</ListItemIcon> </ListItemIcon>
<Typography variant="inherit">{t("User.Logout")}</Typography> <Typography variant="inherit">{t("User.Logout")}</Typography>

View File

@ -1,9 +1,6 @@
import React from "react"; import React from "react";
import { IconButton } from "@material-ui/core"; import { IconButton } from "@mui/material";
import { import { Visibility as VisibilityIcon, VisibilityOff as VisibilityOffIcon } from "@mui/icons-material";
Visibility as VisibilityIcon,
VisibilityOff as VisibilityOffIcon
} from "@material-ui/icons";
import { useSensitiveInfo } from "../../hooks"; import { useSensitiveInfo } from "../../hooks";
const SensitiveInfoToggle = () => { const SensitiveInfoToggle = () => {
@ -12,11 +9,7 @@ const SensitiveInfoToggle = () => {
const handleChange = () => onSensitiveInfoEnabled(!enabled); const handleChange = () => onSensitiveInfoEnabled(!enabled);
return ( return (
<IconButton <IconButton aria-label="sensitive-info-toggle" color="inherit" onClick={handleChange}>
aria-label="sensitive-info-toggle"
color="inherit"
onClick={handleChange}
>
{enabled ? <VisibilityOffIcon /> : <VisibilityIcon />} {enabled ? <VisibilityOffIcon /> : <VisibilityIcon />}
</IconButton> </IconButton>
); );

View File

@ -0,0 +1,112 @@
import * as React from "react";
import { styled, Theme, CSSObject } from "@mui/material/styles";
import MuiDrawer from "@mui/material/Drawer";
import List from "@mui/material/List";
import Divider from "@mui/material/Divider";
import IconButton from "@mui/material/IconButton";
import ChevronLeftIcon from "@mui/icons-material/ChevronLeft";
import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import { useNavigate } from "react-router-dom";
import { drawerWidth } from "./constants";
import MenuItem from "./MenuItem";
import { useTranslation } from "react-i18next";
import { menu } from "./constants";
const openedMixin = (theme: Theme): CSSObject => ({
width: drawerWidth,
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen
}),
overflowX: "hidden"
});
const closedMixin = (theme: Theme): CSSObject => ({
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
}),
overflowX: "hidden",
width: `calc(${theme.spacing(7)} + 1px)`,
[theme.breakpoints.up("sm")]: {
width: `calc(${theme.spacing(8)} + 1px)`
}
});
const DrawerHeader = styled("div")(({ theme }) => ({
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar
}));
const Drawer = styled(MuiDrawer, { shouldForwardProp: prop => prop !== "open" })(({ theme, open }) => ({
width: drawerWidth,
flexShrink: 0,
whiteSpace: "nowrap",
boxSizing: "border-box",
...(open && {
...openedMixin(theme),
"& .MuiDrawer-paper": openedMixin(theme)
}),
...(!open && {
...closedMixin(theme),
"& .MuiDrawer-paper": closedMixin(theme)
})
}));
type SideBarProps = {
open: boolean;
onDrawerOpen: () => void;
onDrawerClose: () => void;
};
const SideBar: React.FC<SideBarProps> = ({ open, onDrawerOpen, onDrawerClose }) => {
const navigate = useNavigate();
const { t } = useTranslation();
menu.sort((a, b) => (a.order || 0) - (b.order || 0));
return (
<Drawer variant="permanent" open={open}>
<DrawerHeader>
<IconButton onClick={open ? onDrawerClose : onDrawerOpen}>
{open ? <ChevronLeftIcon /> : <ChevronRightIcon />}
</IconButton>
</DrawerHeader>
<Divider />
{menu.map((section, index) => {
const isLast = index === menu.length - 1;
return (
<React.Fragment key={`section-${section.order}`}>
<List>
{section.items
.sort((i1, i2) => i1.order - i2.order)
.map(item => (
<MenuItem
key={`${item.code}-${index}`}
open={open}
icon={item.icon}
label={t(item.name)}
onClick={item.subMenus ? undefined : () => navigate(item.route)}
subMenus={item.subMenus?.map(si => ({
open,
icon: si.icon,
label: t(si.name),
onClick: () => navigate(si.route)
}))}
/>
))}
</List>
{!isLast && <Divider />}
</React.Fragment>
);
})}
</Drawer>
);
};
export default SideBar;

View File

@ -1,162 +0,0 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import clsx from "clsx";
import { makeStyles, useTheme } from "@material-ui/core/styles";
import {
Drawer,
List,
Divider,
IconButton,
ListItemIcon,
ListItemText
} from "@material-ui/core";
import ChevronLeftIcon from "@material-ui/icons/ChevronLeft";
import ChevronRightIcon from "@material-ui/icons/ChevronRight";
import ListItem from "@material-ui/core/ListItem";
import BuildIcon from "@material-ui/icons/Build";
import DnsIcon from "@material-ui/icons/Dns";
import DeviceHubIcon from "@material-ui/icons/DeviceHub";
import SettingsIcon from "@material-ui/icons/Settings";
import DashboardIcon from "@material-ui/icons/Dashboard";
import FeaturedPlayListIcon from "@material-ui/icons/FeaturedPlayList";
import { useHistory } from "react-router-dom";
import { useTranslation } from "react-i18next";
import styles from "./styles";
const useStyles = makeStyles(styles);
const menu = [
{
order: 0,
items: [
{
code: "dashboard",
name: "Menu.Dashboard",
route: "/dashboard",
icon: <DashboardIcon />,
order: 0
},
{
code: "machines",
name: "Menu.Machines",
route: "/machines",
icon: <DnsIcon />,
order: 1
},
{
code: "system",
name: "Menu.System",
route: "/system",
icon: <DeviceHubIcon />,
order: 2
}
]
},
{
order: 1,
items: [
{
code: "administration",
name: "Menu.Administration",
route: "/administration",
icon: <BuildIcon />,
order: 0
},
{
code: "settings",
name: "Menu.Settings",
route: "/settings",
icon: <SettingsIcon />,
order: 1
}
]
},
{
order: 2,
items: [
{
code: "about",
name: "Menu.About",
route: "/about",
icon: <FeaturedPlayListIcon />,
order: 0
}
]
}
];
const sortedMenu = menu.sort((i1, i2) => i1 - i2);
const Sidebar = ({ open, handleDrawerClose }) => {
const [selected, setSelected] = useState(null);
const classes = useStyles();
const theme = useTheme();
const history = useHistory();
const { t } = useTranslation();
const handleClick = route => () => {
setSelected(route);
history.push(route);
};
const isSelected = key => selected === key;
return (
<Drawer
variant="permanent"
className={clsx(classes.drawer, {
[classes.drawerOpen]: open,
[classes.drawerClose]: !open
})}
classes={{
paper: clsx({
[classes.drawerOpen]: open,
[classes.drawerClose]: !open
})
}}
>
<div className={classes.toolbar}>
<IconButton onClick={handleDrawerClose}>
{theme.direction === "rtl" ? (
<ChevronRightIcon />
) : (
<ChevronLeftIcon />
)}
</IconButton>
</div>
<Divider />
{sortedMenu.map((menu, index) => {
const isLast = index === sortedMenu.length - 1;
return (
<React.Fragment key={`menu-${menu.order}`}>
<List>
{menu.items
.sort((i1, i2) => i1 - i2)
.map(item => (
<ListItem
button
key={item.code}
onClick={handleClick(item.route)}
selected={isSelected(item.route)}
>
<ListItemIcon>{item.icon}</ListItemIcon>
<ListItemText primary={t(item.name)} />
</ListItem>
))}
</List>
{!isLast && <Divider />}
</React.Fragment>
);
})}
</Drawer>
);
};
Sidebar.propTypes = {
open: PropTypes.bool.isRequired,
handleDrawerClose: PropTypes.func.isRequired
};
export default Sidebar;

View File

@ -1,52 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import clsx from "clsx";
import { makeStyles } from "@material-ui/core/styles";
import { AppBar, Toolbar, Typography, IconButton } from "@material-ui/core";
import MenuIcon from "@material-ui/icons/Menu";
import ProfileButton from "./ProfileButton";
import LightDarkToggle from "./LightDarkToggle";
import SensitiveInfoToggle from "./SensitiveInfoToggle";
import styles from "./styles";
const useStyles = makeStyles(styles);
const TopBar = ({ open, handleDrawerOpen }) => {
const classes = useStyles();
return (
<AppBar
position="fixed"
className={clsx(classes.appBar, {
[classes.appBarShift]: open
})}
>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={handleDrawerOpen}
edge="start"
className={clsx(classes.menuButton, {
[classes.hide]: open
})}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap className={classes.title}>
Network resurrector
</Typography>
<SensitiveInfoToggle />
<LightDarkToggle />
<ProfileButton />
</Toolbar>
</AppBar>
);
};
TopBar.propTypes = {
open: PropTypes.bool.isRequired,
handleDrawerOpen: PropTypes.func.isRequired
};
export default TopBar;

View File

@ -0,0 +1,72 @@
import React from "react";
import { Toolbar, Typography, IconButton, Box } from "@mui/material";
import MuiAppBar, { AppBarProps as MuiAppBarProps } from "@mui/material/AppBar";
import MenuIcon from "@mui/icons-material/Menu";
import ProfileButton from "./ProfileButton";
import LightDarkToggle from "./LightDarkToggle";
import SensitiveInfoToggle from "./SensitiveInfoToggle";
import { styled } from "@mui/material/styles";
import { drawerWidth } from "./constants";
interface AppBarProps extends MuiAppBarProps {
open?: boolean;
}
const AppBar = styled(MuiAppBar, {
shouldForwardProp: prop => prop !== "open"
})<AppBarProps>(({ theme, open }) => ({
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(["width", "margin"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
}),
...(open && {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(["width", "margin"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen
})
})
}));
type TopBarProps = {
open: boolean;
onDrawerOpen: () => void;
};
const title = "Network resurrector";
const TopBar: React.FC<TopBarProps> = ({ open, onDrawerOpen }) => {
// const { userInfo } = useTuitioUserInfo();
// to do: Avatar: userInfo.profilePictureUrl
return (
<AppBar position="fixed" open={open}>
<Toolbar>
<IconButton
color="inherit"
aria-label="open drawer"
onClick={onDrawerOpen}
edge="start"
sx={{
marginRight: 5,
...(open && { display: "none" })
}}
>
<MenuIcon />
</IconButton>
<Typography variant="h6" noWrap component="div">
{title}
</Typography>
<Box sx={{ flexGrow: 1 }} />
<Box sx={{ display: { xs: "none", md: "flex" }, gap: 1 }}>
<SensitiveInfoToggle />
<LightDarkToggle />
<ProfileButton />
</Box>
</Toolbar>
{/* <ProgressBar /> */}
</AppBar>
);
};
export default TopBar;

View File

@ -0,0 +1,3 @@
export * from "./menu";
export const drawerWidth = 240;

View File

@ -0,0 +1,98 @@
import React from "react";
import { Dashboard, Dns, DeviceHub, Build, Settings, Info } from "../../icons";
type MenuItem = {
code: string;
name: string;
route: string;
icon: JSX.Element;
order: number;
subMenus?: MenuItem[];
};
type MenuSection = {
order: number;
items: MenuItem[];
};
type Menu = MenuSection[];
const menu: Menu = [
{
order: 0,
items: [
{
code: "dashboard",
name: "Menu.Dashboard",
route: "/dashboard",
icon: <Dashboard />,
order: 0
},
{
code: "machines",
name: "Menu.Machines",
route: "/machines",
icon: <Dns />,
order: 1
},
{
code: "system",
name: "Menu.System",
route: "/system",
icon: <DeviceHub />,
order: 2
}
]
},
{
order: 1,
items: [
{
code: "administration",
name: "Menu.Administration",
route: "/administration",
icon: <Build />,
order: 0,
subMenus: [
{
code: "machines",
name: "Menu.Machines",
route: "/administration/machines",
icon: <Build />,
order: 0
},
{
code: "agents",
name: "Menu.Agents",
route: "/administration/agents",
icon: <Build />,
order: 1
}
]
},
{
code: "settings",
name: "Menu.Settings",
route: "/settings",
icon: <Settings />,
order: 1
}
]
},
{
order: 2,
items: [
{
code: "about",
name: "Menu.About",
route: "/about",
icon: <Info />,
order: 0
}
]
}
];
export type { MenuItem, MenuSection, Menu };
export { menu };
export default menu;

View File

@ -1,71 +0,0 @@
const drawerWidth = 240;
const styles = theme => ({
root: {
display: "flex"
},
appBar: {
zIndex: theme.zIndex.drawer + 1,
transition: theme.transitions.create(["width", "margin"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
})
},
appBarShift: {
marginLeft: drawerWidth,
width: `calc(100% - ${drawerWidth}px)`,
transition: theme.transitions.create(["width", "margin"], {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen
})
},
menuButton: {
marginRight: 36
},
hide: {
display: "none"
},
drawer: {
width: drawerWidth,
flexShrink: 0,
whiteSpace: "nowrap"
},
drawerOpen: {
width: drawerWidth,
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.enteringScreen
})
},
drawerClose: {
transition: theme.transitions.create("width", {
easing: theme.transitions.easing.sharp,
duration: theme.transitions.duration.leavingScreen
}),
overflowX: "hidden",
width: theme.spacing(7) + 1,
[theme.breakpoints.up("sm")]: {
width: theme.spacing(9) + 1
}
},
toolbar: {
display: "flex",
alignItems: "center",
justifyContent: "flex-end",
padding: theme.spacing(0, 1),
// necessary for content to be below app bar
...theme.mixins.toolbar
},
title: {
flexGrow: 1
},
content: {
flexGrow: 1,
padding: theme.spacing(2)
},
menuItemIcon: {
minWidth: "26px"
}
});
export default styles;

View File

@ -1,8 +1,8 @@
import React, { useState, useMemo } from "react"; import React, { useState, useMemo } from "react";
import PageTitle from "../../components/common/PageTitle"; import PageTitle from "../../components/common/PageTitle";
import BubbleChartIcon from "@material-ui/icons/BubbleChart"; import BubbleChartIcon from "@mui/icons-material/BubbleChart";
import NotesIcon from "@material-ui/icons/Notes"; import NotesIcon from "@mui/icons-material/Notes";
import TimelineIcon from "@material-ui/icons/Timeline"; import TimelineIcon from "@mui/icons-material/Timeline";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import AboutSystemContainer from "./system/AboutSystemContainer"; import AboutSystemContainer from "./system/AboutSystemContainer";
import ReleaseNotesContainer from "./releaseNotes/ReleaseNotesContainer"; import ReleaseNotesContainer from "./releaseNotes/ReleaseNotesContainer";
@ -33,24 +33,14 @@ const AboutContainer = () => {
const [tab, setTab] = useState(NavigationTabs.SYSTEM); const [tab, setTab] = useState(NavigationTabs.SYSTEM);
const { t } = useTranslation(); const { t } = useTranslation();
const navigationTabs = useMemo( const navigationTabs = useMemo(() => tabs.map(z => ({ ...z, tooltip: t(z.code) })), [t]);
() => tabs.map(z => ({ ...z, tooltip: t(z.code) })),
[t]
);
return ( return (
<> <>
<PageTitle <PageTitle text={t(tab)} navigation={<NavigationButtons tabs={navigationTabs} onTabChange={setTab} />} />
text={t(tab)}
navigation={
<NavigationButtons tabs={navigationTabs} onTabChange={setTab} />
}
/>
{tab === NavigationTabs.SYSTEM && <AboutSystemContainer />} {tab === NavigationTabs.SYSTEM && <AboutSystemContainer />}
{tab === NavigationTabs.RELEASE_NOTES && <ReleaseNotesContainer />} {tab === NavigationTabs.RELEASE_NOTES && <ReleaseNotesContainer />}
{tab === NavigationTabs.TIMELINE && ( {tab === NavigationTabs.TIMELINE && <ReleaseNotesContainer view="timeline" />}
<ReleaseNotesContainer view="timeline" />
)}
</> </>
); );
}; };

View File

@ -1,17 +1,13 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import Typography from "@material-ui/core/Typography"; import Typography from "@mui/material/Typography";
const ReleaseNote = ({ releaseNote }) => { const ReleaseNote = ({ releaseNote }) => {
return ( return (
<div> <div>
{releaseNote.notes.map(note => { {releaseNote.notes.map(note => {
return ( return (
<Typography <Typography key={releaseNote.notes.indexOf(note)} variant="body2" gutterBottom>
key={releaseNote.notes.indexOf(note)}
variant="body2"
gutterBottom
>
{note} {note}
</Typography> </Typography>
); );

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Grid, Typography } from "@material-ui/core"; import { Grid, Typography } from "@mui/material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const ReleaseNoteSummary = ({ releaseNote, collapsed }) => { const ReleaseNoteSummary = ({ releaseNote, collapsed }) => {

View File

@ -4,8 +4,7 @@ import ReleaseNotesList from "./ReleaseNotesList";
import TimelineComponent from "../timeline/TimelineComponent"; import TimelineComponent from "../timeline/TimelineComponent";
import { routes, get } from "../../../utils/api"; import { routes, get } from "../../../utils/api";
const sort = releases => const sort = releases => releases.sort((a, b) => new Date(b.date) - new Date(a.date));
releases.sort((a, b) => new Date(b.date) - new Date(a.date));
const ReleaseNotesContainer = ({ view }) => { const ReleaseNotesContainer = ({ view }) => {
const [state, setState] = useState({ data: [], loaded: false }); const [state, setState] = useState({ data: [], loaded: false });

View File

@ -1,11 +1,7 @@
import React, { useState } from "react"; import React, { useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { import { Accordion, AccordionSummary, AccordionDetails } from "@mui/material";
Accordion, import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
AccordionSummary,
AccordionDetails
} from "@material-ui/core";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import ReleaseNoteSummary from "./ReleaseNoteSummary"; import ReleaseNoteSummary from "./ReleaseNoteSummary";
import ReleaseNote from "./ReleaseNote"; import ReleaseNote from "./ReleaseNote";
@ -29,18 +25,9 @@ const ReleaseNotesList = ({ releases }) => {
<> <>
{releases.map(release => { {releases.map(release => {
return ( return (
<Accordion <Accordion key={release.version} onChange={handleToggle(release.version)}>
key={release.version} <AccordionSummary expandIcon={<ExpandMoreIcon />} id={`panel-${release.version}-header`}>
onChange={handleToggle(release.version)} <ReleaseNoteSummary releaseNote={release} collapsed={isCollapsed(release.version)} />
>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
id={`panel-${release.version}-header`}
>
<ReleaseNoteSummary
releaseNote={release}
collapsed={isCollapsed(release.version)}
/>
</AccordionSummary> </AccordionSummary>
<AccordionDetails> <AccordionDetails>
<ReleaseNote releaseNote={release} /> <ReleaseNote releaseNote={release} />

View File

@ -1,29 +1,28 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/core/styles"; import Card from "@mui/material/Card";
import Card from "@material-ui/core/Card"; import CardActions from "@mui/material/CardActions";
import CardActions from "@material-ui/core/CardActions"; import CardContent from "@mui/material/CardContent";
import CardContent from "@material-ui/core/CardContent"; import Button from "@mui/material/Button";
import Button from "@material-ui/core/Button"; import Typography from "@mui/material/Typography";
import Typography from "@material-ui/core/Typography";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import OpenInNewIcon from "@material-ui/icons/OpenInNew"; import OpenInNewIcon from "@mui/icons-material/OpenInNew";
const useStyles = makeStyles(theme => ({ const styles = {
bullet: { bullet: {
display: "inline-block", display: "inline-block",
margin: "0 4px", margin: "0 4px",
transform: "scale(1.5)" transform: "scale(1.5)"
}, },
service: { service: {
marginTop: theme.spacing(1) marginTop: 1
} }
})); };
const buttons = [ const buttons = [
{ {
code: "About.System.Services.Frontend", code: "About.System.Services.Frontend",
url: "https://lab.code-rove.com/gitea/tudor.stanciu/network-resurrector-frontend" url: "https://lab.code-rove.com/gitea/tudor.stanciu/network-resurrector/src/branch/master/frontend"
}, },
{ {
code: "About.System.Services.Api", code: "About.System.Services.Api",
@ -40,10 +39,9 @@ const buttons = [
]; ];
const AboutSystemComponent = ({ handleOpenInNewTab }) => { const AboutSystemComponent = ({ handleOpenInNewTab }) => {
const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
const bullet = <span className={classes.bullet}></span>; const bullet = <span style={styles.bullet}></span>;
return ( return (
<Card variant="outlined"> <Card variant="outlined">
@ -51,30 +49,26 @@ const AboutSystemComponent = ({ handleOpenInNewTab }) => {
<Typography variant="h5" gutterBottom> <Typography variant="h5" gutterBottom>
{t("About.System.Description.Title")} {t("About.System.Description.Title")}
</Typography> </Typography>
<Typography color="textSecondary"> <Typography color="textSecondary">{t("About.System.Description.FirstPhrase")}</Typography>
{t("About.System.Description.FirstPhrase")} <Typography color="textSecondary">{t("About.System.Description.SecondPhrase")}</Typography>
</Typography>
<Typography color="textSecondary">
{t("About.System.Description.SecondPhrase")}
</Typography>
<Typography className={classes.service} color="textSecondary"> <Typography sx={styles.service} color="textSecondary">
{bullet} {bullet}
{t("About.System.Description.Frontend")} {t("About.System.Description.Frontend")}
</Typography> </Typography>
<Typography className={classes.service} color="textSecondary"> <Typography sx={styles.service} color="textSecondary">
{bullet} {bullet}
{t("About.System.Description.Api")} {t("About.System.Description.Api")}
</Typography> </Typography>
<Typography className={classes.service} color="textSecondary"> <Typography sx={styles.service} color="textSecondary">
{bullet} {bullet}
{t("About.System.Description.Server")} {t("About.System.Description.Server")}
</Typography> </Typography>
<Typography className={classes.service} color="textSecondary"> <Typography sx={styles.service} color="textSecondary">
{bullet} {bullet}
{t("About.System.Description.Agent")} {t("About.System.Description.Agent")}
</Typography> </Typography>
<Typography className={classes.service} color="textSecondary"> <Typography sx={styles.service} color="textSecondary">
{bullet} {bullet}
{t("About.System.Description.Tuitio")} {t("About.System.Description.Tuitio")}
</Typography> </Typography>

View File

@ -1,35 +1,32 @@
import React from "react"; import React from "react";
import { makeStyles } from "@material-ui/core/styles"; import { Box } from "@mui/material";
import AboutSystemComponent from "./AboutSystemComponent"; import AboutSystemComponent from "./AboutSystemComponent";
import SystemVersionContainer from "./SystemVersionContainer"; import SystemVersionContainer from "./SystemVersionContainer";
const useStyles = makeStyles(theme => { const styles = {
return {
page: { page: {
display: "flex", display: "flex",
flexDirection: "column" flexDirection: "column"
}, },
element: { element: {
marginTop: theme.spacing(1) marginTop: 1
} }
}; };
});
const AboutSystemContainer = () => { const AboutSystemContainer = () => {
const classes = useStyles();
const handleOpenInNewTab = url => event => { const handleOpenInNewTab = url => event => {
window.open(url, "_blank"); window.open(url, "_blank");
event.preventDefault(); event.preventDefault();
}; };
return ( return (
<div className={classes.page}> <Box sx={styles.page}>
<AboutSystemComponent handleOpenInNewTab={handleOpenInNewTab} /> <AboutSystemComponent handleOpenInNewTab={handleOpenInNewTab} />
<div className={classes.element}> <Box sx={styles.element}>
<SystemVersionContainer /> <SystemVersionContainer />
</div> </Box>
</div> </Box>
); );
}; };
export default AboutSystemContainer; export default AboutSystemContainer;

View File

@ -1,22 +1,16 @@
import React, { useMemo, useEffect, useState } from "react"; import React, { useMemo, useEffect, useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/core/styles"; import { List, ListItem, ListItemText, ListItemAvatar } from "@mui/material";
import { import Avatar from "@mui/material/Avatar";
List, import WebAssetIcon from "@mui/icons-material/WebAsset";
ListItem, import DeveloperBoardIcon from "@mui/icons-material/DeveloperBoard";
ListItemText, import SettingsInputSvideoIcon from "@mui/icons-material/SettingsInputSvideo";
ListItemAvatar
} from "@material-ui/core";
import Avatar from "@material-ui/core/Avatar";
import WebAssetIcon from "@material-ui/icons/WebAsset";
import DeveloperBoardIcon from "@material-ui/icons/DeveloperBoard";
import SettingsInputSvideoIcon from "@material-ui/icons/SettingsInputSvideo";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import packageData from "../../../../package.json"; import packageData from "../../../../package.json";
import Paper from "@material-ui/core/Paper"; import Paper from "@mui/material/Paper";
import { useTheme } from "@mui/material/styles";
const useStyles = makeStyles(theme => { const getStyles = theme => ({
return {
horizontally: { horizontally: {
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "row",
@ -32,19 +26,19 @@ const useStyles = makeStyles(theme => {
versionAvatar: { versionAvatar: {
backgroundColor: theme.palette.secondary.main backgroundColor: theme.palette.secondary.main
} }
};
}); });
const SystemVersionComponent = ({ data }) => { const SystemVersionComponent = ({ data }) => {
const classes = useStyles();
const [listClass, setListClass] = useState(classes.horizontally);
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme();
const styles = getStyles(theme);
const [listClass, setListClass] = useState(styles.horizontally);
useEffect(() => { useEffect(() => {
const mediaQuery = window.matchMedia("(max-width: 800px)"); const mediaQuery = window.matchMedia("(max-width: 800px)");
function handleMatches(event) { function handleMatches(event) {
const cssClass = event.matches ? classes.vertical : classes.horizontally; const cssClass = event.matches ? styles.vertical : styles.horizontally;
setListClass(cssClass); setListClass(cssClass);
} }
@ -54,7 +48,7 @@ const SystemVersionComponent = ({ data }) => {
return () => { return () => {
mediaQuery.removeListener(handleMatches); mediaQuery.removeListener(handleMatches);
}; };
}, [classes.horizontally, classes.vertical]); }, [styles.horizontally, styles.vertical]);
const lastReleaseDate = useMemo(() => { const lastReleaseDate = useMemo(() => {
const format = "DD-MM-YYYY HH:mm:ss"; const format = "DD-MM-YYYY HH:mm:ss";
@ -84,16 +78,16 @@ const SystemVersionComponent = ({ data }) => {
return ( return (
<Paper variant="outlined"> <Paper variant="outlined">
<List className={listClass}> <List sx={listClass}>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar className={classes.versionAvatar}> <Avatar sx={styles.versionAvatar}>
<DeveloperBoardIcon /> <DeveloperBoardIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={ primary={
<span className={classes.value}> <span style={styles.value}>
{t("About.System.Version.Server", { {t("About.System.Version.Server", {
version: data.server.version version: data.server.version
})} })}
@ -106,13 +100,13 @@ const SystemVersionComponent = ({ data }) => {
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar className={classes.versionAvatar}> <Avatar sx={styles.versionAvatar}>
<SettingsInputSvideoIcon /> <SettingsInputSvideoIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={ primary={
<span className={classes.value}> <span style={styles.value}>
{t("About.System.Version.Api", { {t("About.System.Version.Api", {
version: data.api.version version: data.api.version
})} })}
@ -125,13 +119,13 @@ const SystemVersionComponent = ({ data }) => {
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar className={classes.versionAvatar}> <Avatar sx={styles.versionAvatar}>
<WebAssetIcon /> <WebAssetIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={ primary={
<span className={classes.value}> <span style={styles.value}>
{t("About.System.Version.Frontend", { {t("About.System.Version.Frontend", {
version: process.env.APP_VERSION ?? packageData.version version: process.env.APP_VERSION ?? packageData.version
})} })}

View File

@ -1,20 +1,19 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/core/styles"; import Timeline from "@mui/lab/Timeline";
import Timeline from "@material-ui/lab/Timeline"; import TimelineItem from "@mui/lab/TimelineItem";
import TimelineItem from "@material-ui/lab/TimelineItem"; import TimelineSeparator from "@mui/lab/TimelineSeparator";
import TimelineSeparator from "@material-ui/lab/TimelineSeparator"; import TimelineConnector from "@mui/lab/TimelineConnector";
import TimelineConnector from "@material-ui/lab/TimelineConnector"; import TimelineContent from "@mui/lab/TimelineContent";
import TimelineContent from "@material-ui/lab/TimelineContent"; import TimelineOppositeContent from "@mui/lab/TimelineOppositeContent";
import TimelineOppositeContent from "@material-ui/lab/TimelineOppositeContent"; import TimelineDot from "@mui/lab/TimelineDot";
import TimelineDot from "@material-ui/lab/TimelineDot"; import Paper from "@mui/material/Paper";
import Paper from "@material-ui/core/Paper"; import Typography from "@mui/material/Typography";
import Typography from "@material-ui/core/Typography";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { getRandomElement } from "../../../utils"; import { getRandomElement } from "../../../utils";
import { import {
Announcement, Announcement,
AmpStories, Book,
Apps, Apps,
BugReport, BugReport,
DeviceHub, DeviceHub,
@ -29,11 +28,11 @@ import {
Star, Star,
Whatshot, Whatshot,
Widgets Widgets
} from "@material-ui/icons"; } from "@mui/icons-material";
const timelineIcons = [ const timelineIcons = [
Announcement, Announcement,
AmpStories, Book,
Apps, Apps,
BugReport, BugReport,
DeviceHub, DeviceHub,
@ -55,14 +54,7 @@ const timelineDotVariants = [
{ color: "secondary", variant: "outlined" } { color: "secondary", variant: "outlined" }
]; ];
const useStyles = makeStyles(() => ({
paper: {
padding: "6px 16px"
}
}));
const TimelineComponent = ({ releases }) => { const TimelineComponent = ({ releases }) => {
const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
const _releases = releases.map((release, index) => { const _releases = releases.map((release, index) => {
@ -73,7 +65,7 @@ const TimelineComponent = ({ releases }) => {
}); });
return ( return (
<Timeline align="alternate"> <Timeline position="alternate">
{_releases.map(release => ( {_releases.map(release => (
<TimelineItem key={release.version}> <TimelineItem key={release.version}>
<TimelineOppositeContent> <TimelineOppositeContent>
@ -84,16 +76,18 @@ const TimelineComponent = ({ releases }) => {
</Typography> </Typography>
</TimelineOppositeContent> </TimelineOppositeContent>
<TimelineSeparator> <TimelineSeparator>
<TimelineDot <TimelineDot color={release.dot.color} variant={release.dot.variant}>
color={release.dot.color}
variant={release.dot.variant}
>
<release.icon /> <release.icon />
</TimelineDot> </TimelineDot>
{!release.isLast && <TimelineConnector />} {!release.isLast && <TimelineConnector />}
</TimelineSeparator> </TimelineSeparator>
<TimelineContent> <TimelineContent>
<Paper elevation={3} className={classes.paper}> <Paper
elevation={3}
sx={{
padding: "6px 16px"
}}
>
<Typography variant="h6" component="h1"> <Typography variant="h6" component="h1">
{release.notes[0]} {release.notes[0]}
</Typography> </Typography>

View File

@ -1,17 +1,16 @@
import React from "react"; import React from "react";
import { makeStyles } from "@material-ui/core/styles"; import { Alert, AlertTitle } from "@mui/material";
import { Alert, AlertTitle } from "@material-ui/lab"; import { getStyles } from "../styles";
import styles from "../styles";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useTheme } from "@mui/material/styles";
const useStyles = makeStyles(styles);
export default function GuestAnnouncement() { export default function GuestAnnouncement() {
const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme();
const styles = getStyles(theme);
return ( return (
<div className={classes.alert}> <div style={styles.alert}>
<Alert variant="outlined" severity="warning"> <Alert variant="outlined" severity="warning">
<AlertTitle>{t("Dashboard.Announcements.Guest.Title")}</AlertTitle> <AlertTitle>{t("Dashboard.Announcements.Guest.Title")}</AlertTitle>
{t("Dashboard.Announcements.Guest.Message")} {t("Dashboard.Announcements.Guest.Message")}

View File

@ -1,19 +1,18 @@
import React from "react"; import React from "react";
import { makeStyles } from "@material-ui/core/styles"; import { Alert, AlertTitle } from "@mui/material";
import { Alert, AlertTitle } from "@material-ui/lab"; import { getStyles } from "../styles";
import styles from "../styles";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useTuitioUser } from "@flare/tuitio-client-react"; import { useTuitioUser } from "@flare/tuitio-client-react";
import { useTheme } from "@mui/material/styles";
const useStyles = makeStyles(styles);
export default function UserAnnouncement() { export default function UserAnnouncement() {
const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
const { userName } = useTuitioUser(); const { userName } = useTuitioUser();
const theme = useTheme();
const styles = getStyles(theme);
return ( return (
<div className={classes.alert}> <div style={styles.alert}>
<Alert variant="outlined" severity="info"> <Alert variant="outlined" severity="info">
<AlertTitle> <AlertTitle>
{t("Dashboard.Announcements.User.Title", { {t("Dashboard.Announcements.User.Title", {

View File

@ -1,4 +1,4 @@
const styles = theme => ({ const getStyles = theme => ({
alert: { alert: {
width: "100%", width: "100%",
"& > * + *": { "& > * + *": {
@ -7,4 +7,4 @@ const styles = theme => ({
} }
}); });
export default styles; export { getStyles };

View File

@ -1,23 +1,14 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Card } from "@material-ui/core"; import { Card } from "@mui/material";
import { makeStyles } from "@material-ui/core/styles";
import styles from "../styles"; import styles from "../styles";
import LoginComponent from "./LoginComponent"; import LoginComponent from "./LoginComponent";
const useStyles = makeStyles(styles);
const LoginCard = ({ credentials, onChange, onLogin }) => { const LoginCard = ({ credentials, onChange, onLogin }) => {
const classes = useStyles();
return ( return (
<div className={classes.appLogin}> <div style={styles.appLogin}>
<Card variant="outlined"> <Card variant="outlined">
<LoginComponent <LoginComponent credentials={credentials} onChange={onChange} onLogin={onLogin} />
credentials={credentials}
onChange={onChange}
onLogin={onLogin}
/>
</Card> </Card>
</div> </div>
); );

View File

@ -1,29 +1,19 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/core/styles"; import { TextField, InputAdornment, Button, CardActions, CardContent } from "@mui/material";
import { import { AccountCircleOutlined } from "@mui/icons-material";
TextField,
InputAdornment,
Button,
CardActions,
CardContent
} from "@material-ui/core";
import { AccountCircleOutlined } from "@material-ui/icons";
import PasswordField from "../../../components/common/inputs/PasswordField"; import PasswordField from "../../../components/common/inputs/PasswordField";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import styles from "../styles"; import styles from "../styles";
const useStyles = makeStyles(styles);
const LoginComponent = ({ credentials, onChange, onLogin }) => { const LoginComponent = ({ credentials, onChange, onLogin }) => {
const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
return ( return (
<> <>
<CardContent> <CardContent>
<TextField <TextField
className={classes.field} sx={styles.field}
id="username" id="username"
label={t("Login.Username")} label={t("Login.Username")}
onChange={onChange("userName")} onChange={onChange("userName")}
@ -40,7 +30,7 @@ const LoginComponent = ({ credentials, onChange, onLogin }) => {
<PasswordField <PasswordField
id="password" id="password"
label={t("Login.Password")} label={t("Login.Password")}
className={classes.field} sx={styles.field}
onChange={onChange("password")} onChange={onChange("password")}
value={credentials.password} value={credentials.password}
onKeyDown={e => { onKeyDown={e => {
@ -48,13 +38,8 @@ const LoginComponent = ({ credentials, onChange, onLogin }) => {
}} }}
/> />
</CardContent> </CardContent>
<CardActions className={classes.actions}> <CardActions sx={styles.actions}>
<Button <Button sx={styles.onRight} variant="contained" color="primary" onClick={onLogin}>
className={classes.onRight}
variant="contained"
color="primary"
onClick={onLogin}
>
{t("Login.Label")} {t("Login.Label")}
</Button> </Button>
</CardActions> </CardActions>

View File

@ -26,13 +26,7 @@ const LoginContainer = () => {
return login(userName, password); return login(userName, password);
}; };
return ( return <LoginCard credentials={credentials} onChange={handleChange} onLogin={handleLogin} />;
<LoginCard
credentials={credentials}
onChange={handleChange}
onLogin={handleLogin}
/>
);
}; };
export default LoginContainer; export default LoginContainer;

View File

@ -1,4 +1,4 @@
const styles = theme => ({ const styles = {
onRight: { onRight: {
marginLeft: "auto" marginLeft: "auto"
}, },
@ -9,13 +9,13 @@ const styles = theme => ({
alignItems: "center" alignItems: "center"
}, },
field: { field: {
margin: theme.spacing(1), margin: 1,
width: "300px" width: "300px"
}, },
actions: { actions: {
paddingRight: "16px", paddingRight: "16px",
paddingLeft: "16px" paddingLeft: "16px"
} }
}); };
export default styles; export default styles;

View File

@ -1,114 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import {
Accordion,
AccordionSummary,
AccordionDetails,
Grid
} from "@material-ui/core";
import ExpandMoreIcon from "@material-ui/icons/ExpandMore";
import withStyles from "@material-ui/core/styles/withStyles";
import MachineCollapsedContent from "./common/MachineCollapsedContent";
import { DataLabel } from "../../../components/common";
import { useTranslation } from "react-i18next";
import { useSensitiveInfo } from "../../../hooks";
import ActionsGroup from "./common/ActionsGroup";
import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles(() => ({
panel: {
justifyContent: "center",
alignItems: "center"
}
}));
const IconLeftAccordionSummary = withStyles(theme => ({
root: {
minHeight: "20px",
height: "42px",
[theme.breakpoints.down("md")]: {
height: "62px"
},
[theme.breakpoints.down("sm")]: {
height: "102px"
}
},
expandIcon: {
order: -1
}
}))(AccordionSummary);
const GridCell = ({ label, value }) => {
const { mask } = useSensitiveInfo();
return (
<Grid item xs={12} md={6} lg={3}>
<DataLabel label={label} data={mask(value)} />
</Grid>
);
};
GridCell.propTypes = {
label: PropTypes.string.isRequired,
value: PropTypes.string.isRequired
};
const MachineAccordion = ({ machine, actions, logs, addLog }) => {
const { t } = useTranslation();
const classes = useStyles();
return (
<Accordion>
<IconLeftAccordionSummary
expandIcon={<ExpandMoreIcon />}
aria-label="Expand"
aria-controls="additional-actions1-content"
id="additional-actions1-header"
IconButtonProps={{ edge: "start" }}
>
<Grid container className={classes.panel}>
<Grid item xs={11}>
<Grid container>
<GridCell
label={t("Machine.FullName")}
value={machine.fullMachineName}
/>
<GridCell label={t("Machine.Name")} value={machine.machineName} />
<GridCell label={t("Machine.IP")} value={machine.iPv4Address} />
<GridCell label={t("Machine.MAC")} value={machine.macAddress} />
</Grid>
</Grid>
<Grid item xs={1} style={{ textAlign: "right" }}>
<ActionsGroup
className={classes.actions}
machine={machine}
actions={actions}
addLog={addLog}
/>
</Grid>
</Grid>
</IconLeftAccordionSummary>
<AccordionDetails>
<MachineCollapsedContent
description={machine.description}
logs={logs}
style={{ width: "100%" }}
/>
</AccordionDetails>
</Accordion>
);
};
MachineAccordion.propTypes = {
machine: PropTypes.shape({
machineId: PropTypes.number.isRequired,
machineName: PropTypes.string.isRequired,
fullMachineName: PropTypes.string.isRequired,
macAddress: PropTypes.string.isRequired,
iPv4Address: PropTypes.string,
description: PropTypes.string
}).isRequired,
actions: PropTypes.array.isRequired,
logs: PropTypes.array.isRequired,
addLog: PropTypes.func.isRequired
};
export default MachineAccordion;

View File

@ -0,0 +1,106 @@
import React from "react";
import { styled } from "@mui/material/styles";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import MuiAccordion, { AccordionProps } from "@mui/material/Accordion";
import MuiAccordionSummary, { AccordionSummaryProps } from "@mui/material/AccordionSummary";
import MuiAccordionDetails from "@mui/material/AccordionDetails";
import { models } from "types";
import { Grid } from "@mui/material";
import ActionsGroup from "./common/ActionsGroup";
import { useTranslation } from "react-i18next";
import { useSensitiveInfo } from "hooks";
import { DataLabel } from "components/common";
import MachineCollapsedContent from "./common/MachineCollapsedContent";
const Accordion = styled((props: AccordionProps) => <MuiAccordion disableGutters elevation={0} square {...props} />)(
({ theme }) => ({
border: `1px solid ${theme.palette.divider}`,
"&:not(:last-child)": {
borderBottom: 0
},
"&::before": {
display: "none"
}
})
);
const AccordionSummary = styled((props: AccordionSummaryProps) => (
<MuiAccordionSummary expandIcon={<ExpandMoreIcon />} {...props} />
))(({ theme }) => ({
backgroundColor: theme.palette.mode === "dark" ? "rgba(255, 255, 255, .05)" : "rgba(0, 0, 0, .03)",
flexDirection: "row-reverse",
"& .MuiAccordionSummary-expandIconWrapper.Mui-expanded": {
transform: "rotate(180deg)"
},
"& .MuiAccordionSummary-content": {
marginLeft: theme.spacing(1)
},
minHeight: "20px",
height: "42px",
[theme.breakpoints.down("md")]: {
height: "62px"
},
[theme.breakpoints.down("sm")]: {
height: "102px"
}
}));
const AccordionDetails = styled(MuiAccordionDetails)(({ theme }) => ({
padding: theme.spacing(1),
borderTop: "1px solid rgba(0, 0, 0, .125)"
}));
type GridCellProps = {
label: string;
value: string;
};
const GridCell: React.FC<GridCellProps> = ({ label, value }) => {
const { mask } = useSensitiveInfo();
return (
<Grid item xs={12} md={6} lg={3}>
<DataLabel label={label} data={mask(value)} />
</Grid>
);
};
type Props = {
machine: models.Machine;
actions: Array<any>; // Replace any with the actual type of the actions
logs: Array<any>; // Replace any with the actual type of the logs
addLog: () => void; // Replace with the actual function signature
};
const MachineAccordion: React.FC<Props> = ({ machine, actions, logs, addLog }) => {
const { t } = useTranslation();
return (
<Accordion>
<AccordionSummary aria-controls={`machine-${machine.machineId}-summary`} id={`machine-${machine.machineId}`}>
<Grid
container
sx={{
justifyContent: "center",
alignItems: "center"
}}
>
<Grid item xs={11}>
<Grid container>
<GridCell label={t("Machine.FullName")} value={machine.fullMachineName} />
<GridCell label={t("Machine.Name")} value={machine.machineName} />
<GridCell label={t("Machine.IP")} value={machine.iPv4Address || ""} />
<GridCell label={t("Machine.MAC")} value={machine.macAddress} />
</Grid>
</Grid>
<Grid item xs={1} style={{ textAlign: "right" }}>
<ActionsGroup machine={machine} actions={actions} addLog={addLog} />
</Grid>
</Grid>
</AccordionSummary>
<AccordionDetails>
<MachineCollapsedContent description={machine.description} logs={logs} style={{ width: "100%" }} />
</AccordionDetails>
</Accordion>
);
};
export default MachineAccordion;

View File

@ -4,7 +4,7 @@ import MachineTableRow from "./MachineTableRow";
import MachineAccordion from "./MachineAccordion"; import MachineAccordion from "./MachineAccordion";
import { ViewModes } from "./ViewModeSelection"; import { ViewModes } from "./ViewModeSelection";
import { useToast } from "../../../hooks"; import { useToast } from "../../../hooks";
import { LastPage, RotateLeft, Launch, Stop } from "@material-ui/icons"; import { LastPage, RotateLeft, Launch, Stop } from "@mui/icons-material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { routes, post } from "../../../utils/api"; import { routes, post } from "../../../utils/api";
@ -96,7 +96,9 @@ const MachineContainer = ({ machine, viewMode }) => {
}, },
{ {
code: "advanced", code: "advanced",
effect: () => {}, effect: () => {
// to do: implement
},
icon: Launch, icon: Launch,
tooltip: t("Machine.Actions.Advanced"), tooltip: t("Machine.Actions.Advanced"),
main: false main: false
@ -106,20 +108,10 @@ const MachineContainer = ({ machine, viewMode }) => {
return ( return (
<> <>
{viewMode === ViewModes.TABLE && ( {viewMode === ViewModes.TABLE && (
<MachineTableRow <MachineTableRow machine={machine} actions={actions} logs={logs} addLog={addLog} />
machine={machine}
actions={actions}
logs={logs}
addLog={addLog}
/>
)} )}
{viewMode === ViewModes.ACCORDION && ( {viewMode === ViewModes.ACCORDION && (
<MachineAccordion <MachineAccordion machine={machine} actions={actions} logs={logs} addLog={addLog} />
machine={machine}
actions={actions}
logs={logs}
addLog={addLog}
/>
)} )}
</> </>
); );

View File

@ -1,34 +1,26 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { TableCell, TableRow, IconButton, Collapse } from "@material-ui/core"; import { TableCell, TableRow, IconButton, Collapse } from "@mui/material";
import { KeyboardArrowDown, KeyboardArrowUp } from "@material-ui/icons"; import { KeyboardArrowDown, KeyboardArrowUp } from "@mui/icons-material";
import { makeStyles } from "@material-ui/core/styles";
import MachineCollapsedContent from "./common/MachineCollapsedContent"; import MachineCollapsedContent from "./common/MachineCollapsedContent";
import { useSensitiveInfo } from "../../../hooks"; import { useSensitiveInfo } from "../../../hooks";
import ActionsGroup from "./common/ActionsGroup"; import ActionsGroup from "./common/ActionsGroup";
const useRowStyles = makeStyles({
root: {
"& > *": {
borderBottom: "unset"
}
}
});
const MachineTableRow = ({ machine, actions, logs, addLog }) => { const MachineTableRow = ({ machine, actions, logs, addLog }) => {
const [open, setOpen] = React.useState(false); const [open, setOpen] = React.useState(false);
const classes = useRowStyles();
const { mask } = useSensitiveInfo(); const { mask } = useSensitiveInfo();
return ( return (
<React.Fragment> <React.Fragment>
<TableRow className={classes.root}> <TableRow
<TableCell> sx={{
<IconButton "& .MuiTableCell-root": {
aria-label="expand row" borderBottom: "unset"
size="small" }
onClick={() => setOpen(!open)} }}
> >
<TableCell>
<IconButton aria-label="expand row" size="small" onClick={() => setOpen(!open)}>
{open ? <KeyboardArrowUp /> : <KeyboardArrowDown />} {open ? <KeyboardArrowUp /> : <KeyboardArrowDown />}
</IconButton> </IconButton>
</TableCell> </TableCell>
@ -48,7 +40,7 @@ const MachineTableRow = ({ machine, actions, logs, addLog }) => {
<MachineCollapsedContent <MachineCollapsedContent
description={machine.description} description={machine.description}
logs={logs} logs={logs}
style={{ paddingBottom: "10px" }} style={{ paddingBottom: "0.5rem" }}
/> />
</Collapse> </Collapse>
</TableCell> </TableCell>

View File

@ -1,8 +1,5 @@
import React, { useContext, useEffect, useCallback, useState } from "react"; import React, { useContext, useEffect, useCallback, useState } from "react";
import { import { NetworkStateContext, NetworkDispatchContext } from "../../network/state/contexts";
NetworkStateContext,
NetworkDispatchContext
} from "../../network/state/contexts";
import MachinesListComponent from "./MachinesListComponent"; import MachinesListComponent from "./MachinesListComponent";
import PageTitle from "../../../components/common/PageTitle"; import PageTitle from "../../../components/common/PageTitle";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
@ -35,19 +32,9 @@ const MachinesContainer = () => {
<> <>
<PageTitle <PageTitle
text={t("Menu.Machines")} text={t("Menu.Machines")}
toolBar={ toolBar={<ViewModeSelection callback={setViewMode} initialMode={ViewModes.TABLE} />}
<ViewModeSelection
callback={setViewMode}
initialMode={ViewModes.TABLE}
/> />
} {viewMode && <MachinesListComponent machines={state.network.machines} viewMode={viewMode} />}
/>
{viewMode && (
<MachinesListComponent
machines={state.network.machines}
viewMode={viewMode}
/>
)}
</> </>
); );
}; };

View File

@ -1,14 +1,7 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
Table, import Paper from "@mui/material/Paper";
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow
} from "@material-ui/core";
import Paper from "@material-ui/core/Paper";
import MachineContainer from "./MachineContainer"; import MachineContainer from "./MachineContainer";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { ViewModes } from "./ViewModeSelection"; import { ViewModes } from "./ViewModeSelection";
@ -17,11 +10,7 @@ const MachinesList = ({ machines, viewMode }) => {
return ( return (
<> <>
{machines.map(machine => ( {machines.map(machine => (
<MachineContainer <MachineContainer key={`machine-${machine.machineId}`} machine={machine} viewMode={viewMode} />
key={`machine-${machine.machineId}`}
machine={machine}
viewMode={viewMode}
/>
))} ))}
</> </>
); );

View File

@ -1,10 +1,9 @@
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import TableChartIcon from "@material-ui/icons/TableChart"; import TableChartIcon from "@mui/icons-material/TableChart";
import ViewListIcon from "@material-ui/icons/ViewList"; import ViewListIcon from "@mui/icons-material/ViewList";
import ToggleButton from "@material-ui/lab/ToggleButton"; import { ToggleButtonGroup, ToggleButton } from "@mui/material";
import ToggleButtonGroup from "@material-ui/lab/ToggleButtonGroup"; import { Tooltip } from "@mui/material";
import { Tooltip } from "@material-ui/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
export const ViewModes = { export const ViewModes = {
@ -44,17 +43,8 @@ const ViewModeSelection = ({ initialMode, callback }) => {
useEffect(() => callback && callback(state.mode), [callback, state.mode]); useEffect(() => callback && callback(state.mode), [callback, state.mode]);
return ( return (
<ToggleButtonGroup <ToggleButtonGroup size="small" value={state.mode} exclusive onChange={handleViewModeSelection}>
size="small" <ToggleButton value={ViewModes.TABLE} aria-label="table view mode" disabled={state.mode === ViewModes.TABLE}>
value={state.mode}
exclusive
onChange={handleViewModeSelection}
>
<ToggleButton
value={ViewModes.TABLE}
aria-label="table view mode"
disabled={state.mode === ViewModes.TABLE}
>
<Tooltip title={t("ViewModes.Table")}> <Tooltip title={t("ViewModes.Table")}>
<TableChartIcon /> <TableChartIcon />
</Tooltip> </Tooltip>

View File

@ -1,8 +1,8 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { IconButton, Tooltip } from "@material-ui/core"; import { IconButton, Tooltip } from "@mui/material";
const ActionButton = React.forwardRef((props, _ref) => { const ActionButton = React.forwardRef(props => {
const { action, machine, callback, disabled } = props; const { action, machine, callback, disabled } = props;
const id = `machine-item-${machine.machineId}-${action.code}`; const id = `machine-item-${machine.machineId}-${action.code}`;
const handleActionClick = event => { const handleActionClick = event => {
@ -12,14 +12,12 @@ const ActionButton = React.forwardRef((props, _ref) => {
}; };
return ( return (
<Tooltip <Tooltip id={`machine-item-${machine.machineId}-${action.code}-tooltip`} title={action.tooltip}>
id={`machine-item-${machine.machineId}-${action.code}-tooltip`}
title={action.tooltip}
>
<span> <span>
<IconButton <IconButton
id={id} id={id}
size={"small"} size={"small"}
sx={{ padding: "0.2rem" }}
onFocus={event => event.stopPropagation()} onFocus={event => event.stopPropagation()}
onClick={handleActionClick} onClick={handleActionClick}
disabled={disabled} disabled={disabled}
@ -44,4 +42,6 @@ ActionButton.propTypes = {
disabled: PropTypes.bool disabled: PropTypes.bool
}; };
ActionButton.displayName = "ActionButton";
export default ActionButton; export default ActionButton;

View File

@ -2,8 +2,8 @@ import React, { useMemo, useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import WakeComponent from "./WakeComponent"; import WakeComponent from "./WakeComponent";
import ActionButton from "./ActionButton"; import ActionButton from "./ActionButton";
import { Menu } from "@material-ui/core"; import { Menu } from "@mui/material";
import { MoreHoriz } from "@material-ui/icons"; import { MoreHoriz } from "@mui/icons-material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { usePermissions } from "../../../../hooks"; import { usePermissions } from "../../../../hooks";
@ -13,15 +13,9 @@ const ActionsGroup = ({ machine, actions, addLog }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { operateMachines: canOperateMachines } = usePermissions(); const { operateMachines: canOperateMachines } = usePermissions();
const mainActions = useMemo( const mainActions = useMemo(() => actions.filter(a => a.main === true), [actions]);
() => actions.filter(a => a.main === true),
[actions]
);
const secondaryActions = useMemo( const secondaryActions = useMemo(() => actions.filter(a => a.main === false), [actions]);
() => actions.filter(a => a.main === false),
[actions]
);
const handleMenuOpen = (_, event) => { const handleMenuOpen = (_, event) => {
setMenuAnchor(event.currentTarget); setMenuAnchor(event.currentTarget);
@ -33,11 +27,15 @@ const ActionsGroup = ({ machine, actions, addLog }) => {
return ( return (
<> <>
<WakeComponent <div
machine={machine} style={{
addLog={addLog} display: "flex",
disabled={!canOperateMachines} flexDirection: "row",
/> justifyContent: "flex-end",
alignItems: "center"
}}
>
<WakeComponent machine={machine} addLog={addLog} disabled={!canOperateMachines} />
{mainActions.map(action => ( {mainActions.map(action => (
<ActionButton <ActionButton
key={`machine-item-${machine.machineId}-${action.code}`} key={`machine-item-${machine.machineId}-${action.code}`}
@ -55,7 +53,7 @@ const ActionsGroup = ({ machine, actions, addLog }) => {
}} }}
machine={machine} machine={machine}
/> />
</div>
<Menu <Menu
id="secondary-actions-menu" id="secondary-actions-menu"
anchorEl={menuAnchor} anchorEl={menuAnchor}

View File

@ -1,27 +1,21 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import MachineLog from "./MachineLog"; import MachineLog from "./MachineLog";
import Typography from "@material-ui/core/Typography"; import Typography from "@mui/material/Typography";
import { useSensitiveInfo } from "../../../../hooks"; import { useSensitiveInfo } from "../../../../hooks";
import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles(_theme => ({
panel: {
display: "flex"
},
label: {
marginRight: "4px"
}
}));
const MachineDescription = ({ description }) => { const MachineDescription = ({ description }) => {
const classes = useStyles();
return ( return (
<div className={classes.panel}> <div
style={{
display: "flex"
}}
>
<Typography <Typography
variant="body2" variant="body2"
className={classes.label} sx={{
marginRight: "4px"
}}
color="textSecondary" color="textSecondary"
> >
{"Description:"} {"Description:"}

View File

@ -1,16 +1,13 @@
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Box } from "@material-ui/core"; import { Box } from "@mui/material";
import { useSensitiveInfo } from "../../../../hooks"; import { useSensitiveInfo } from "../../../../hooks";
import { LazyLog, ScrollFollow } from "react-lazylog"; import { LazyLog, ScrollFollow } from "react-lazylog";
const MachineLog = ({ logs }) => { const MachineLog = ({ logs }) => {
const { maskElements } = useSensitiveInfo(); const { maskElements } = useSensitiveInfo();
const displayLogs = useMemo( const displayLogs = useMemo(() => (logs.length > 0 ? maskElements(logs).join("\n") : "..."), [logs, maskElements]);
() => (logs.length > 0 ? maskElements(logs).join("\n") : "..."),
[logs, maskElements]
);
return ( return (
<Box> <Box>

View File

@ -1,7 +1,7 @@
import React, { useState, useEffect, useCallback } from "react"; import React, { useState, useEffect, useCallback } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { IconButton, Tooltip } from "@material-ui/core"; import { IconButton, Tooltip } from "@mui/material";
import { PowerSettingsNew } from "@material-ui/icons"; import { PowerSettingsNew } from "@mui/icons-material";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { useToast } from "../../../../hooks"; import { useToast } from "../../../../hooks";
import { msToMinAndSec } from "../../../../utils/time"; import { msToMinAndSec } from "../../../../utils/time";
@ -18,10 +18,8 @@ const WakeComponent = ({ machine, addLog, disabled }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { success, error } = useToast(); const { success, error } = useToast();
const pingInterval = const pingInterval = process.env.REACT_APP_MACHINE_PING_INTERVAL || defaultPingInterval;
process.env.REACT_APP_MACHINE_PING_INTERVAL || defaultPingInterval; const startingTime = process.env.REACT_APP_MACHINE_STARTING_TIME || defaultStartingTime;
const startingTime =
process.env.REACT_APP_MACHINE_STARTING_TIME || defaultStartingTime;
const getCurrentDateTime = useCallback(() => { const getCurrentDateTime = useCallback(() => {
const currentDateTime = Date.now(); const currentDateTime = Date.now();
@ -31,10 +29,7 @@ const WakeComponent = ({ machine, addLog, disabled }) => {
return result; return result;
}, [t]); }, [t]);
const log = useCallback( const log = useCallback(message => addLog(`[${getCurrentDateTime()}] ${message}`), [addLog, getCurrentDateTime]);
message => addLog(`[${getCurrentDateTime()}] ${message}`),
[addLog, getCurrentDateTime]
);
const wakeMachine = useCallback(async () => { const wakeMachine = useCallback(async () => {
await post( await post(
@ -48,11 +43,7 @@ const WakeComponent = ({ machine, addLog, disabled }) => {
success(result.status); success(result.status);
//retrigger //retrigger
log( log(`Periodic ping will be re-triggered in ${startingTime} ms [${msToMinAndSec(startingTime)}]`);
`Periodic ping will be re-triggered in ${startingTime} ms [${msToMinAndSec(
startingTime
)}]`
);
setTimeout(() => { setTimeout(() => {
setTrigger(prev => !prev); setTrigger(prev => !prev);
}, startingTime); }, startingTime);
@ -80,12 +71,16 @@ const WakeComponent = ({ machine, addLog, disabled }) => {
}, pingInterval); }, pingInterval);
} }
}, },
onError: () => {} onError: () => {
// to do: handle error
}
} }
); );
}, [machine, log, pingInterval, disabled]); }, [machine, log, pingInterval, disabled]);
useEffect(pingInLoop, [trigger, pingInLoop]); useEffect(() => {
pingInLoop();
}, [trigger, pingInLoop]);
const handleWakeClick = event => { const handleWakeClick = event => {
wakeMachine(); wakeMachine();
@ -100,6 +95,7 @@ const WakeComponent = ({ machine, addLog, disabled }) => {
size={"small"} size={"small"}
disabled={disabled || state.on} disabled={disabled || state.on}
onClick={handleWakeClick} onClick={handleWakeClick}
sx={{ padding: "0.2rem" }}
style={state.on ? { color: "#33cc33" } : undefined} style={state.on ? { color: "#33cc33" } : undefined}
onFocus={event => event.stopPropagation()} onFocus={event => event.stopPropagation()}
> >

View File

@ -4,10 +4,10 @@ import NetworkStateProvider from "../state/NetworkStateProvider";
import { usePermissions } from "../../../hooks"; import { usePermissions } from "../../../hooks";
import NotAllowed from "../../../components/common/NotAllowed"; import NotAllowed from "../../../components/common/NotAllowed";
const NetworkContainer = () => { const NetworkContainer = (): JSX.Element | null => {
const { loading, viewMachines } = usePermissions(); const { loading, viewMachines } = usePermissions();
if (loading) return ""; if (loading) return null;
if (!viewMachines) return <NotAllowed />; if (!viewMachines) return <NotAllowed />;
return ( return (

View File

@ -6,16 +6,11 @@ import { initialState } from "./initialState";
const NetworkStateProvider = ({ children }) => { const NetworkStateProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState); const [state, dispatch] = useReducer(reducer, initialState);
const dispatchActions = useMemo( const dispatchActions = useMemo(() => reducerDispatchActions(dispatch), [dispatch]);
() => reducerDispatchActions(dispatch),
[dispatch]
);
return ( return (
<NetworkStateContext.Provider value={state}> <NetworkStateContext.Provider value={state}>
<NetworkDispatchContext.Provider value={dispatchActions}> <NetworkDispatchContext.Provider value={dispatchActions}>{children}</NetworkDispatchContext.Provider>
{children}
</NetworkDispatchContext.Provider>
</NetworkStateContext.Provider> </NetworkStateContext.Provider>
); );
}; };

View File

@ -17,6 +17,5 @@ export function reducer(state, action) {
} }
export const dispatchActions = dispatch => ({ export const dispatchActions = dispatch => ({
onNetworkChange: (prop, value) => onNetworkChange: (prop, value) => dispatch({ type: "onNetworkChange", payload: { prop, value } })
dispatch({ type: "onNetworkChange", payload: { prop, value } })
}); });

View File

@ -1,7 +1,7 @@
import React, { useState, useMemo } from "react"; import React, { useState, useMemo } from "react";
import BubbleChartIcon from "@material-ui/icons/BubbleChart"; import BubbleChartIcon from "@mui/icons-material/BubbleChart";
import BrushIcon from "@material-ui/icons/Brush"; import BrushIcon from "@mui/icons-material/Brush";
import NotificationsIcon from "@material-ui/icons/Notifications"; import NotificationsIcon from "@mui/icons-material/Notifications";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import PageTitle from "../../components/common/PageTitle"; import PageTitle from "../../components/common/PageTitle";
import NavigationButtons from "../../components/common/NavigationButtons"; import NavigationButtons from "../../components/common/NavigationButtons";
@ -34,19 +34,11 @@ const SettingsContainer = () => {
const [tab, setTab] = useState(NavigationTabs.SYSTEM); const [tab, setTab] = useState(NavigationTabs.SYSTEM);
const { t } = useTranslation(); const { t } = useTranslation();
const navigationTabs = useMemo( const navigationTabs = useMemo(() => tabs.map(z => ({ ...z, tooltip: t(z.code) })), [t]);
() => tabs.map(z => ({ ...z, tooltip: t(z.code) })),
[t]
);
return ( return (
<> <>
<PageTitle <PageTitle text={t(tab)} navigation={<NavigationButtons tabs={navigationTabs} onTabChange={setTab} />} />
text={t(tab)}
navigation={
<NavigationButtons tabs={navigationTabs} onTabChange={setTab} />
}
/>
{tab === NavigationTabs.SYSTEM && <SystemContainer />} {tab === NavigationTabs.SYSTEM && <SystemContainer />}
{tab === NavigationTabs.APPEARANCE && <AppearanceContainer />} {tab === NavigationTabs.APPEARANCE && <AppearanceContainer />}
{tab === NavigationTabs.NOTIFICATIONS && <NotificationsContainer />} {tab === NavigationTabs.NOTIFICATIONS && <NotificationsContainer />}

View File

@ -1,21 +1,19 @@
import React from "react"; import React from "react";
import { useApplicationTheme } from "../../../providers/ThemeProvider"; import { useApplicationTheme } from "../../../providers/ThemeProvider";
import { Grid, Paper, FormControlLabel, Switch } from "@material-ui/core"; import { Grid, Paper, FormControlLabel, Switch, Box } from "@mui/material";
import LanguageContainer from "./language/LanguageContainer"; import LanguageContainer from "./language/LanguageContainer";
import { PaperTitle } from "components/common"; import { PaperTitle } from "components/common";
import { makeStyles } from "@material-ui/core/styles";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const useStyles = makeStyles(theme => ({ const styles = {
language: { language: {
paddingLeft: theme.spacing(1) paddingLeft: 1
} }
})); };
const AppearanceComponent = () => { const AppearanceComponent = () => {
const { isDark, onDarkModeChanged } = useApplicationTheme(); const { isDark, onDarkModeChanged } = useApplicationTheme();
const { t } = useTranslation(); const { t } = useTranslation();
const classes = useStyles();
const handleChange = event => { const handleChange = event => {
const { checked } = event.target; const { checked } = event.target;
@ -29,14 +27,7 @@ const AppearanceComponent = () => {
<Grid item xs={12} sm={6} md={4} lg={3}> <Grid item xs={12} sm={6} md={4} lg={3}>
<FormControlLabel <FormControlLabel
value="start" value="start"
control={ control={<Switch checked={isDark} onChange={handleChange} color="secondary" name="dark-mode-switch" />}
<Switch
checked={isDark}
onChange={handleChange}
color="secondary"
name="dark-mode-switch"
/>
}
label="Dark mode:" label="Dark mode:"
labelPlacement="start" labelPlacement="start"
/> />
@ -45,9 +36,9 @@ const AppearanceComponent = () => {
<FormControlLabel <FormControlLabel
value="start" value="start"
control={ control={
<div className={classes.language}> <Box sx={styles.language}>
<LanguageContainer /> <LanguageContainer />
</div> </Box>
} }
label="Language:" label="Language:"
labelPlacement="start" labelPlacement="start"

View File

@ -1,75 +0,0 @@
import React from "react";
import PropTypes from "prop-types";
import Flag from "react-flags";
import { IconButton, Menu, MenuItem } from "@material-ui/core";
import { useTranslation } from "react-i18next";
const LanguageComponent = ({
languageIsSet,
anchorEl,
onMenuOpen,
onLanguageChange,
onClose,
flag,
flagsPath
}) => {
const { t } = useTranslation();
const open = Boolean(anchorEl);
return (
<>
<IconButton
aria-controls="language-menu"
aria-haspopup="true"
onClick={onMenuOpen}
color="inherit"
size="small"
>
{languageIsSet && (
<Flag
name={flag.name}
format="png"
pngSize={32}
shiny={true}
basePath={flagsPath}
alt={flag.alt}
/>
)}
</IconButton>
<Menu
id="language-menu"
anchorEl={anchorEl}
anchorOrigin={{
vertical: "top",
horizontal: "right"
}}
keepMounted
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
open={open}
onClose={onClose}
>
<MenuItem onClick={onLanguageChange("ro")}>
{t("Language.Romanian")}
</MenuItem>
<MenuItem onClick={onLanguageChange("en")}>
{t("Language.English")}
</MenuItem>
</Menu>
</>
);
};
LanguageComponent.propTypes = {
languageIsSet: PropTypes.bool.isRequired,
anchorEl: PropTypes.object,
onMenuOpen: PropTypes.func.isRequired,
onLanguageChange: PropTypes.func.isRequired,
onClose: PropTypes.func.isRequired,
flag: PropTypes.object.isRequired,
flagsPath: PropTypes.string.isRequired
};
export default LanguageComponent;

View File

@ -0,0 +1,54 @@
import React, { MouseEvent, ReactElement } from "react";
import { IconButton, Menu, MenuItem } from "@mui/material";
import { useTranslation } from "react-i18next";
import { FlagIcon } from "components/common";
interface LanguageComponentProps {
languageIsSet: boolean;
anchorEl: null | HTMLElement;
onMenuOpen: (event: MouseEvent<HTMLElement>) => void;
onLanguageChange: (lang: string) => () => void;
onClose: () => void;
flagCode: string;
flagsPath: string;
}
const LanguageComponent: React.FC<LanguageComponentProps> = ({
languageIsSet,
anchorEl,
onMenuOpen,
onLanguageChange,
onClose,
flagCode
}): ReactElement => {
const { t } = useTranslation();
const open = Boolean(anchorEl);
return (
<>
<IconButton aria-controls="language-menu" aria-haspopup="true" onClick={onMenuOpen} color="inherit" size="small">
{languageIsSet && <FlagIcon code={flagCode} />}
</IconButton>
<Menu
id="language-menu"
anchorEl={anchorEl}
anchorOrigin={{
vertical: "top",
horizontal: "right"
}}
keepMounted
transformOrigin={{
vertical: "top",
horizontal: "left"
}}
open={open}
onClose={onClose}
>
<MenuItem onClick={onLanguageChange("ro")}>{t("Language.Romanian")}</MenuItem>
<MenuItem onClick={onLanguageChange("en")}>{t("Language.English")}</MenuItem>
</Menu>
</>
);
};
export default LanguageComponent;

View File

@ -2,25 +2,15 @@ import React, { useState, useEffect } from "react";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import LanguageComponent from "./LanguageComponent"; import LanguageComponent from "./LanguageComponent";
const flagsPath = process.env.PUBLIC_URL
? `${process.env.PUBLIC_URL}/flags`
: "flags";
const LanguageContainer = () => { const LanguageContainer = () => {
const [anchorEl, setAnchorEl] = useState(null); const [anchorEl, setAnchorEl] = useState(null);
const { i18n } = useTranslation(); const { i18n } = useTranslation();
const [flag, setFlag] = useState({ const [flagCode, setFlagCode] = useState("RO");
name: "RO",
alt: "-"
});
useEffect(() => { useEffect(() => {
if (!i18n.language) return; if (!i18n.language) return;
setFlag({ setFlagCode(i18n.language === "en" ? "GB" : i18n.language.toUpperCase());
name: i18n.language === "en" ? "GB" : i18n.language.toUpperCase(),
alt: i18n.language
});
}, [i18n.language]); }, [i18n.language]);
const handleMenuOpen = event => { const handleMenuOpen = event => {
@ -45,8 +35,7 @@ const LanguageContainer = () => {
onMenuOpen={handleMenuOpen} onMenuOpen={handleMenuOpen}
onLanguageChange={handleLanguageChange} onLanguageChange={handleLanguageChange}
onClose={handleClose} onClose={handleClose}
flag={flag} flagCode={flagCode}
flagsPath={flagsPath}
/> />
); );
}; };

View File

@ -3,9 +3,8 @@ import React from "react";
const NotificationsContainer = () => { const NotificationsContainer = () => {
return ( return (
<div> <div>
Enable/Disable email notifications (for each one separately - when Enable/Disable email notifications (for each one separately - when starting the machine, when stopping) You can go
starting the machine, when stopping) You can go even further and have an even further and have an advanced site where you can configure each individual machine.
advanced site where you can configure each individual machine.
</div> </div>
); );
}; };

View File

@ -1,37 +1,22 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Paper, Button } from "@material-ui/core"; import { Paper, Button, Box } from "@mui/material";
import { makeStyles } from "@material-ui/core/styles";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { PaperTitle } from "components/common"; import { PaperTitle } from "components/common";
import { usePermissions } from "hooks"; import { usePermissions } from "hooks";
const useStyles = makeStyles(theme => ({
content: {
"& > *": {
margin: theme.spacing(1)
}
}
}));
const CacheSettingsComponent = ({ onResetCache }) => { const CacheSettingsComponent = ({ onResetCache }) => {
const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
const { sysAdmin } = usePermissions(); const { sysAdmin } = usePermissions();
return ( return (
<Paper variant="outlined"> <Paper variant="outlined">
<PaperTitle text={t("Settings.Cache.Title")} /> <PaperTitle text={t("Settings.Cache.Title")} />
<div className={classes.content}> <Box sx={{ margin: 1 }}>
<Button <Button variant="outlined" color="secondary" disabled={!sysAdmin} onClick={onResetCache}>
variant="outlined"
color="secondary"
disabled={!sysAdmin}
onClick={onResetCache}
>
{t("Settings.Cache.Reset")} {t("Settings.Cache.Reset")}
</Button> </Button>
</div> </Box>
</Paper> </Paper>
); );
}; };

View File

@ -1,6 +1,6 @@
import React, { useState, useMemo } from "react"; import React, { useState, useMemo } from "react";
import CategoryIcon from "@material-ui/icons/Category"; import CategoryIcon from "@mui/icons-material/Category";
import GrainIcon from "@material-ui/icons/Grain"; import GrainIcon from "@mui/icons-material/Grain";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import PageTitle from "../../components/common/PageTitle"; import PageTitle from "../../components/common/PageTitle";
import NavigationButtons from "../../components/common/NavigationButtons"; import NavigationButtons from "../../components/common/NavigationButtons";
@ -27,19 +27,11 @@ const SystemContainer = () => {
const [tab, setTab] = useState(NavigationTabs.MAIN_SERVICES); const [tab, setTab] = useState(NavigationTabs.MAIN_SERVICES);
const { t } = useTranslation(); const { t } = useTranslation();
const navigationTabs = useMemo( const navigationTabs = useMemo(() => tabs.map(z => ({ ...z, tooltip: t(z.code) })), [t]);
() => tabs.map(z => ({ ...z, tooltip: t(z.code) })),
[t]
);
return ( return (
<> <>
<PageTitle <PageTitle text={t(tab)} navigation={<NavigationButtons tabs={navigationTabs} onTabChange={setTab} />} />
text={t(tab)}
navigation={
<NavigationButtons tabs={navigationTabs} onTabChange={setTab} />
}
/>
{tab === NavigationTabs.MAIN_SERVICES && <MainServicesContainer />} {tab === NavigationTabs.MAIN_SERVICES && <MainServicesContainer />}
{tab === NavigationTabs.AGENTS && <AgentsContainer />} {tab === NavigationTabs.AGENTS && <AgentsContainer />}
</> </>

View File

@ -2,19 +2,15 @@ import React, { useMemo } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import UserProfilePicture from "./UserProfilePicture"; import UserProfilePicture from "./UserProfilePicture";
import ContactOptions from "../contact/ContactOptions"; import ContactOptions from "../contact/ContactOptions";
import { makeStyles } from "@material-ui/core/styles"; import { useTheme } from "@mui/material/styles";
import styles from "../styles"; import { getStyles } from "../styles";
const useStyles = makeStyles(styles);
const UserProfileCardContent = ({ userData }) => { const UserProfileCardContent = ({ userData }) => {
const { profilePictureUrl } = userData; const { profilePictureUrl } = userData;
const classes = useStyles(); const theme = useTheme();
const styles = getStyles(theme);
const userName = useMemo( const userName = useMemo(() => `${userData.firstName} ${userData.lastName}`, [userData.firstName, userData.lastName]);
() => `${userData.firstName} ${userData.lastName}`,
[userData.firstName, userData.lastName]
);
const _contactOptions = useMemo( const _contactOptions = useMemo(
() => () =>
profilePictureUrl profilePictureUrl
@ -31,7 +27,7 @@ const UserProfileCardContent = ({ userData }) => {
); );
return ( return (
<div className={classes.panel}> <div style={styles.panel}>
<UserProfilePicture pictureUrl={userData.profilePictureUrl} /> <UserProfilePicture pictureUrl={userData.profilePictureUrl} />
<ContactOptions contactOptions={_contactOptions} userName={userName} /> <ContactOptions contactOptions={_contactOptions} userName={userName} />
</div> </div>

View File

@ -1,18 +1,17 @@
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Card, CardHeader, CardContent } from "@material-ui/core"; import { Card, CardHeader, CardContent } from "@mui/material";
import PageTitle from "../../../../components/common/PageTitle"; import PageTitle from "../../../../components/common/PageTitle";
import UserProfileCardContent from "./UserProfileCardContent"; import UserProfileCardContent from "./UserProfileCardContent";
import SecurityComponent from "../security/SecurityComponent"; import SecurityComponent from "../security/SecurityComponent";
import styles from "../styles"; import { getStyles } from "../styles";
import { makeStyles } from "@material-ui/core/styles"; import { useTheme } from "@mui/material/styles";
const useStyles = makeStyles(styles);
const UserProfileComponent = ({ userData }) => { const UserProfileComponent = ({ userData }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const classes = useStyles(); const theme = useTheme();
const styles = getStyles(theme);
const userLoginDate = useMemo( const userLoginDate = useMemo(
() => () =>
@ -29,9 +28,7 @@ const UserProfileComponent = ({ userData }) => {
return ( return (
<> <>
<PageTitle <PageTitle text={t("User.Profile.Hello", { userName: userData.firstName })} />
text={t("User.Profile.Hello", { userName: userData.firstName })}
/>
<Card> <Card>
<CardHeader title={userData.userName} subheader={userDescription} /> <CardHeader title={userData.userName} subheader={userDescription} />
<CardContent> <CardContent>
@ -39,11 +36,8 @@ const UserProfileComponent = ({ userData }) => {
</CardContent> </CardContent>
</Card> </Card>
<div className={classes.section}> <div style={styles.section}>
<SecurityComponent <SecurityComponent userGroups={userData.userGroups} userRoles={userData.userRoles} />
userGroups={userData.userGroups}
userRoles={userData.userRoles}
/>
</div> </div>
</> </>
); );

View File

@ -2,7 +2,7 @@ import React from "react";
import { useTuitioUserInfo } from "@flare/tuitio-client-react"; import { useTuitioUserInfo } from "@flare/tuitio-client-react";
import UserProfileComponent from "./UserProfileComponent"; import UserProfileComponent from "./UserProfileComponent";
const UserProfileContainer = () => { const UserProfileContainer: React.FC = () => {
const { userInfo } = useTuitioUserInfo(); const { userInfo } = useTuitioUserInfo();
return <>{userInfo && <UserProfileComponent userData={userInfo} />}</>; return <>{userInfo && <UserProfileComponent userData={userInfo} />}</>;
}; };

View File

@ -1,16 +1,15 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { makeStyles } from "@material-ui/core/styles"; import { useTheme } from "@mui/material/styles";
import style from "../styles"; import { getStyles } from "../styles";
import Avatar from "@material-ui/core/Avatar"; import Avatar from "@mui/material/Avatar";
import DefaultUserProfilePicture from "../../../../assets/images/DefaultUserProfilePicture.png"; import DefaultUserProfilePicture from "../../../../assets/images/DefaultUserProfilePicture.png";
const useStyles = makeStyles(style);
const UserProfilePicture = ({ pictureUrl }) => { const UserProfilePicture = ({ pictureUrl }) => {
const classes = useStyles(); const theme = useTheme();
const styles = getStyles(theme);
const url = pictureUrl ?? DefaultUserProfilePicture; const url = pictureUrl ?? DefaultUserProfilePicture;
return <Avatar src={url} alt="..." className={classes.profilePicture} />; return <Avatar src={url} alt="..." sx={styles.profilePicture} />;
}; };
UserProfilePicture.propTypes = { UserProfilePicture.propTypes = {

View File

@ -1,13 +1,6 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { import { ListItem, ListItemText, ListItemIcon, Link, Tooltip, IconButton } from "@mui/material";
ListItem,
ListItemText,
ListItemIcon,
Link,
Tooltip,
IconButton
} from "@material-ui/core";
const ContactIcon = ({ onIconClick, iconTooltip, ...props }) => { const ContactIcon = ({ onIconClick, iconTooltip, ...props }) => {
if (!onIconClick) return <props.icon />; if (!onIconClick) return <props.icon />;

View File

@ -1,6 +1,6 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { List } from "@material-ui/core"; import { List } from "@mui/material";
import ContactOption from "./ContactOption"; import ContactOption from "./ContactOption";
const ContactOptionList = ({ options }) => { const ContactOptionList = ({ options }) => {

View File

@ -1,18 +1,18 @@
import React, { useCallback, useMemo } from "react"; import React, { useCallback, useMemo } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { Grid } from "@material-ui/core"; import { Grid } from "@mui/material";
import ContactOptionList from "./ContactOptionList"; import ContactOptionList from "./ContactOptionList";
import BusinessCenterIcon from "@material-ui/icons/BusinessCenter"; import BusinessCenterIcon from "@mui/icons-material/BusinessCenter";
import EmailIcon from "@material-ui/icons/Email"; import EmailIcon from "@mui/icons-material/Email";
import PhoneAndroidIcon from "@material-ui/icons/PhoneAndroid"; import PhoneAndroidIcon from "@mui/icons-material/PhoneAndroid";
import LanguageIcon from "@material-ui/icons/Language"; import LanguageIcon from "@mui/icons-material/Language";
import LinkedInIcon from "@material-ui/icons/LinkedIn"; import LinkedInIcon from "@mui/icons-material/LinkedIn";
import GitHubIcon from "@material-ui/icons/GitHub"; import GitHubIcon from "@mui/icons-material/GitHub";
import RedditIcon from "@material-ui/icons/Reddit"; import RedditIcon from "@mui/icons-material/Reddit";
import BookIcon from "@material-ui/icons/Book"; import BookIcon from "@mui/icons-material/Book";
import MenuBookIcon from "@material-ui/icons/MenuBook"; import MenuBookIcon from "@mui/icons-material/MenuBook";
import FileCopyOutlinedIcon from "@material-ui/icons/FileCopyOutlined"; import FileCopyOutlinedIcon from "@mui/icons-material/FileCopyOutlined";
import { useClipboard } from "../../../../hooks"; import { useClipboard } from "../../../../hooks";
const icons = { const icons = {
@ -62,8 +62,7 @@ const getTooltip = contactOption => {
}; };
const getOrderNumber = contactOption => { const getOrderNumber = contactOption => {
const orderNo = const orderNo = orderNumbers[contactOption.contactTypeCode] || orderNumbers.DEFAULT;
orderNumbers[contactOption.contactTypeCode] || orderNumbers.DEFAULT;
return orderNo; return orderNo;
}; };
@ -152,10 +151,7 @@ const ContactOptions = ({ contactOptions, userName }) => {
[contactOptions, getOnClickEvent, getIconClickEvent, t, userName] [contactOptions, getOnClickEvent, getIconClickEvent, t, userName]
); );
const sorted = useMemo( const sorted = useMemo(() => enrichedContactOptions.sort((a, b) => a.orderNo - b.orderNo), [enrichedContactOptions]);
() => enrichedContactOptions.sort((a, b) => a.orderNo - b.orderNo),
[enrichedContactOptions]
);
const chunks = useMemo(() => sliceContactOptions(sorted), [sorted]); const chunks = useMemo(() => sliceContactOptions(sorted), [sorted]);

View File

@ -1,43 +1,37 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Paper, Grid, Chip, Typography } from "@material-ui/core"; import { Paper, Grid, Chip, Typography } from "@mui/material";
import styles from "../styles"; import { getStyles } from "../styles";
import { makeStyles } from "@material-ui/core/styles"; import { useTheme } from "@mui/material/styles";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
const useStyles = makeStyles(styles);
const SecurityComponent = ({ userGroups, userRoles }) => { const SecurityComponent = ({ userGroups, userRoles }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const classes = useStyles(); const theme = useTheme();
const styles = getStyles(theme);
return ( return (
<Paper> <Paper>
<Grid container> <Grid container>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<div className={classes.paper}> <div style={styles.paper}>
<Typography gutterBottom variant="body1"> <Typography gutterBottom variant="body1">
{t("User.Profile.Security.UserGroups")} {t("User.Profile.Security.UserGroups")}
</Typography> </Typography>
<div> <div>
{userGroups.map(g => ( {userGroups.map(g => (
<Chip key={g.code} className={classes.chip} label={g.name} /> <Chip key={g.code} style={styles.chip} label={g.name} />
))} ))}
</div> </div>
</div> </div>
</Grid> </Grid>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<div className={classes.paper}> <div style={styles.paper}>
<Typography gutterBottom variant="body1"> <Typography gutterBottom variant="body1">
{t("User.Profile.Security.UserRoles")} {t("User.Profile.Security.UserRoles")}
</Typography> </Typography>
<div> <div>
{userRoles.map(r => ( {userRoles.map(r => (
<Chip <Chip key={r.code} style={styles.chip} color="primary" label={r.name} />
key={r.code}
className={classes.chip}
color="primary"
label={r.name}
/>
))} ))}
</div> </div>
</div> </div>

View File

@ -1,4 +1,4 @@
const style = theme => { const getStyles = theme => {
return { return {
section: { section: {
marginTop: theme.spacing(2) marginTop: theme.spacing(2)
@ -6,7 +6,7 @@ const style = theme => {
panel: { panel: {
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "row",
"@media (max-width: 600px)": { "@media (maxWidth: 600px)": {
flexDirection: "column" // change direction for small screens flexDirection: "column" // change direction for small screens
} }
}, },
@ -25,4 +25,4 @@ const style = theme => {
}; };
}; };
export default style; export { getStyles };

View File

@ -1,14 +1,16 @@
import React, { Suspense } from "react"; import React, { Suspense } from "react";
import ReactDOM from "react-dom"; import ReactDOM from "react-dom/client";
import ThemeProvider from "./providers/ThemeProvider"; import ThemeProvider from "./providers/ThemeProvider";
import CssBaseline from "@material-ui/core/CssBaseline"; import { CssBaseline } from "@mui/material";
import AppRouter from "./components/AppRouter"; import AppRouter from "./components/AppRouter";
import { TuitioProvider } from "@flare/tuitio-client-react"; import { TuitioProvider } from "@flare/tuitio-client-react";
import { ToastProvider } from "./providers"; import { ToastProvider } from "./providers";
import env from "./utils/env"; import env from "./utils/env";
import "./utils/i18n"; import "./utils/i18n";
ReactDOM.render( const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
root.render(
<TuitioProvider tuitioUrl={env.REACT_APP_TUITIO_URL}> <TuitioProvider tuitioUrl={env.REACT_APP_TUITIO_URL}>
<ThemeProvider> <ThemeProvider>
<CssBaseline /> <CssBaseline />
@ -18,6 +20,5 @@ ReactDOM.render(
</ToastProvider> </ToastProvider>
</Suspense> </Suspense>
</ThemeProvider> </ThemeProvider>
</TuitioProvider>, </TuitioProvider>
document.getElementById("root")
); );

View File

@ -23,8 +23,7 @@ const reducer = (state = initialState, action) => {
}; };
const dispatchActions = dispatch => ({ const dispatchActions = dispatch => ({
onSensitiveInfoEnabled: enabled => onSensitiveInfoEnabled: enabled => dispatch({ type: "onSensitiveInfoEnabled", payload: { enabled } })
dispatch({ type: "onSensitiveInfoEnabled", payload: { enabled } })
}); });
const useSensitiveInfo = () => { const useSensitiveInfo = () => {

View File

@ -1,6 +1,6 @@
import React, { useReducer, useMemo, useContext } from "react"; import React, { useReducer, useMemo, useContext } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { ThemeProvider as MuiThemeProvider } from "@material-ui/styles"; import { ThemeProvider as MuiThemeProvider } from "@mui/material/styles";
import { localStorage } from "@flare/js-utils"; import { localStorage } from "@flare/js-utils";
import { getThemes } from "../themes"; import { getThemes } from "../themes";
@ -13,13 +13,10 @@ const COLOR_SCHEME = {
}; };
const colorScheme = localStorage.getItem(LOCAL_STORAGE_COLOR_SCHEME_KEY); const colorScheme = localStorage.getItem(LOCAL_STORAGE_COLOR_SCHEME_KEY);
const prefersDarkMode = window.matchMedia( const prefersDarkMode = window.matchMedia("(prefers-color-scheme: dark)").matches;
"(prefers-color-scheme: dark)"
).matches;
const initialState = { const initialState = {
scheme: scheme: colorScheme ?? (prefersDarkMode ? COLOR_SCHEME.DARK : COLOR_SCHEME.LIGHT)
colorScheme ?? (prefersDarkMode ? COLOR_SCHEME.DARK : COLOR_SCHEME.LIGHT)
}; };
const reducer = (state = initialState, action) => { const reducer = (state = initialState, action) => {
@ -48,8 +45,7 @@ const useApplicationTheme = () => {
const { onColorSchemeChanged } = actions; const { onColorSchemeChanged } = actions;
const { scheme } = state; const { scheme } = state;
const onDarkModeChanged = active => const onDarkModeChanged = active => onColorSchemeChanged(active ? COLOR_SCHEME.DARK : COLOR_SCHEME.LIGHT);
onColorSchemeChanged(active ? COLOR_SCHEME.DARK : COLOR_SCHEME.LIGHT);
return { isDark: scheme === COLOR_SCHEME.DARK, onDarkModeChanged }; return { isDark: scheme === COLOR_SCHEME.DARK, onDarkModeChanged };
}; };
@ -57,10 +53,7 @@ const useApplicationTheme = () => {
const ThemeProvider = ({ children }) => { const ThemeProvider = ({ children }) => {
const [state, dispatch] = useReducer(reducer, initialState); const [state, dispatch] = useReducer(reducer, initialState);
const actions = useMemo(() => dispatchActions(dispatch), [dispatch]); const actions = useMemo(() => dispatchActions(dispatch), [dispatch]);
const themes = useMemo( const themes = useMemo(() => getThemes(state.scheme === COLOR_SCHEME.DARK), [state.scheme]);
() => getThemes(state.scheme === COLOR_SCHEME.DARK),
[state.scheme]
);
return ( return (
<ApplicationThemeContext.Provider <ApplicationThemeContext.Provider

View File

@ -19,22 +19,14 @@ const initialState = {
}; };
const getPermissionFlags = permissions => { const getPermissionFlags = permissions => {
const viewDashboard = const viewDashboard = permissions.includes(permissionCodes.VIEW_DASHBOARD) ?? false;
permissions.includes(permissionCodes.VIEW_DASHBOARD) ?? false; const manageUsers = permissions.includes(permissionCodes.MANAGE_USERS) ?? false;
const manageUsers = const manageSettings = permissions.includes(permissionCodes.MANAGE_SETTINGS) ?? false;
permissions.includes(permissionCodes.MANAGE_USERS) ?? false; const viewMachines = permissions.includes(permissionCodes.VIEW_MACHINES) ?? false;
const manageSettings = const manageMachines = permissions.includes(permissionCodes.MANAGE_MACHINES) ?? false;
permissions.includes(permissionCodes.MANAGE_SETTINGS) ?? false; const operateMachines = permissions.includes(permissionCodes.OPERATE_MACHINES) ?? false;
const viewMachines = const guestAccess = permissions.includes(permissionCodes.GUEST_ACCESS) ?? false;
permissions.includes(permissionCodes.VIEW_MACHINES) ?? false; const sysAdmin = permissions.includes(permissionCodes.SYSTEM_ADMINISTRATION) ?? false;
const manageMachines =
permissions.includes(permissionCodes.MANAGE_MACHINES) ?? false;
const operateMachines =
permissions.includes(permissionCodes.OPERATE_MACHINES) ?? false;
const guestAccess =
permissions.includes(permissionCodes.GUEST_ACCESS) ?? false;
const sysAdmin =
permissions.includes(permissionCodes.SYSTEM_ADMINISTRATION) ?? false;
const flags = { const flags = {
viewDashboard, viewDashboard,
@ -74,11 +66,7 @@ const UserPermissionsProvider = ({ children }) => {
}); });
}, []); }, []);
return ( return <UserPermissionsContext.Provider value={permissions}>{children}</UserPermissionsContext.Provider>;
<UserPermissionsContext.Provider value={permissions}>
{children}
</UserPermissionsContext.Provider>
);
}; };
UserPermissionsProvider.propTypes = { UserPermissionsProvider.propTypes = {

View File

@ -3,9 +3,4 @@ import ToastProvider from "./ToastProvider";
import SensitiveInfoProvider from "./SensitiveInfoProvider"; import SensitiveInfoProvider from "./SensitiveInfoProvider";
import UserPermissionsProvider from "./UserPermissionsProvider"; import UserPermissionsProvider from "./UserPermissionsProvider";
export { export { ThemeProvider, ToastProvider, SensitiveInfoProvider, UserPermissionsProvider };
ThemeProvider,
ToastProvider,
SensitiveInfoProvider,
UserPermissionsProvider
};

View File

@ -1,35 +0,0 @@
const primary = "#00695C";
const secondary = "#DC7633";
const warning = "#ff9800";
const success = "#4caf50";
const info = "#2196f3";
const defaultTheme = {
palette: {
primary: {
main: primary
},
secondary: {
main: secondary
// contrastText: "#ffcc00"
},
warning: {
main: warning
},
success: {
main: success
},
info: {
main: info
}
},
overrides: {
MuiBackdrop: {
root: {
backgroundColor: "#4A4A4A1A"
}
}
}
};
export default defaultTheme;

View File

@ -0,0 +1,69 @@
import { ThemeOptions } from "@mui/material/styles";
const primary = "#00695C";
const secondary = "#DC7633";
const warning = "#ff9800";
const success = "#4caf50";
const info = "#2196f3";
// background_default: "#f4f5fd",
export const commonTheme: ThemeOptions = {
palette: {
primary: {
main: primary
},
secondary: {
main: secondary
},
warning: {
main: warning
},
success: {
main: success
},
info: {
main: info
}
},
components: {
MuiAppBar: {
defaultProps: {
enableColorOnDark: true
}
},
MuiPaper: {
styleOverrides: {
root: {
backgroundImage: "unset" // The gradient is part of how MUI implements the elevation of the Paper component in dark mode. If you don't want this gradient, you can override it with "unset".
}
}
}
}
};
const lightThemeOptions: ThemeOptions = {
...commonTheme,
palette: {
...commonTheme.palette,
mode: "light",
background: {
default: "#fafafa",
paper: "#fff"
}
}
};
const darkThemeOptions: ThemeOptions = {
...commonTheme,
palette: {
...commonTheme.palette,
mode: "dark",
background: {
default: "#303030",
paper: "#424242"
}
}
};
export { lightThemeOptions, darkThemeOptions };

View File

@ -1,39 +0,0 @@
import defaultTheme from "./default";
import { createTheme } from "@material-ui/core/styles";
const overrides = {
typography: {
h1: {
fontSize: "3rem"
},
h2: {
fontSize: "2rem"
},
h3: {
fontSize: "1.64rem"
},
h4: {
fontSize: "1.5rem"
},
h5: {
fontSize: "1.285rem"
},
h6: {
fontSize: "1.142rem"
}
}
};
const getThemes = darkMode => {
const type = darkMode ? "dark" : "light";
return {
default: createTheme({
...defaultTheme,
...overrides,
palette: { ...defaultTheme.palette, type }
})
};
};
export { getThemes };

View File

@ -0,0 +1,43 @@
import { lightThemeOptions, darkThemeOptions } from "./defaults";
import { createTheme, Theme } from "@mui/material/styles";
type Themes = {
default: Theme;
};
const overrides = {
typography: {
h1: {
fontSize: "3rem"
},
h2: {
fontSize: "2rem"
},
h3: {
fontSize: "1.64rem"
},
h4: {
fontSize: "1.5rem"
},
h5: {
fontSize: "1.285rem"
},
h6: {
fontSize: "1.142rem"
}
}
};
const getThemes = (darkMode: boolean): Themes => {
const defaultTheme = darkMode ? darkThemeOptions : lightThemeOptions;
const dTheme = createTheme({
...defaultTheme,
...overrides
});
return {
default: dTheme
};
};
export { getThemes };

View File

@ -0,0 +1,5 @@
import * as models from "./models";
export * from "./models";
export { models };

View File

@ -0,0 +1,8 @@
export type Machine = {
machineId: number;
machineName: string;
fullMachineName: string;
macAddress: string;
iPv4Address?: string;
description?: string;
};

View File

@ -38,7 +38,7 @@ const handleError = err => {
}; };
const defaultOptions = { const defaultOptions = {
onCompleted: () => {}, onCompleted: () => null,
onError: handleError onError: handleError
}; };

Some files were not shown because too many files have changed in this diff Show More