Compare commits

..

55 Commits

Author SHA1 Message Date
Tudor Stanciu e80d246fc0 Merged PR 83: Frontend Upgrades - The frontend has been upgraded to leverage TypeScript, React 18, and Material UI 5, enhancing code quality, UI consistency, and leveraging the latest features of these technologies.
• Frontend Upgrades - The frontend has been upgraded to leverage TypeScript, React 18, and Material UI 5, enhancing code quality, UI consistency, and leveraging the latest features of these technologies.
      • Build process overhaul - I've switched from react-scripts to react-app-rewired. This gives me more control to tweak the webpack configuration as I need. Plus, it's now set up to handle CommonJS (CJS) modules.
      • TypeScript refactoring - Several components have been rewritten in TypeScript, improving type safety and predictability in our codebase.
2024-07-13 18:49:29 +00:00
Tudor Stanciu 6d451162b1 theme enhancements 2024-07-13 21:06:42 +03:00
Tudor Stanciu e3af87cd88 refactor: Update theme configuration and remove unused code 2024-07-13 20:32:04 +03:00
Tudor Stanciu 4235323c4a refactor: Improve padding in ActionButton and WakeComponent 2024-07-13 19:54:22 +03:00
Tudor Stanciu b695cd6014 refactor: Update ActionsGroup component to improve readability and maintainability 2024-07-13 18:38:26 +03:00
Tudor Stanciu e2f724dd97 . 2024-06-03 11:59:38 +03:00
Tudor Stanciu b34f8cf888 package lock 2024-06-03 11:37:49 +03:00
Tudor Stanciu c6a3290891 Theme fix 2024-04-01 01:14:21 +03:00
Tudor Stanciu 6798057b2d Fix media query in profile styles 2024-04-01 01:01:57 +03:00
Tudor Stanciu 7685a7595d LanguageComponent fix 2024-04-01 00:58:11 +03:00
Tudor Stanciu 038b97ff84 Remove outdated notes on REACT v4 and theming 2024-04-01 00:33:18 +03:00
Tudor Stanciu 76b2bf64f3 Update TimelineComponent and CacheSettingsComponent 2024-04-01 00:30:18 +03:00
Tudor Stanciu 5f2f701611 Bump version to 1.3.0 and updated release notes 2024-03-31 23:38:21 +03:00
Tudor Stanciu 8f4c0bb2fa Fix useState hook placement in SystemVersionComponent.js 2024-03-31 23:24:05 +03:00
Tudor Stanciu 56d80a9dd1 Update MachineTableRow component styles 2024-03-31 23:19:24 +03:00
Tudor Stanciu f25f0d5438 MachineAccordion component upgrade 2024-03-31 22:57:58 +03:00
Tudor Stanciu 367db653f1 Refactor useEffect in WakeComponent.js 2024-03-31 22:07:02 +03:00
Tudor Stanciu 2265676c81 Update fetchTuitioData import in axios.js 2024-03-31 21:18:21 +03:00
Tudor Stanciu 0081c2e482 Replace react-scripts with react-app-rewired 2024-03-31 21:00:54 +03:00
Tudor Stanciu 9b30ef90dd Update dependencies in package.json 2024-03-31 02:23:46 +02:00
Tudor Stanciu 455304b8f9 Update dependencies and remove UserProfileContainer component 2024-03-30 14:18:19 +02:00
Tudor Stanciu 86d243bffd Refactor AppLayout component 2024-03-30 14:12:15 +02:00
Tudor Stanciu add5ae2734 Remove unused styles and dependencies from ProfileButton.js 2024-03-30 13:57:07 +02:00
Tudor Stanciu b8019e62ef Remove unused icons from SideBar component 2024-03-30 13:43:39 +02:00
Tudor Stanciu 0e5b69235c Refactor SideBar and TopBar components 2024-03-30 13:43:11 +02:00
Tudor Stanciu c981660442 Migrate Sidebar to typescript and MUI 5 2024-03-30 12:55:22 +02:00
Tudor Stanciu 14f16677c9 Migrate TopBar component to typescript and MUI 5 2024-03-30 10:33:40 +02:00
Tudor Stanciu ed94bb9510 Refactor layout component styles 2024-03-25 03:19:58 +02:00
Tudor Stanciu cbe251d776 Refactor ActionButton component 2024-03-25 03:13:28 +02:00
Tudor Stanciu b3bdb38997 Refactor MachineCollapsedContent.js to use inline styles 2024-03-25 03:12:17 +02:00
Tudor Stanciu c3cb995e86 Remove unused makeStyles import and inline paper styling in TimelineComponent.js 2024-03-25 03:10:32 +02:00
Tudor Stanciu a3e7578262 Refactor user profile components and update styles 2024-03-25 03:09:25 +02:00
Tudor Stanciu 565410da32 Refactor MachineAccordion component 2024-03-25 03:03:26 +02:00
Tudor Stanciu 0e0d59bb8e Refactor MachineAccordion component 2024-03-25 03:01:45 +02:00
Tudor Stanciu c46ffc2e39 Refactor MachineTableRow component 2024-03-25 02:58:52 +02:00
Tudor Stanciu 1a53110db1 Refactor SystemVersionComponent styles 2024-03-25 02:57:40 +02:00
Tudor Stanciu f29f159cc3 Refactor CacheSettingsComponent to use Box component 2024-03-25 02:53:42 +02:00
Tudor Stanciu 79bd644f95 Refactor login component styles 2024-03-25 02:51:56 +02:00
Tudor Stanciu 968fd2e785 Refactor AboutSystemComponent styles 2024-03-25 02:49:18 +02:00
Tudor Stanciu a9b528d1f5 Refactor AboutSystemContainer component 2024-03-25 02:47:50 +02:00
Tudor Stanciu 4ab78e51cd Refactor announcement components and styles 2024-03-25 02:46:14 +02:00
Tudor Stanciu cbca462893 Update AppearanceComponent.js with MUI Box component 2024-03-25 02:41:20 +02:00
Tudor Stanciu ea5e15bf45 Refactor PasswordField component styling 2024-03-25 02:39:35 +02:00
Tudor Stanciu 89514653ce Refactor PaperTitle component to use Box component from Material-UI 2024-03-25 02:38:49 +02:00
Tudor Stanciu 799d95ab23 Refactor common components 2024-03-25 02:37:54 +02:00
Tudor Stanciu 33d61de6d3 Refactor NotAllowed component to use Box component from Material-UI 2024-03-25 02:36:11 +02:00
Tudor Stanciu 8174e63a6e Refactor DataLabel component styles 2024-03-25 02:33:37 +02:00
Tudor Stanciu 77c0264963 Fix import statements and add missing code comments 2024-03-25 02:28:47 +02:00
Tudor Stanciu e085e36934 Update Material-UI imports to MUI imports 2024-03-25 02:10:14 +02:00
Tudor Stanciu f8276f756b Update Material-UI icons to MUI icons 2024-03-25 02:08:05 +02:00
Tudor Stanciu d9501339c6 Update Material-UI imports to MUI imports 2024-03-25 02:06:10 +02:00
Tudor Stanciu 95d92db790 material ui upgrade 2024-03-25 01:35:56 +02:00
Tudor Stanciu a5dba2e346 Changes for react-router-dom upgrade 2024-03-25 01:16:11 +02:00
Tudor Stanciu ae71b142fa Remove unnecessary configuration files and update dependencies 2024-03-25 00:56:50 +02:00
Tudor Stanciu c434bf8729 package-lock 2024-03-25 00:19:43 +02:00
104 changed files with 31721 additions and 11923 deletions

View File

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

View File

@ -201,4 +201,15 @@
• Updated menu component to permanently display the selected item.
</Content>
</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>

34
frontend/.eslintrc Normal file
View File

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

View File

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

8
frontend/.prettierrc Normal file
View File

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

View File

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

View File

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

View File

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

40760
frontend/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

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

View File

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

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 937 B

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,7 @@
import React, { useState } from "react";
import PropTypes from "prop-types";
import ToggleButton from "@material-ui/lab/ToggleButton";
import ToggleButtonGroup from "@material-ui/lab/ToggleButtonGroup";
import { Tooltip } from "@material-ui/core";
import { ToggleButtonGroup, ToggleButton } from "@mui/material";
import { Tooltip } from "@mui/material";
const NavigationButtons = ({ tabs, onTabChange }) => {
const [selected, setSelected] = useState(tabs[0].code);
@ -13,19 +12,9 @@ const NavigationButtons = ({ tabs, onTabChange }) => {
};
return (
<ToggleButtonGroup
size="small"
value={selected}
exclusive
onChange={handleTabSelection}
>
<ToggleButtonGroup size="small" value={selected} exclusive onChange={handleTabSelection}>
{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}>
<tab.icon color="primary" />
</Tooltip>
@ -36,9 +25,7 @@ const NavigationButtons = ({ tabs, onTabChange }) => {
};
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
};

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,8 @@
import React, { useState, useMemo } from "react";
import PageTitle from "../../components/common/PageTitle";
import BubbleChartIcon from "@material-ui/icons/BubbleChart";
import NotesIcon from "@material-ui/icons/Notes";
import TimelineIcon from "@material-ui/icons/Timeline";
import BubbleChartIcon from "@mui/icons-material/BubbleChart";
import NotesIcon from "@mui/icons-material/Notes";
import TimelineIcon from "@mui/icons-material/Timeline";
import { useTranslation } from "react-i18next";
import AboutSystemContainer from "./system/AboutSystemContainer";
import ReleaseNotesContainer from "./releaseNotes/ReleaseNotesContainer";
@ -33,24 +33,14 @@ const AboutContainer = () => {
const [tab, setTab] = useState(NavigationTabs.SYSTEM);
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 (
<>
<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.RELEASE_NOTES && <ReleaseNotesContainer />}
{tab === NavigationTabs.TIMELINE && (
<ReleaseNotesContainer view="timeline" />
)}
{tab === NavigationTabs.TIMELINE && <ReleaseNotesContainer view="timeline" />}
</>
);
};

View File

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

View File

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

View File

@ -4,8 +4,7 @@ import ReleaseNotesList from "./ReleaseNotesList";
import TimelineComponent from "../timeline/TimelineComponent";
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 [state, setState] = useState({ data: [], loaded: false });

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,8 +1,5 @@
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 PageTitle from "../../../components/common/PageTitle";
import { useTranslation } from "react-i18next";
@ -35,19 +32,9 @@ const MachinesContainer = () => {
<>
<PageTitle
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,14 +1,7 @@
import React from "react";
import PropTypes from "prop-types";
import {
Table,
TableBody,
TableCell,
TableContainer,
TableHead,
TableRow
} from "@material-ui/core";
import Paper from "@material-ui/core/Paper";
import { Table, TableBody, TableCell, TableContainer, TableHead, TableRow } from "@mui/material";
import Paper from "@mui/material/Paper";
import MachineContainer from "./MachineContainer";
import { useTranslation } from "react-i18next";
import { ViewModes } from "./ViewModeSelection";
@ -17,11 +10,7 @@ const MachinesList = ({ machines, viewMode }) => {
return (
<>
{machines.map(machine => (
<MachineContainer
key={`machine-${machine.machineId}`}
machine={machine}
viewMode={viewMode}
/>
<MachineContainer key={`machine-${machine.machineId}`} machine={machine} viewMode={viewMode} />
))}
</>
);

View File

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

View File

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

View File

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

View File

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

View File

@ -1,16 +1,13 @@
import React, { useMemo } from "react";
import PropTypes from "prop-types";
import { Box } from "@material-ui/core";
import { Box } from "@mui/material";
import { useSensitiveInfo } from "../../../../hooks";
import { LazyLog, ScrollFollow } from "react-lazylog";
const MachineLog = ({ logs }) => {
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 (
<Box>

View File

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

View File

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

View File

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

View File

@ -17,6 +17,5 @@ export function reducer(state, action) {
}
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 BubbleChartIcon from "@material-ui/icons/BubbleChart";
import BrushIcon from "@material-ui/icons/Brush";
import NotificationsIcon from "@material-ui/icons/Notifications";
import BubbleChartIcon from "@mui/icons-material/BubbleChart";
import BrushIcon from "@mui/icons-material/Brush";
import NotificationsIcon from "@mui/icons-material/Notifications";
import { useTranslation } from "react-i18next";
import PageTitle from "../../components/common/PageTitle";
import NavigationButtons from "../../components/common/NavigationButtons";
@ -34,19 +34,11 @@ const SettingsContainer = () => {
const [tab, setTab] = useState(NavigationTabs.SYSTEM);
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 (
<>
<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.APPEARANCE && <AppearanceContainer />}
{tab === NavigationTabs.NOTIFICATIONS && <NotificationsContainer />}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,6 @@
import React, { useState, useMemo } from "react";
import CategoryIcon from "@material-ui/icons/Category";
import GrainIcon from "@material-ui/icons/Grain";
import CategoryIcon from "@mui/icons-material/Category";
import GrainIcon from "@mui/icons-material/Grain";
import { useTranslation } from "react-i18next";
import PageTitle from "../../components/common/PageTitle";
import NavigationButtons from "../../components/common/NavigationButtons";
@ -27,19 +27,11 @@ const SystemContainer = () => {
const [tab, setTab] = useState(NavigationTabs.MAIN_SERVICES);
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 (
<>
<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.AGENTS && <AgentsContainer />}
</>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

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