Compare commits

..

No commits in common. "e80d246fc05ae304951b88ed245285b8dd8530e0" and "08f1360f21915d2a6a334b3f1a269339ab3485e1" have entirely different histories.

104 changed files with 12350 additions and 32148 deletions

View File

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

View File

@ -201,15 +201,4 @@
• 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>

View File

@ -1,34 +0,0 @@
{
"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
}
}

38
frontend/.eslintrc.json Normal file
View File

@ -0,0 +1,38 @@
{
"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": "^_" }]
}
}

View File

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

View File

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

View File

@ -1,41 +0,0 @@
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;
};

6
frontend/jsconfig.json Normal file
View File

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

41652
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.3.0", "version": "1.2.7",
"description": "Frontend component of Network resurrector system", "description": "Frontend component of Network resurrector system",
"author": { "author": {
"name": "Tudor Stanciu", "name": "Tudor Stanciu",
@ -9,49 +9,41 @@
}, },
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://lab.code-rove.com/gitea/tudor.stanciu/network-resurrector" "url": "https://lab.code-rove.com/gitea/tudor.stanciu/network-resurrector-frontend"
}, },
"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.10", "@flare/tuitio-client-react": "^1.2.6",
"@mui/icons-material": "^5.14.16", "@material-ui/core": "^4.11.2",
"@mui/lab": "^5.0.0-alpha.169", "@material-ui/icons": "^4.11.2",
"@mui/material": "^5.14.16", "@material-ui/lab": "^4.0.0-alpha.61",
"axios": "^1.6.8", "axios": "^1.3.4",
"i18next": "^22.4.15", "i18next": "^19.4.4",
"i18next-browser-languagedetector": "^7.0.1", "i18next-browser-languagedetector": "^4.1.1",
"i18next-http-backend": "^2.2.0", "i18next-http-backend": "^1.4.0",
"moment": "^2.29.4", "moment": "^2.29.3",
"react": "^18.2.0", "react": "^17.0.1",
"react-dom": "^18.2.0", "react-dom": "^17.0.1",
"react-i18next": "^12.2.2", "react-flags": "^0.1.18",
"react-i18next": "^11.4.0",
"react-lazylog": "^4.5.3", "react-lazylog": "^4.5.3",
"react-router-dom": "^6.10.0", "react-router-dom": "^5.2.0",
"react-toastify": "^9.1.3", "react-scripts": "4.0.1",
"react-world-flags": "^1.6.0" "react-toastify": "^6.2.0"
}, },
"devDependencies": { "devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11", "@babel/eslint-parser": "^7.16.5",
"@types/react": "^18.2.33", "@testing-library/jest-dom": "^5.11.6",
"@types/react-dom": "^18.2.14", "@testing-library/react": "^11.2.2",
"@types/react-world-flags": "^1.4.5", "@testing-library/user-event": "^12.5.0",
"eslint": "^8.34.0", "prettier": "^2.5.1"
"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-app-rewired start", "start": "react-scripts start",
"build": "react-app-rewired build", "build": "react-scripts build",
"test": "react-app-rewired test", "test": "react-scripts test",
"eject": "react-app-rewired eject" "eject": "react-scripts eject"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
@ -61,14 +53,17 @@
}, },
"browserslist": { "browserslist": {
"production": [ "production": [
">0.2%", ">0.3%",
"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

@ -0,0 +1,13 @@
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.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

View File

@ -0,0 +1,73 @@
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

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

View File

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

View File

@ -1,24 +1,28 @@
import React from "react"; import React from "react";
import { Alert, AlertTitle, Box } from "@mui/material"; import { makeStyles } from "@material-ui/core/styles";
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 (
<Box <div className={classes.alert}>
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>
</Box> </div>
); );
}; };

View File

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

View File

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

View File

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

View File

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

View File

@ -1,19 +0,0 @@
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

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

View File

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

View File

@ -0,0 +1,34 @@
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

@ -1,54 +0,0 @@
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, Routes } from "react-router-dom"; import { Route, Switch } 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: React.FC = () => { const AppRoutes = () => {
return ( return (
<Routes> <Switch>
<Route path="/dashboard" element={<DashboardContainer />} /> <Route exact path="/dashboard" component={DashboardContainer} />
<Route path="/user-profile" element={<UserProfileContainer />} /> <Route exact path="/user-profile" component={UserProfileContainer} />
<Route path="/machines" element={<NetworkContainer />} /> <Route exact path="/machines" component={NetworkContainer} />
<Route path="/system" element={<SystemContainer />} /> <Route exact path="/system" component={SystemContainer} />
<Route path="/settings" element={<SettingsContainer />} /> <Route exact path="/settings" component={SettingsContainer} />
<Route path="/about" element={<AboutContainer />} /> <Route exact path="/about" component={AboutContainer} />
<Route path="/*" element={<PageNotFound />} /> <Route component={PageNotFound} />
</Routes> </Switch>
); );
}; };

View File

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

View File

@ -1,84 +0,0 @@
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,17 +1,28 @@
import React, { useState } from "react"; import React, { useState } from "react";
import { IconButton, Menu, MenuItem, Typography, ListItemIcon } from "@mui/material"; import {
import AccountCircle from "@mui/icons-material/AccountCircle"; IconButton,
import ExitToAppIcon from "@mui/icons-material/ExitToApp"; Menu,
import AccountBoxIcon from "@mui/icons-material/AccountBox"; MenuItem,
import SettingsIcon from "@mui/icons-material/Settings"; Typography,
import { useNavigate } from "react-router-dom"; ListItemIcon
} 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 navigate = useNavigate(); const history = useHistory();
const { error } = useToast(); const { error } = useToast();
const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
const { logout } = useTuitioClient({ const { logout } = useTuitioClient({
@ -58,28 +69,28 @@ const ProfileButton = () => {
> >
<MenuItem <MenuItem
onClick={() => { onClick={() => {
navigate("/user-profile"); history.push("/user-profile");
handleClose(); handleClose();
}} }}
> >
<ListItemIcon sx={{ minWidth: "26px" }}> <ListItemIcon className={classes.menuItemIcon}>
<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={() => {
navigate("/settings"); history.push("/settings");
handleClose(); handleClose();
}} }}
> >
<ListItemIcon sx={{ minWidth: "26px" }}> <ListItemIcon className={classes.menuItemIcon}>
<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 sx={{ minWidth: "26px" }}> <ListItemIcon className={classes.menuItemIcon}>
<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,6 +1,9 @@
import React from "react"; import React from "react";
import { IconButton } from "@mui/material"; import { IconButton } from "@material-ui/core";
import { Visibility as VisibilityIcon, VisibilityOff as VisibilityOffIcon } from "@mui/icons-material"; import {
Visibility as VisibilityIcon,
VisibilityOff as VisibilityOffIcon
} from "@material-ui/icons";
import { useSensitiveInfo } from "../../hooks"; import { useSensitiveInfo } from "../../hooks";
const SensitiveInfoToggle = () => { const SensitiveInfoToggle = () => {
@ -9,7 +12,11 @@ const SensitiveInfoToggle = () => {
const handleChange = () => onSensitiveInfoEnabled(!enabled); const handleChange = () => onSensitiveInfoEnabled(!enabled);
return ( return (
<IconButton aria-label="sensitive-info-toggle" color="inherit" onClick={handleChange}> <IconButton
aria-label="sensitive-info-toggle"
color="inherit"
onClick={handleChange}
>
{enabled ? <VisibilityOffIcon /> : <VisibilityIcon />} {enabled ? <VisibilityOffIcon /> : <VisibilityIcon />}
</IconButton> </IconButton>
); );

View File

@ -1,112 +0,0 @@
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

@ -0,0 +1,162 @@
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

@ -0,0 +1,52 @@
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

@ -1,72 +0,0 @@
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

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

View File

@ -1,98 +0,0 @@
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

@ -0,0 +1,71 @@
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 "@mui/icons-material/BubbleChart"; import BubbleChartIcon from "@material-ui/icons/BubbleChart";
import NotesIcon from "@mui/icons-material/Notes"; import NotesIcon from "@material-ui/icons/Notes";
import TimelineIcon from "@mui/icons-material/Timeline"; import TimelineIcon from "@material-ui/icons/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,14 +33,24 @@ const AboutContainer = () => {
const [tab, setTab] = useState(NavigationTabs.SYSTEM); const [tab, setTab] = useState(NavigationTabs.SYSTEM);
const { t } = useTranslation(); const { t } = useTranslation();
const navigationTabs = useMemo(() => tabs.map(z => ({ ...z, tooltip: t(z.code) })), [t]); const navigationTabs = useMemo(
() => tabs.map(z => ({ ...z, tooltip: t(z.code) })),
[t]
);
return ( return (
<> <>
<PageTitle text={t(tab)} navigation={<NavigationButtons tabs={navigationTabs} onTabChange={setTab} />} /> <PageTitle
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 && <ReleaseNotesContainer view="timeline" />} {tab === NavigationTabs.TIMELINE && (
<ReleaseNotesContainer view="timeline" />
)}
</> </>
); );
}; };

View File

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

View File

@ -4,7 +4,8 @@ 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 => releases.sort((a, b) => new Date(b.date) - new Date(a.date)); const sort = releases =>
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,7 +1,11 @@
import React, { useState } from "react"; import React, { useState } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Accordion, AccordionSummary, AccordionDetails } from "@mui/material"; import {
import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; Accordion,
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";
@ -25,9 +29,18 @@ const ReleaseNotesList = ({ releases }) => {
<> <>
{releases.map(release => { {releases.map(release => {
return ( return (
<Accordion key={release.version} onChange={handleToggle(release.version)}> <Accordion
<AccordionSummary expandIcon={<ExpandMoreIcon />} id={`panel-${release.version}-header`}> key={release.version}
<ReleaseNoteSummary releaseNote={release} collapsed={isCollapsed(release.version)} /> onChange={handleToggle(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,28 +1,29 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import Card from "@mui/material/Card"; import { makeStyles } from "@material-ui/core/styles";
import CardActions from "@mui/material/CardActions"; import Card from "@material-ui/core/Card";
import CardContent from "@mui/material/CardContent"; import CardActions from "@material-ui/core/CardActions";
import Button from "@mui/material/Button"; import CardContent from "@material-ui/core/CardContent";
import Typography from "@mui/material/Typography"; import Button from "@material-ui/core/Button";
import Typography from "@material-ui/core/Typography";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import OpenInNewIcon from "@mui/icons-material/OpenInNew"; import OpenInNewIcon from "@material-ui/icons/OpenInNew";
const styles = { const useStyles = makeStyles(theme => ({
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: 1 marginTop: theme.spacing(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/src/branch/master/frontend" url: "https://lab.code-rove.com/gitea/tudor.stanciu/network-resurrector-frontend"
}, },
{ {
code: "About.System.Services.Api", code: "About.System.Services.Api",
@ -39,9 +40,10 @@ const buttons = [
]; ];
const AboutSystemComponent = ({ handleOpenInNewTab }) => { const AboutSystemComponent = ({ handleOpenInNewTab }) => {
const classes = useStyles();
const { t } = useTranslation(); const { t } = useTranslation();
const bullet = <span style={styles.bullet}></span>; const bullet = <span className={classes.bullet}></span>;
return ( return (
<Card variant="outlined"> <Card variant="outlined">
@ -49,26 +51,30 @@ 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">{t("About.System.Description.FirstPhrase")}</Typography> <Typography color="textSecondary">
<Typography color="textSecondary">{t("About.System.Description.SecondPhrase")}</Typography> {t("About.System.Description.FirstPhrase")}
</Typography>
<Typography color="textSecondary">
{t("About.System.Description.SecondPhrase")}
</Typography>
<Typography sx={styles.service} color="textSecondary"> <Typography className={classes.service} color="textSecondary">
{bullet} {bullet}
{t("About.System.Description.Frontend")} {t("About.System.Description.Frontend")}
</Typography> </Typography>
<Typography sx={styles.service} color="textSecondary"> <Typography className={classes.service} color="textSecondary">
{bullet} {bullet}
{t("About.System.Description.Api")} {t("About.System.Description.Api")}
</Typography> </Typography>
<Typography sx={styles.service} color="textSecondary"> <Typography className={classes.service} color="textSecondary">
{bullet} {bullet}
{t("About.System.Description.Server")} {t("About.System.Description.Server")}
</Typography> </Typography>
<Typography sx={styles.service} color="textSecondary"> <Typography className={classes.service} color="textSecondary">
{bullet} {bullet}
{t("About.System.Description.Agent")} {t("About.System.Description.Agent")}
</Typography> </Typography>
<Typography sx={styles.service} color="textSecondary"> <Typography className={classes.service} color="textSecondary">
{bullet} {bullet}
{t("About.System.Description.Tuitio")} {t("About.System.Description.Tuitio")}
</Typography> </Typography>

View File

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

View File

@ -1,44 +1,50 @@
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 { List, ListItem, ListItemText, ListItemAvatar } from "@mui/material"; import { makeStyles } from "@material-ui/core/styles";
import Avatar from "@mui/material/Avatar"; import {
import WebAssetIcon from "@mui/icons-material/WebAsset"; List,
import DeveloperBoardIcon from "@mui/icons-material/DeveloperBoard"; ListItem,
import SettingsInputSvideoIcon from "@mui/icons-material/SettingsInputSvideo"; ListItemText,
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 "@mui/material/Paper"; import Paper from "@material-ui/core/Paper";
import { useTheme } from "@mui/material/styles";
const getStyles = theme => ({ const useStyles = makeStyles(theme => {
horizontally: { return {
display: "flex", horizontally: {
flexDirection: "row", display: "flex",
padding: 0 flexDirection: "row",
}, padding: 0
vertical: { },
width: "100%" vertical: {
}, width: "100%"
value: { },
fontSize: "0.9rem", value: {
fontWeight: theme.typography.fontWeightMedium fontSize: "0.9rem",
}, fontWeight: theme.typography.fontWeightMedium
versionAvatar: { },
backgroundColor: theme.palette.secondary.main versionAvatar: {
} 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 ? styles.vertical : styles.horizontally; const cssClass = event.matches ? classes.vertical : classes.horizontally;
setListClass(cssClass); setListClass(cssClass);
} }
@ -48,7 +54,7 @@ const SystemVersionComponent = ({ data }) => {
return () => { return () => {
mediaQuery.removeListener(handleMatches); mediaQuery.removeListener(handleMatches);
}; };
}, [styles.horizontally, styles.vertical]); }, [classes.horizontally, classes.vertical]);
const lastReleaseDate = useMemo(() => { const lastReleaseDate = useMemo(() => {
const format = "DD-MM-YYYY HH:mm:ss"; const format = "DD-MM-YYYY HH:mm:ss";
@ -78,16 +84,16 @@ const SystemVersionComponent = ({ data }) => {
return ( return (
<Paper variant="outlined"> <Paper variant="outlined">
<List sx={listClass}> <List className={listClass}>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar sx={styles.versionAvatar}> <Avatar className={classes.versionAvatar}>
<DeveloperBoardIcon /> <DeveloperBoardIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={ primary={
<span style={styles.value}> <span className={classes.value}>
{t("About.System.Version.Server", { {t("About.System.Version.Server", {
version: data.server.version version: data.server.version
})} })}
@ -100,13 +106,13 @@ const SystemVersionComponent = ({ data }) => {
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar sx={styles.versionAvatar}> <Avatar className={classes.versionAvatar}>
<SettingsInputSvideoIcon /> <SettingsInputSvideoIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={ primary={
<span style={styles.value}> <span className={classes.value}>
{t("About.System.Version.Api", { {t("About.System.Version.Api", {
version: data.api.version version: data.api.version
})} })}
@ -119,13 +125,13 @@ const SystemVersionComponent = ({ data }) => {
</ListItem> </ListItem>
<ListItem> <ListItem>
<ListItemAvatar> <ListItemAvatar>
<Avatar sx={styles.versionAvatar}> <Avatar className={classes.versionAvatar}>
<WebAssetIcon /> <WebAssetIcon />
</Avatar> </Avatar>
</ListItemAvatar> </ListItemAvatar>
<ListItemText <ListItemText
primary={ primary={
<span style={styles.value}> <span className={classes.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,19 +1,20 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import Timeline from "@mui/lab/Timeline"; import { makeStyles } from "@material-ui/core/styles";
import TimelineItem from "@mui/lab/TimelineItem"; import Timeline from "@material-ui/lab/Timeline";
import TimelineSeparator from "@mui/lab/TimelineSeparator"; import TimelineItem from "@material-ui/lab/TimelineItem";
import TimelineConnector from "@mui/lab/TimelineConnector"; import TimelineSeparator from "@material-ui/lab/TimelineSeparator";
import TimelineContent from "@mui/lab/TimelineContent"; import TimelineConnector from "@material-ui/lab/TimelineConnector";
import TimelineOppositeContent from "@mui/lab/TimelineOppositeContent"; import TimelineContent from "@material-ui/lab/TimelineContent";
import TimelineDot from "@mui/lab/TimelineDot"; import TimelineOppositeContent from "@material-ui/lab/TimelineOppositeContent";
import Paper from "@mui/material/Paper"; import TimelineDot from "@material-ui/lab/TimelineDot";
import Typography from "@mui/material/Typography"; import Paper from "@material-ui/core/Paper";
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,
Book, AmpStories,
Apps, Apps,
BugReport, BugReport,
DeviceHub, DeviceHub,
@ -28,11 +29,11 @@ import {
Star, Star,
Whatshot, Whatshot,
Widgets Widgets
} from "@mui/icons-material"; } from "@material-ui/icons";
const timelineIcons = [ const timelineIcons = [
Announcement, Announcement,
Book, AmpStories,
Apps, Apps,
BugReport, BugReport,
DeviceHub, DeviceHub,
@ -54,7 +55,14 @@ 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) => {
@ -65,7 +73,7 @@ const TimelineComponent = ({ releases }) => {
}); });
return ( return (
<Timeline position="alternate"> <Timeline align="alternate">
{_releases.map(release => ( {_releases.map(release => (
<TimelineItem key={release.version}> <TimelineItem key={release.version}>
<TimelineOppositeContent> <TimelineOppositeContent>
@ -76,18 +84,16 @@ const TimelineComponent = ({ releases }) => {
</Typography> </Typography>
</TimelineOppositeContent> </TimelineOppositeContent>
<TimelineSeparator> <TimelineSeparator>
<TimelineDot color={release.dot.color} variant={release.dot.variant}> <TimelineDot
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 <Paper elevation={3} className={classes.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,16 +1,17 @@
import React from "react"; import React from "react";
import { Alert, AlertTitle } from "@mui/material"; import { makeStyles } from "@material-ui/core/styles";
import { getStyles } from "../styles"; import { Alert, AlertTitle } from "@material-ui/lab";
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 style={styles.alert}> <div className={classes.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,18 +1,19 @@
import React from "react"; import React from "react";
import { Alert, AlertTitle } from "@mui/material"; import { makeStyles } from "@material-ui/core/styles";
import { getStyles } from "../styles"; import { Alert, AlertTitle } from "@material-ui/lab";
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 style={styles.alert}> <div className={classes.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 getStyles = theme => ({ const styles = theme => ({
alert: { alert: {
width: "100%", width: "100%",
"& > * + *": { "& > * + *": {
@ -7,4 +7,4 @@ const getStyles = theme => ({
} }
}); });
export { getStyles }; export default styles;

View File

@ -1,14 +1,23 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Card } from "@mui/material"; import { Card } from "@material-ui/core";
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 style={styles.appLogin}> <div className={classes.appLogin}>
<Card variant="outlined"> <Card variant="outlined">
<LoginComponent credentials={credentials} onChange={onChange} onLogin={onLogin} /> <LoginComponent
credentials={credentials}
onChange={onChange}
onLogin={onLogin}
/>
</Card> </Card>
</div> </div>
); );

View File

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

View File

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

View File

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

View File

@ -0,0 +1,114 @@
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

@ -1,106 +0,0 @@
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 "@mui/icons-material"; import { LastPage, RotateLeft, Launch, Stop } from "@material-ui/icons";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { routes, post } from "../../../utils/api"; import { routes, post } from "../../../utils/api";
@ -96,9 +96,7 @@ 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
@ -108,10 +106,20 @@ const MachineContainer = ({ machine, viewMode }) => {
return ( return (
<> <>
{viewMode === ViewModes.TABLE && ( {viewMode === ViewModes.TABLE && (
<MachineTableRow machine={machine} actions={actions} logs={logs} addLog={addLog} /> <MachineTableRow
machine={machine}
actions={actions}
logs={logs}
addLog={addLog}
/>
)} )}
{viewMode === ViewModes.ACCORDION && ( {viewMode === ViewModes.ACCORDION && (
<MachineAccordion machine={machine} actions={actions} logs={logs} addLog={addLog} /> <MachineAccordion
machine={machine}
actions={actions}
logs={logs}
addLog={addLog}
/>
)} )}
</> </>
); );

View File

@ -1,26 +1,34 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { TableCell, TableRow, IconButton, Collapse } from "@mui/material"; import { TableCell, TableRow, IconButton, Collapse } from "@material-ui/core";
import { KeyboardArrowDown, KeyboardArrowUp } from "@mui/icons-material"; import { KeyboardArrowDown, KeyboardArrowUp } from "@material-ui/icons";
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 <TableRow className={classes.root}>
sx={{
"& .MuiTableCell-root": {
borderBottom: "unset"
}
}}
>
<TableCell> <TableCell>
<IconButton aria-label="expand row" size="small" onClick={() => setOpen(!open)}> <IconButton
aria-label="expand row"
size="small"
onClick={() => setOpen(!open)}
>
{open ? <KeyboardArrowUp /> : <KeyboardArrowDown />} {open ? <KeyboardArrowUp /> : <KeyboardArrowDown />}
</IconButton> </IconButton>
</TableCell> </TableCell>
@ -40,7 +48,7 @@ const MachineTableRow = ({ machine, actions, logs, addLog }) => {
<MachineCollapsedContent <MachineCollapsedContent
description={machine.description} description={machine.description}
logs={logs} logs={logs}
style={{ paddingBottom: "0.5rem" }} style={{ paddingBottom: "10px" }}
/> />
</Collapse> </Collapse>
</TableCell> </TableCell>

View File

@ -1,5 +1,8 @@
import React, { useContext, useEffect, useCallback, useState } from "react"; import React, { useContext, useEffect, useCallback, useState } from "react";
import { NetworkStateContext, NetworkDispatchContext } from "../../network/state/contexts"; import {
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";
@ -32,9 +35,19 @@ const MachinesContainer = () => {
<> <>
<PageTitle <PageTitle
text={t("Menu.Machines")} text={t("Menu.Machines")}
toolBar={<ViewModeSelection callback={setViewMode} initialMode={ViewModes.TABLE} />} toolBar={
<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,7 +1,14 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material"; import {
import Paper from "@mui/material/Paper"; Table,
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";
@ -10,7 +17,11 @@ const MachinesList = ({ machines, viewMode }) => {
return ( return (
<> <>
{machines.map(machine => ( {machines.map(machine => (
<MachineContainer key={`machine-${machine.machineId}`} machine={machine} viewMode={viewMode} /> <MachineContainer
key={`machine-${machine.machineId}`}
machine={machine}
viewMode={viewMode}
/>
))} ))}
</> </>
); );

View File

@ -1,9 +1,10 @@
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 "@mui/icons-material/TableChart"; import TableChartIcon from "@material-ui/icons/TableChart";
import ViewListIcon from "@mui/icons-material/ViewList"; import ViewListIcon from "@material-ui/icons/ViewList";
import { ToggleButtonGroup, ToggleButton } from "@mui/material"; import ToggleButton from "@material-ui/lab/ToggleButton";
import { Tooltip } from "@mui/material"; import ToggleButtonGroup from "@material-ui/lab/ToggleButtonGroup";
import { Tooltip } from "@material-ui/core";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
export const ViewModes = { export const ViewModes = {
@ -43,8 +44,17 @@ const ViewModeSelection = ({ initialMode, callback }) => {
useEffect(() => callback && callback(state.mode), [callback, state.mode]); useEffect(() => callback && callback(state.mode), [callback, state.mode]);
return ( return (
<ToggleButtonGroup size="small" value={state.mode} exclusive onChange={handleViewModeSelection}> <ToggleButtonGroup
<ToggleButton value={ViewModes.TABLE} aria-label="table view mode" disabled={state.mode === ViewModes.TABLE}> size="small"
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 "@mui/material"; import { IconButton, Tooltip } from "@material-ui/core";
const ActionButton = React.forwardRef(props => { const ActionButton = React.forwardRef((props, _ref) => {
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,12 +12,14 @@ const ActionButton = React.forwardRef(props => {
}; };
return ( return (
<Tooltip id={`machine-item-${machine.machineId}-${action.code}-tooltip`} title={action.tooltip}> <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}
@ -42,6 +44,4 @@ 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 "@mui/material"; import { Menu } from "@material-ui/core";
import { MoreHoriz } from "@mui/icons-material"; import { MoreHoriz } from "@material-ui/icons";
import { useTranslation } from "react-i18next"; import { useTranslation } from "react-i18next";
import { usePermissions } from "../../../../hooks"; import { usePermissions } from "../../../../hooks";
@ -13,9 +13,15 @@ const ActionsGroup = ({ machine, actions, addLog }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { operateMachines: canOperateMachines } = usePermissions(); const { operateMachines: canOperateMachines } = usePermissions();
const mainActions = useMemo(() => actions.filter(a => a.main === true), [actions]); const mainActions = useMemo(
() => actions.filter(a => a.main === true),
[actions]
);
const secondaryActions = useMemo(() => actions.filter(a => a.main === false), [actions]); const secondaryActions = useMemo(
() => actions.filter(a => a.main === false),
[actions]
);
const handleMenuOpen = (_, event) => { const handleMenuOpen = (_, event) => {
setMenuAnchor(event.currentTarget); setMenuAnchor(event.currentTarget);
@ -27,33 +33,29 @@ const ActionsGroup = ({ machine, actions, addLog }) => {
return ( return (
<> <>
<div <WakeComponent
style={{ machine={machine}
display: "flex", addLog={addLog}
flexDirection: "row", disabled={!canOperateMachines}
justifyContent: "flex-end", />
alignItems: "center" {mainActions.map(action => (
}}
>
<WakeComponent machine={machine} addLog={addLog} disabled={!canOperateMachines} />
{mainActions.map(action => (
<ActionButton
key={`machine-item-${machine.machineId}-${action.code}`}
action={action}
machine={machine}
disabled={!canOperateMachines}
/>
))}
<ActionButton <ActionButton
action={{ key={`machine-item-${machine.machineId}-${action.code}`}
code: "more", action={action}
effect: handleMenuOpen,
icon: MoreHoriz,
tooltip: t("Machine.Actions.More")
}}
machine={machine} machine={machine}
disabled={!canOperateMachines}
/> />
</div> ))}
<ActionButton
action={{
code: "more",
effect: handleMenuOpen,
icon: MoreHoriz,
tooltip: t("Machine.Actions.More")
}}
machine={machine}
/>
<Menu <Menu
id="secondary-actions-menu" id="secondary-actions-menu"
anchorEl={menuAnchor} anchorEl={menuAnchor}

View File

@ -1,21 +1,27 @@
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 "@mui/material/Typography"; import Typography from "@material-ui/core/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 <div className={classes.panel}>
style={{
display: "flex"
}}
>
<Typography <Typography
variant="body2" variant="body2"
sx={{ className={classes.label}
marginRight: "4px"
}}
color="textSecondary" color="textSecondary"
> >
{"Description:"} {"Description:"}

View File

@ -1,13 +1,16 @@
import React, { useMemo } from "react"; import React, { useMemo } from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Box } from "@mui/material"; import { Box } from "@material-ui/core";
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(() => (logs.length > 0 ? maskElements(logs).join("\n") : "..."), [logs, maskElements]); const displayLogs = useMemo(
() => (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 "@mui/material"; import { IconButton, Tooltip } from "@material-ui/core";
import { PowerSettingsNew } from "@mui/icons-material"; import { PowerSettingsNew } from "@material-ui/icons";
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,8 +18,10 @@ const WakeComponent = ({ machine, addLog, disabled }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const { success, error } = useToast(); const { success, error } = useToast();
const pingInterval = process.env.REACT_APP_MACHINE_PING_INTERVAL || defaultPingInterval; const pingInterval =
const startingTime = process.env.REACT_APP_MACHINE_STARTING_TIME || defaultStartingTime; process.env.REACT_APP_MACHINE_PING_INTERVAL || defaultPingInterval;
const startingTime =
process.env.REACT_APP_MACHINE_STARTING_TIME || defaultStartingTime;
const getCurrentDateTime = useCallback(() => { const getCurrentDateTime = useCallback(() => {
const currentDateTime = Date.now(); const currentDateTime = Date.now();
@ -29,7 +31,10 @@ const WakeComponent = ({ machine, addLog, disabled }) => {
return result; return result;
}, [t]); }, [t]);
const log = useCallback(message => addLog(`[${getCurrentDateTime()}] ${message}`), [addLog, getCurrentDateTime]); const log = useCallback(
message => addLog(`[${getCurrentDateTime()}] ${message}`),
[addLog, getCurrentDateTime]
);
const wakeMachine = useCallback(async () => { const wakeMachine = useCallback(async () => {
await post( await post(
@ -43,7 +48,11 @@ const WakeComponent = ({ machine, addLog, disabled }) => {
success(result.status); success(result.status);
//retrigger //retrigger
log(`Periodic ping will be re-triggered in ${startingTime} ms [${msToMinAndSec(startingTime)}]`); log(
`Periodic ping will be re-triggered in ${startingTime} ms [${msToMinAndSec(
startingTime
)}]`
);
setTimeout(() => { setTimeout(() => {
setTrigger(prev => !prev); setTrigger(prev => !prev);
}, startingTime); }, startingTime);
@ -71,16 +80,12 @@ const WakeComponent = ({ machine, addLog, disabled }) => {
}, pingInterval); }, pingInterval);
} }
}, },
onError: () => { onError: () => {}
// to do: handle error
}
} }
); );
}, [machine, log, pingInterval, disabled]); }, [machine, log, pingInterval, disabled]);
useEffect(() => { useEffect(pingInLoop, [trigger, pingInLoop]);
pingInLoop();
}, [trigger, pingInLoop]);
const handleWakeClick = event => { const handleWakeClick = event => {
wakeMachine(); wakeMachine();
@ -95,7 +100,6 @@ 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 = (): JSX.Element | null => { const NetworkContainer = () => {
const { loading, viewMachines } = usePermissions(); const { loading, viewMachines } = usePermissions();
if (loading) return null; if (loading) return "";
if (!viewMachines) return <NotAllowed />; if (!viewMachines) return <NotAllowed />;
return ( return (

View File

@ -6,11 +6,16 @@ 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(() => reducerDispatchActions(dispatch), [dispatch]); const dispatchActions = useMemo(
() => reducerDispatchActions(dispatch),
[dispatch]
);
return ( return (
<NetworkStateContext.Provider value={state}> <NetworkStateContext.Provider value={state}>
<NetworkDispatchContext.Provider value={dispatchActions}>{children}</NetworkDispatchContext.Provider> <NetworkDispatchContext.Provider value={dispatchActions}>
{children}
</NetworkDispatchContext.Provider>
</NetworkStateContext.Provider> </NetworkStateContext.Provider>
); );
}; };

View File

@ -17,5 +17,6 @@ export function reducer(state, action) {
} }
export const dispatchActions = dispatch => ({ export const dispatchActions = dispatch => ({
onNetworkChange: (prop, value) => dispatch({ type: "onNetworkChange", payload: { prop, value } }) onNetworkChange: (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 "@mui/icons-material/BubbleChart"; import BubbleChartIcon from "@material-ui/icons/BubbleChart";
import BrushIcon from "@mui/icons-material/Brush"; import BrushIcon from "@material-ui/icons/Brush";
import NotificationsIcon from "@mui/icons-material/Notifications"; import NotificationsIcon from "@material-ui/icons/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,11 +34,19 @@ const SettingsContainer = () => {
const [tab, setTab] = useState(NavigationTabs.SYSTEM); const [tab, setTab] = useState(NavigationTabs.SYSTEM);
const { t } = useTranslation(); const { t } = useTranslation();
const navigationTabs = useMemo(() => tabs.map(z => ({ ...z, tooltip: t(z.code) })), [t]); const navigationTabs = useMemo(
() => tabs.map(z => ({ ...z, tooltip: t(z.code) })),
[t]
);
return ( return (
<> <>
<PageTitle text={t(tab)} navigation={<NavigationButtons tabs={navigationTabs} onTabChange={setTab} />} /> <PageTitle
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,19 +1,21 @@
import React from "react"; import React from "react";
import { useApplicationTheme } from "../../../providers/ThemeProvider"; import { useApplicationTheme } from "../../../providers/ThemeProvider";
import { Grid, Paper, FormControlLabel, Switch, Box } from "@mui/material"; import { Grid, Paper, FormControlLabel, Switch } from "@material-ui/core";
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 styles = { const useStyles = makeStyles(theme => ({
language: { language: {
paddingLeft: 1 paddingLeft: theme.spacing(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;
@ -27,7 +29,14 @@ 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={<Switch checked={isDark} onChange={handleChange} color="secondary" name="dark-mode-switch" />} control={
<Switch
checked={isDark}
onChange={handleChange}
color="secondary"
name="dark-mode-switch"
/>
}
label="Dark mode:" label="Dark mode:"
labelPlacement="start" labelPlacement="start"
/> />
@ -36,9 +45,9 @@ const AppearanceComponent = () => {
<FormControlLabel <FormControlLabel
value="start" value="start"
control={ control={
<Box sx={styles.language}> <div className={classes.language}>
<LanguageContainer /> <LanguageContainer />
</Box> </div>
} }
label="Language:" label="Language:"
labelPlacement="start" labelPlacement="start"

View File

@ -0,0 +1,75 @@
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

@ -1,54 +0,0 @@
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,15 +2,25 @@ 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 [flagCode, setFlagCode] = useState("RO"); const [flag, setFlag] = useState({
name: "RO",
alt: "-"
});
useEffect(() => { useEffect(() => {
if (!i18n.language) return; if (!i18n.language) return;
setFlagCode(i18n.language === "en" ? "GB" : i18n.language.toUpperCase()); setFlag({
name: i18n.language === "en" ? "GB" : i18n.language.toUpperCase(),
alt: i18n.language
});
}, [i18n.language]); }, [i18n.language]);
const handleMenuOpen = event => { const handleMenuOpen = event => {
@ -35,7 +45,8 @@ const LanguageContainer = () => {
onMenuOpen={handleMenuOpen} onMenuOpen={handleMenuOpen}
onLanguageChange={handleLanguageChange} onLanguageChange={handleLanguageChange}
onClose={handleClose} onClose={handleClose}
flagCode={flagCode} flag={flag}
flagsPath={flagsPath}
/> />
); );
}; };

View File

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

View File

@ -1,22 +1,37 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Paper, Button, Box } from "@mui/material"; import { Paper, Button } from "@material-ui/core";
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")} />
<Box sx={{ margin: 1 }}> <div className={classes.content}>
<Button variant="outlined" color="secondary" disabled={!sysAdmin} onClick={onResetCache}> <Button
variant="outlined"
color="secondary"
disabled={!sysAdmin}
onClick={onResetCache}
>
{t("Settings.Cache.Reset")} {t("Settings.Cache.Reset")}
</Button> </Button>
</Box> </div>
</Paper> </Paper>
); );
}; };

View File

@ -1,6 +1,6 @@
import React, { useState, useMemo } from "react"; import React, { useState, useMemo } from "react";
import CategoryIcon from "@mui/icons-material/Category"; import CategoryIcon from "@material-ui/icons/Category";
import GrainIcon from "@mui/icons-material/Grain"; import GrainIcon from "@material-ui/icons/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,11 +27,19 @@ 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(() => tabs.map(z => ({ ...z, tooltip: t(z.code) })), [t]); const navigationTabs = useMemo(
() => tabs.map(z => ({ ...z, tooltip: t(z.code) })),
[t]
);
return ( return (
<> <>
<PageTitle text={t(tab)} navigation={<NavigationButtons tabs={navigationTabs} onTabChange={setTab} />} /> <PageTitle
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,15 +2,19 @@ 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 { useTheme } from "@mui/material/styles"; import { makeStyles } from "@material-ui/core/styles";
import { getStyles } from "../styles"; import styles from "../styles";
const useStyles = makeStyles(styles);
const UserProfileCardContent = ({ userData }) => { const UserProfileCardContent = ({ userData }) => {
const { profilePictureUrl } = userData; const { profilePictureUrl } = userData;
const theme = useTheme(); const classes = useStyles();
const styles = getStyles(theme);
const userName = useMemo(() => `${userData.firstName} ${userData.lastName}`, [userData.firstName, userData.lastName]); const userName = useMemo(
() => `${userData.firstName} ${userData.lastName}`,
[userData.firstName, userData.lastName]
);
const _contactOptions = useMemo( const _contactOptions = useMemo(
() => () =>
profilePictureUrl profilePictureUrl
@ -27,7 +31,7 @@ const UserProfileCardContent = ({ userData }) => {
); );
return ( return (
<div style={styles.panel}> <div className={classes.panel}>
<UserProfilePicture pictureUrl={userData.profilePictureUrl} /> <UserProfilePicture pictureUrl={userData.profilePictureUrl} />
<ContactOptions contactOptions={_contactOptions} userName={userName} /> <ContactOptions contactOptions={_contactOptions} userName={userName} />
</div> </div>

View File

@ -1,17 +1,18 @@
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 "@mui/material"; import { Card, CardHeader, CardContent } from "@material-ui/core";
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 { getStyles } from "../styles"; import styles from "../styles";
import { useTheme } from "@mui/material/styles"; import { makeStyles } from "@material-ui/core/styles";
const useStyles = makeStyles(styles);
const UserProfileComponent = ({ userData }) => { const UserProfileComponent = ({ userData }) => {
const { t } = useTranslation(); const { t } = useTranslation();
const theme = useTheme(); const classes = useStyles();
const styles = getStyles(theme);
const userLoginDate = useMemo( const userLoginDate = useMemo(
() => () =>
@ -28,7 +29,9 @@ const UserProfileComponent = ({ userData }) => {
return ( return (
<> <>
<PageTitle text={t("User.Profile.Hello", { userName: userData.firstName })} /> <PageTitle
text={t("User.Profile.Hello", { userName: userData.firstName })}
/>
<Card> <Card>
<CardHeader title={userData.userName} subheader={userDescription} /> <CardHeader title={userData.userName} subheader={userDescription} />
<CardContent> <CardContent>
@ -36,8 +39,11 @@ const UserProfileComponent = ({ userData }) => {
</CardContent> </CardContent>
</Card> </Card>
<div style={styles.section}> <div className={classes.section}>
<SecurityComponent userGroups={userData.userGroups} userRoles={userData.userRoles} /> <SecurityComponent
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: React.FC = () => { const UserProfileContainer = () => {
const { userInfo } = useTuitioUserInfo(); const { userInfo } = useTuitioUserInfo();
return <>{userInfo && <UserProfileComponent userData={userInfo} />}</>; return <>{userInfo && <UserProfileComponent userData={userInfo} />}</>;
}; };

View File

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

View File

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

View File

@ -1,37 +1,43 @@
import React from "react"; import React from "react";
import PropTypes from "prop-types"; import PropTypes from "prop-types";
import { Paper, Grid, Chip, Typography } from "@mui/material"; import { Paper, Grid, Chip, Typography } from "@material-ui/core";
import { getStyles } from "../styles"; import styles from "../styles";
import { useTheme } from "@mui/material/styles"; import { makeStyles } from "@material-ui/core/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 theme = useTheme(); const classes = useStyles();
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 style={styles.paper}> <div className={classes.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} style={styles.chip} label={g.name} /> <Chip key={g.code} className={classes.chip} label={g.name} />
))} ))}
</div> </div>
</div> </div>
</Grid> </Grid>
<Grid item xs={12} md={6}> <Grid item xs={12} md={6}>
<div style={styles.paper}> <div className={classes.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 key={r.code} style={styles.chip} color="primary" label={r.name} /> <Chip
key={r.code}
className={classes.chip}
color="primary"
label={r.name}
/>
))} ))}
</div> </div>
</div> </div>

View File

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

View File

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

View File

@ -23,7 +23,8 @@ const reducer = (state = initialState, action) => {
}; };
const dispatchActions = dispatch => ({ const dispatchActions = dispatch => ({
onSensitiveInfoEnabled: enabled => dispatch({ type: "onSensitiveInfoEnabled", payload: { enabled } }) onSensitiveInfoEnabled: 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 "@mui/material/styles"; import { ThemeProvider as MuiThemeProvider } from "@material-ui/styles";
import { localStorage } from "@flare/js-utils"; import { localStorage } from "@flare/js-utils";
import { getThemes } from "../themes"; import { getThemes } from "../themes";
@ -13,10 +13,13 @@ 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("(prefers-color-scheme: dark)").matches; const prefersDarkMode = window.matchMedia(
"(prefers-color-scheme: dark)"
).matches;
const initialState = { const initialState = {
scheme: colorScheme ?? (prefersDarkMode ? COLOR_SCHEME.DARK : COLOR_SCHEME.LIGHT) scheme:
colorScheme ?? (prefersDarkMode ? COLOR_SCHEME.DARK : COLOR_SCHEME.LIGHT)
}; };
const reducer = (state = initialState, action) => { const reducer = (state = initialState, action) => {
@ -45,7 +48,8 @@ const useApplicationTheme = () => {
const { onColorSchemeChanged } = actions; const { onColorSchemeChanged } = actions;
const { scheme } = state; const { scheme } = state;
const onDarkModeChanged = active => onColorSchemeChanged(active ? COLOR_SCHEME.DARK : COLOR_SCHEME.LIGHT); const onDarkModeChanged = active =>
onColorSchemeChanged(active ? COLOR_SCHEME.DARK : COLOR_SCHEME.LIGHT);
return { isDark: scheme === COLOR_SCHEME.DARK, onDarkModeChanged }; return { isDark: scheme === COLOR_SCHEME.DARK, onDarkModeChanged };
}; };
@ -53,7 +57,10 @@ 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(() => getThemes(state.scheme === COLOR_SCHEME.DARK), [state.scheme]); const themes = useMemo(
() => getThemes(state.scheme === COLOR_SCHEME.DARK),
[state.scheme]
);
return ( return (
<ApplicationThemeContext.Provider <ApplicationThemeContext.Provider

View File

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

View File

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

View File

@ -0,0 +1,35 @@
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

@ -1,69 +0,0 @@
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

@ -0,0 +1,39 @@
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

@ -1,43 +0,0 @@
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

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

View File

@ -1,8 +0,0 @@
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: () => null, onCompleted: () => {},
onError: handleError onError: handleError
}; };

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