diff --git a/csharp/App/Backend/Controller.cs b/csharp/App/Backend/Controller.cs index b9b5d3878..4763556a2 100644 --- a/csharp/App/Backend/Controller.cs +++ b/csharp/App/Backend/Controller.cs @@ -456,9 +456,9 @@ public class Controller : ControllerBase } [HttpPost(nameof(ResetPasswordRequest))] - public async Task>> ResetPasswordRequest(String email) + public async Task>> ResetPasswordRequest(String username) { - var user = Db.GetUserByEmail(email); + var user = Db.GetUserByEmail(username); if (user is null) return Unauthorized(); @@ -482,7 +482,7 @@ public class Controller : ControllerBase Db.DeleteUserPassword(user); - return Redirect($"https://monitor.innov.energy/?username={user.Email}&reset=true"); + return Redirect($"https://monnitor.innov.energy/?username={user.Email}&reset=true"); // TODO: move to settings file } } diff --git a/csharp/App/Backend/DataTypes/Methods/User.cs b/csharp/App/Backend/DataTypes/Methods/User.cs index b85020cfe..2467c7d3b 100644 --- a/csharp/App/Backend/DataTypes/Methods/User.cs +++ b/csharp/App/Backend/DataTypes/Methods/User.cs @@ -1,4 +1,5 @@ using System.Security.Cryptography; +using System.Web; using InnovEnergy.App.Backend.Database; using InnovEnergy.Lib.Mailer; using InnovEnergy.Lib.Utils; @@ -218,11 +219,12 @@ public static class UserMethods public static Task SendPasswordResetEmail(this User user, String token) { const String subject = "Reset the password of your InnovEnergy-Account"; - const String resetLink = "https://monitor.innov.energy/api/ResetPassword"; // TODO: move to settings file + const String resetLink = "https://monnitor.innov.energy/api/ResetPassword"; // TODO: move to settings file + var encodedToken = HttpUtility.UrlEncode(token); var body = $"Dear {user.Name}\n" + $"To reset your password " + - $"please open this link:{resetLink}?token={token}"; + $"please open this link:{resetLink}?token={encodedToken}"; return user.SendEmail(subject, body); } diff --git a/csharp/App/Backend/Properties/launchSettings.json b/csharp/App/Backend/Properties/launchSettings.json index 3bef649bb..760aaabab 100644 --- a/csharp/App/Backend/Properties/launchSettings.json +++ b/csharp/App/Backend/Properties/launchSettings.json @@ -7,7 +7,7 @@ "dotnetRunMessages": true, "launchUrl": "swagger", - "applicationUrl": "https://localhost:7087", + "applicationUrl": "http://localhost:7087", "environmentVariables": { "ASPNETCORE_ENVIRONMENT": "Development", "HOME":"~/backend" diff --git a/typescript/frontend-marios2/package-lock.json b/typescript/frontend-marios2/package-lock.json index e573f61eb..fc39e5adb 100644 --- a/typescript/frontend-marios2/package-lock.json +++ b/typescript/frontend-marios2/package-lock.json @@ -21,7 +21,7 @@ "chart.js": "^4.4.0", "clsx": "1.1.1", "cytoscape": "^3.26.0", - "date-fns": "2.28.0", + "date-fns": "^2.28.0", "history": "5.3.0", "linq-to-typescript": "^11.0.0", "nprogress": "0.2.0", @@ -38,9 +38,11 @@ "react-icons": "^4.11.0", "react-icons-converter": "^1.1.4", "react-intl": "^6.4.4", + "react-redux": "^8.1.3", "react-router": "6.3.0", "react-router-dom": "6.3.0", "react-scripts": "5.0.1", + "redux": "^4.2.1", "rxjs": "^7.8.1", "simplytyped": "^3.3.0", "stylis": "4.1.1", @@ -4662,6 +4664,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "node_modules/@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "node_modules/@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -15155,6 +15162,49 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "node_modules/react-redux": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", + "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==", + "dependencies": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "peerDependencies": { + "@types/react": "^16.8 || ^17.0 || ^18.0", + "@types/react-dom": "^16.8 || ^17.0 || ^18.0", + "react": "^16.8 || ^17.0 || ^18.0", + "react-dom": "^16.8 || ^17.0 || ^18.0", + "react-native": ">=0.59", + "redux": "^4 || ^5.0.0-beta.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "@types/react-dom": { + "optional": true + }, + "react-dom": { + "optional": true + }, + "react-native": { + "optional": true + }, + "redux": { + "optional": true + } + } + }, + "node_modules/react-redux/node_modules/react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + }, "node_modules/react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -15334,6 +15384,14 @@ "node": "*" } }, + "node_modules/redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "dependencies": { + "@babel/runtime": "^7.9.2" + } + }, "node_modules/regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -17185,6 +17243,14 @@ "punycode": "^2.1.0" } }, + "node_modules/use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "peerDependencies": { + "react": "^16.8.0 || ^17.0.0 || ^18.0.0" + } + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -21400,6 +21466,11 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz", "integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg==" }, + "@types/use-sync-external-store": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz", + "integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA==" + }, "@types/ws": { "version": "8.5.3", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz", @@ -28886,6 +28957,26 @@ "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", "integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==" }, + "react-redux": { + "version": "8.1.3", + "resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz", + "integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==", + "requires": { + "@babel/runtime": "^7.12.1", + "@types/hoist-non-react-statics": "^3.3.1", + "@types/use-sync-external-store": "^0.0.3", + "hoist-non-react-statics": "^3.3.2", + "react-is": "^18.0.0", + "use-sync-external-store": "^1.0.0" + }, + "dependencies": { + "react-is": { + "version": "18.2.0", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz", + "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==" + } + } + }, "react-refresh": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz", @@ -29020,6 +29111,14 @@ } } }, + "redux": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz", + "integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==", + "requires": { + "@babel/runtime": "^7.9.2" + } + }, "regenerate": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", @@ -30399,6 +30498,12 @@ "punycode": "^2.1.0" } }, + "use-sync-external-store": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", + "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", + "requires": {} + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", diff --git a/typescript/frontend-marios2/package.json b/typescript/frontend-marios2/package.json index d0c1bf1ae..23ef0f8a9 100644 --- a/typescript/frontend-marios2/package.json +++ b/typescript/frontend-marios2/package.json @@ -17,7 +17,7 @@ "chart.js": "^4.4.0", "clsx": "1.1.1", "cytoscape": "^3.26.0", - "date-fns": "2.28.0", + "date-fns": "^2.28.0", "history": "5.3.0", "linq-to-typescript": "^11.0.0", "nprogress": "0.2.0", @@ -34,9 +34,11 @@ "react-icons": "^4.11.0", "react-icons-converter": "^1.1.4", "react-intl": "^6.4.4", + "react-redux": "^8.1.3", "react-router": "6.3.0", "react-router-dom": "6.3.0", "react-scripts": "5.0.1", + "redux": "^4.2.1", "rxjs": "^7.8.1", "simplytyped": "^3.3.0", "stylis": "4.1.1", diff --git a/typescript/frontend-marios2/src/App.tsx b/typescript/frontend-marios2/src/App.tsx index 605cafc58..817713db7 100644 --- a/typescript/frontend-marios2/src/App.tsx +++ b/typescript/frontend-marios2/src/App.tsx @@ -1,4 +1,4 @@ -import { Navigate, Route, Routes } from 'react-router-dom'; +import { Navigate, Route, Routes, useNavigate } from 'react-router-dom'; import { CssBaseline } from '@mui/material'; import ThemeProvider from './theme/ThemeProvider'; import React, { lazy, Suspense, useContext, useState } from 'react'; @@ -9,15 +9,16 @@ import en from './lang/en.json'; import de from './lang/de.json'; import fr from './lang/fr.json'; import SuspenseLoader from './components/SuspenseLoader'; -import { RouteObject } from 'react-router'; -import BaseLayout from './layouts/BaseLayout'; import SidebarLayout from './layouts/SidebarLayout'; import { TokenContext } from './contexts/tokenContext'; import ResetPassword from './components/ResetPassword'; -import ForgotPassword from './components/ForgotPassword'; import InstallationTabs from './content/dashboards/Installations/index'; import routes from 'src/Resources/routes.json'; import './App.css'; +import ForgotPassword from './components/ForgotPassword'; +import { axiosConfigWithoutToken } from './Resources/axiosConfig'; +import UsersContextProvider from './contexts/UsersContextProvider'; +import InstallationsContextProvider from './contexts/InstallationsContextProvider'; function App() { const context = useContext(UserContext); @@ -25,6 +26,9 @@ function App() { const tokencontext = useContext(TokenContext); const { token, setNewToken, removeToken } = tokencontext; const [forgotPassword, setForgotPassword] = useState(false); + const navigate = useNavigate(); + const searchParams = new URLSearchParams(location.search); + const username = searchParams.get('username'); const [language, setLanguage] = useState('en'); const getTranslations = () => { @@ -62,9 +66,26 @@ function App() { lazy(() => import('src/components/ResetPassword')) ); + const SetNewPassword = Loader( + lazy(() => import('src/components/SetNewPassword')) + ); + const Login = Loader(lazy(() => import('src/components/login'))); const Users = Loader(lazy(() => import('src/content/dashboards/Users'))); + const loginToResetPassword = () => { + axiosConfigWithoutToken + .post('/Login', null, { params: { username, password: '' } }) + .then((response) => { + if (response.data && response.data.token) { + setNewToken(response.data.token); + setUser(response.data.user); + navigate(routes.installations); + } + }) + .catch((error) => {}); + }; + // Status const Status404 = Loader( lazy(() => import('src/content/pages/Status/Status404')) @@ -79,61 +100,25 @@ function App() { lazy(() => import('src/content/pages/Status/Maintenance')) ); - const routesArray: RouteObject[] = [ - { - path: '', - element: , - children: [ - { - path: '/', - element: - }, - { - path: 'status', - children: [ - { - path: '', - element: - }, - { - path: '404', - element: - }, - { - path: '500', - element: - }, - { - path: 'maintenance', - element: - }, - { - path: 'coming-soon', - element: - } - ] - }, - { - path: '*', - element: - } - ] - } - ]; - if (forgotPassword) { - return ( - - - - - ); + if (username) { + loginToResetPassword(); } if (!token) { return ( - + + } + > + }> + } + > + ); } @@ -142,7 +127,7 @@ function App() { return ( - + ); } @@ -156,18 +141,10 @@ function App() { > - {routesArray.map((route, index) => ( - - {route.children && - route.children.map((childRoute, childIndex) => ( - - ))} - - ))} + } + > } + element={ + + + + + + } /> + } /> + }> - }> diff --git a/typescript/frontend-marios2/src/Resources/formatPower.tsx b/typescript/frontend-marios2/src/Resources/formatPower.tsx index 6ac38ca6c..a54dcc960 100644 --- a/typescript/frontend-marios2/src/Resources/formatPower.tsx +++ b/typescript/frontend-marios2/src/Resources/formatPower.tsx @@ -37,7 +37,7 @@ export function findPower(value) { value = Math.abs(value); // Calculate the power of 10 that's greater or equal to the absolute value - let exponent = Math.floor(Math.log10(value)); + const exponent = Math.floor(Math.log10(value)); // Compute the nearest power of 10 const nearestPowerOf10 = Math.pow(10, exponent); diff --git a/typescript/frontend-marios2/src/Resources/routes.json b/typescript/frontend-marios2/src/Resources/routes.json index d33a51dfc..c592e0bfe 100644 --- a/typescript/frontend-marios2/src/Resources/routes.json +++ b/typescript/frontend-marios2/src/Resources/routes.json @@ -1,6 +1,6 @@ { "installation": "installation/", - "liveView": "liveView/", + "live": "live", "users": "/users/", "log": "log/", "installations": "/installations/", @@ -9,6 +9,13 @@ "folder": "folder/", "manageAccess": "manageAccess/", "user": "user/", - "tree": "tree", - "list": "list" + "tree": "tree/", + "list": "list/", + "overview": "overview", + "manage": "manage", + "log": "log", + "information": "information", + "configuration": "configuration", + "login": "/login/", + "forgotPassword": "/forgotPassword/" } diff --git a/typescript/frontend-marios2/src/components/ForgotPassword.tsx b/typescript/frontend-marios2/src/components/ForgotPassword.tsx index 0063d3563..c44a4325c 100644 --- a/typescript/frontend-marios2/src/components/ForgotPassword.tsx +++ b/typescript/frontend-marios2/src/components/ForgotPassword.tsx @@ -16,12 +16,14 @@ import { TokenContext } from 'src/contexts/tokenContext'; import Avatar from '@mui/material/Avatar'; import LockOutlinedIcon from '@mui/icons-material/LockOutlined'; import axiosConfig from 'src/Resources/axiosConfig'; +import { useNavigate } from 'react-router-dom'; +import routes from 'src/Resources/routes.json'; interface ForgotPasswordPromps { resetPassword: () => void; } -function ForgotPassword(props: ForgotPasswordPromps) { +function ForgotPassword() { const [username, setUsername] = useState(''); const [loading, setLoading] = useState(false); const [open, setOpen] = useState(false); @@ -35,6 +37,7 @@ function ForgotPassword(props: ForgotPasswordPromps) { const { currentUser, setUser, removeUser } = context; const tokencontext = useContext(TokenContext); const { token, setNewToken, removeToken } = tokencontext; + const navigate = useNavigate(); const handleUsernameChange = (e) => { const { name, value } = e.target; @@ -43,7 +46,8 @@ function ForgotPassword(props: ForgotPasswordPromps) { const handleReturn = () => { setOpen(false); - props.resetPassword(); + navigate(routes.login); + //props.resetPassword(); }; const handleSubmit = () => { @@ -72,7 +76,7 @@ function ForgotPassword(props: ForgotPasswordPromps) { - + innovenergy logo @@ -122,6 +126,12 @@ function ForgotPassword(props: ForgotPasswordPromps) { margin="normal" required sx={{ width: 350 }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleSubmit(); + } + }} /> {loading && } diff --git a/typescript/frontend-marios2/src/components/ResetPassword.tsx b/typescript/frontend-marios2/src/components/ResetPassword.tsx index 789fed067..2544de6f1 100644 --- a/typescript/frontend-marios2/src/components/ResetPassword.tsx +++ b/typescript/frontend-marios2/src/components/ResetPassword.tsx @@ -73,7 +73,7 @@ function ResetPassword() { - + innovenergy logo diff --git a/typescript/frontend-marios2/src/components/login.tsx b/typescript/frontend-marios2/src/components/login.tsx index 30d144ef9..dbcd203b3 100644 --- a/typescript/frontend-marios2/src/components/login.tsx +++ b/typescript/frontend-marios2/src/components/login.tsx @@ -23,12 +23,9 @@ import { TokenContext } from 'src/contexts/tokenContext'; import { useNavigate } from 'react-router-dom'; import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; import CheckBoxIcon from '@mui/icons-material/CheckBox'; +import routes from 'src/Resources/routes.json'; -interface loginPromps { - onForgotPassword: () => void; -} - -function Login(props: loginPromps) { +function Login() { const [username, setUsername] = useState(''); const [password, setPassword] = useState(''); const [loading, setLoading] = useState(false); @@ -43,11 +40,10 @@ function Login(props: loginPromps) { if (!context) { return null; } - const { currentUser, setUser, removeUser } = context; + const { currentUser, setUser, removeUser } = context; const tokencontext = useContext(TokenContext); const { token, setNewToken, removeToken } = tokencontext; - const cookies = new Cookies(); const handleUsernameChange = (event: React.ChangeEvent) => { @@ -61,6 +57,11 @@ function Login(props: loginPromps) { const handleRememberMeChange = () => { setRememberMe(!rememberMe); }; + + const onForgotPassword = () => { + navigate(routes.forgotPassword); + }; + const handleSubmit = () => { setLoading(true); axiosConfigWithoutToken @@ -76,7 +77,7 @@ function Login(props: loginPromps) { cookies.set('rememberedUsername', username, { path: '/' }); cookies.set('rememberedPassword', password, { path: '/' }); } - navigate('/'); + navigate(routes.installations); } }) .catch((error) => { @@ -91,10 +92,10 @@ function Login(props: loginPromps) { return ( <> - + - + innovenergy logo @@ -113,7 +114,6 @@ function Login(props: loginPromps) { boxShadow: 24, p: 6, position: 'absolute', - top: '30%', left: '50%', transform: 'translate(-50%, -50%)' @@ -143,6 +143,12 @@ function Login(props: loginPromps) { margin="normal" required sx={{ width: 350 }} + onKeyDown={(e) => { + if (e.key === 'Enter') { + e.preventDefault(); + handleSubmit(); + } + }} /> { + if (e.key === 'Enter') { + e.preventDefault(); + handleSubmit(); + } + }} /> @@ -242,7 +254,7 @@ function Login(props: loginPromps) { sx={{ color: '#111111' }} onClick={(e) => { e.preventDefault(); - props.onForgotPassword(); + onForgotPassword(); }} > Forgot password? diff --git a/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx b/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx index 1d8f5e03e..09c4fcdb6 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Configuration/Configuration.tsx @@ -1,6 +1,7 @@ import { TopologyValues } from '../Log/graph.util'; import { Box, CardContent, Container, Grid, TextField } from '@mui/material'; import React from 'react'; +import { FormattedMessage } from 'react-intl'; interface ConfigurationProps { values: TopologyValues; @@ -32,7 +33,12 @@ function Configuration(props: ConfigurationProps) { >
+ } value={props.values.minimumSoC.values[0].value + ' %'} fullWidth /> @@ -40,14 +46,24 @@ function Configuration(props: ConfigurationProps) {
+ } value={props.values.calibrationChargeForced.values[0].value} fullWidth />
+ } value={ ( (props.values.gridSetPoint.values[0].value as number) / @@ -59,7 +75,12 @@ function Configuration(props: ConfigurationProps) {
+ } value={ ( (props.values.installedDcDcPower.values[0] @@ -71,7 +92,12 @@ function Configuration(props: ConfigurationProps) {
+ } value={ ( (props.values.maximumDischargePower.values[0] @@ -83,7 +109,12 @@ function Configuration(props: ConfigurationProps) {
+ } value={props.values.battery.values.length - 4} fullWidth /> diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/FlatInstallationView.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/FlatInstallationView.tsx index 36e301b70..8ea7c68cb 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/FlatInstallationView.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/FlatInstallationView.tsx @@ -18,7 +18,6 @@ import CancelIcon from '@mui/icons-material/Cancel'; import { LogContext } from 'src/contexts/LogContextProvider'; import { FormattedMessage } from 'react-intl'; import { useNavigate } from 'react-router-dom'; -import routes from 'src/Resources/routes.json'; interface FlatInstallationViewProps { installations: I_Installation[]; @@ -29,7 +28,6 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => { const logContext = useContext(LogContext); const { getStatus } = logContext; const navigate = useNavigate(); - const searchParams = new URLSearchParams(location.search); const installationId = parseInt(searchParams.get('installation')); const [selectedInstallation, setSelectedInstallation] = useState(-1); @@ -37,15 +35,9 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => { const handleSelectOneInstallation = (installationID: number): void => { if (selectedInstallation != installationID) { setSelectedInstallation(installationID); - navigate( - routes.installations + - routes.list + - '?installation=' + - installationID.toString(), - { - replace: true - } - ); + navigate(`?installation=${installationID}`, { + replace: true + }); } else { setSelectedInstallation(-1); } @@ -88,8 +80,8 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => { @@ -234,6 +226,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => { + {props.installations.map((installation) => ( - }, - { - value: 'overview', - label: - }, - , - { - value: 'manage', - label: - }, - { - value: 'log', - label: - }, - { - value: 'information', - label: - }, - - { - value: 'configuration', - label: ( - - ) - } - ]; const theme = useTheme(); - const [currentTab, setCurrentTab] = useState('live'); const [formValues, setFormValues] = useState(props.current_installation); const requiredFields = ['name', 'region', 'location', 'country']; const context = useContext(UserContext); @@ -100,17 +68,16 @@ function Installation(props: singleInstallationProps) { const { installationStatus, handleLogWarningOrError, getStatus } = logContext; const searchParams = new URLSearchParams(location.search); const installationId = parseInt(searchParams.get('installation')); + const currentTab = searchParams.get('tab'); + const [values, setValues] = useState(null); + const [openModalDeleteInstallation, setOpenModalDeleteInstallation] = + useState(false); if (formValues == undefined) { return null; } - const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => { - setCurrentTab(value); - setError(false); - }; - const handleChange = (e) => { const { name, value } = e.target; setFormValues({ @@ -127,7 +94,18 @@ function Installation(props: singleInstallationProps) { const handleDelete = (e) => { setLoading(true); setError(false); + setOpenModalDeleteInstallation(true); + }; + + const deleteInstallationModalHandle = (e) => { + setOpenModalDeleteInstallation(false); deleteInstallation(formValues, props.type); + setLoading(false); + }; + + const deleteInstallationModalHandleCancel = (e) => { + setOpenModalDeleteInstallation(false); + setLoading(false); }; const areRequiredFieldsFilled = () => { @@ -155,8 +133,8 @@ function Installation(props: singleInstallationProps) { useEffect(() => { let isMounted = true; setFormValues(props.current_installation); - setErrorLoadingS3Data(false); + let disconnectedStatusResult = []; const fetchDataPeriodically = async () => { const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20)); @@ -165,9 +143,6 @@ function Installation(props: singleInstallationProps) { try { const res = await fetchData(now, s3Credentials); - if (installationId == 2) { - console.log('Fetched data from unix timestamp ' + now); - } if (!isMounted) { return; } @@ -176,8 +151,22 @@ function Installation(props: singleInstallationProps) { const newErrors: Notification[] = []; if (res === FetchResult.notAvailable || res === FetchResult.tryLater) { - setErrorLoadingS3Data(true); handleLogWarningOrError(props.current_installation.id, -1); + + disconnectedStatusResult.unshift(-1); + disconnectedStatusResult = disconnectedStatusResult.slice(0, 5); + + let i = 0; + //If at least one status value shows an error, then show error + for (i; i < disconnectedStatusResult.length; i++) { + if (disconnectedStatusResult[i] != -1) { + break; + } + } + + if (i === disconnectedStatusResult.length) { + setErrorLoadingS3Data(true); + } } else { setErrorLoadingS3Data(false); setValues( @@ -237,9 +226,15 @@ function Installation(props: singleInstallationProps) { if (newErrors.length > 0) { handleLogWarningOrError(props.current_installation.id, 2); + disconnectedStatusResult.unshift(2); + disconnectedStatusResult = disconnectedStatusResult.slice(0, 5); } else if (newWarnings.length > 0) { + disconnectedStatusResult.unshift(1); + disconnectedStatusResult = disconnectedStatusResult.slice(0, 5); handleLogWarningOrError(props.current_installation.id, 1); } else { + disconnectedStatusResult.unshift(0); + disconnectedStatusResult = disconnectedStatusResult.slice(0, 5); handleLogWarningOrError(props.current_installation.id, 0); } } @@ -259,291 +254,394 @@ function Installation(props: singleInstallationProps) { if (installationId == props.current_installation.id) { return ( - - - + {openModalDeleteInstallation && ( + setOpenModalDeleteInstallation(false)} + aria-labelledby="error-modal" + aria-describedby="error-modal-description" > - {tabs.map((tab) => ( - - ))} - - - - - {currentTab === 'information' && ( - - + + Do you want to delete this installation? + + +
+ + +
+ + + )} - {currentUser.hasWriteAccess && ( - <> -
- -
+ +
+ + + + + {props.current_installation.name} + +
-
- -
- -
- -
- - )} - -
+ + {currentTab === 'information' && ( + + + + + - {currentUser.hasWriteAccess && ( - - )} - {currentUser.hasWriteAccess && ( - - )} - {loading && ( - + + } + name="name" + value={formValues.name} + onChange={handleChange} + fullWidth + required + error={formValues.name === ''} /> - )} - {error && ( - - - setError(false)} - sx={{ marginLeft: '4px' }} - > - - - - )} - {updated && ( - - +
+
+ + } + name="region" + value={formValues.region} + onChange={handleChange} + variant="outlined" + fullWidth + required + error={formValues.name === ''} + /> +
+
+ + } + name="location" + value={formValues.location} + onChange={handleChange} + variant="outlined" + fullWidth + required + error={formValues.name === ''} + /> +
+
+ + } + name="country" + value={formValues.country} + onChange={handleChange} + variant="outlined" + fullWidth + required + error={formValues.name === ''} + /> +
+
+ + } + name="orderNumbers" + value={formValues.orderNumbers} + onChange={handleChange} + variant="outlined" + fullWidth + /> +
+
+ + } + name="installationName" + value={formValues.installationName} + onChange={handleChange} + variant="outlined" + fullWidth + /> +
- setUpdated(false)} // Set error state to false on click - sx={{ marginLeft: '4px' }} - > - - - + {currentUser.hasWriteAccess && ( + <> +
+ +
+ +
+ +
+ +
+ +
+ )} -
- - + +
+ {currentUser.hasWriteAccess && ( + + )} + {currentUser.hasWriteAccess && ( + + )} + + {loading && ( + + )} + {error && ( + + + setError(false)} + sx={{ marginLeft: '4px' }} + > + + + + )} + {updated && ( + + + + setUpdated(false)} // Set error state to false on click + sx={{ marginLeft: '4px' }} + > + + + + )} +
+ + + - - - )} - {currentTab === 'overview' && ( - - )} - {currentTab === 'configuration' && currentUser.hasWriteAccess && ( - - )} - {currentTab === 'manage' && currentUser.hasWriteAccess && ( - - - - )} - {currentTab === 'live' && } - {currentTab === 'log' && ( - - )} - - - + + )} + {currentTab === 'overview' && ( + + )} + {currentTab === 'configuration' && currentUser.hasWriteAccess && ( + + )} + {currentTab === 'manage' && currentUser.hasWriteAccess && ( + + + + )} + {currentTab === 'live' && } + {currentTab === 'log' && ( + + )} + + + + ); } else { return null; diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/InstallationSearch.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/InstallationSearch.tsx index 2af85562f..85d1f878c 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/InstallationSearch.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/InstallationSearch.tsx @@ -1,4 +1,4 @@ -import React, { useContext, useEffect, useState } from 'react'; +import React, { useEffect, useState } from 'react'; import { FormControl, Grid, @@ -6,34 +6,32 @@ import { TextField, useTheme } from '@mui/material'; -import { InstallationsContext } from 'src/contexts/InstallationsContextProvider'; import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone'; import FlatInstallationView from 'src/content/dashboards/Installations/FlatInstallationView'; -import LogContextProvider from 'src/contexts/LogContextProvider'; +import LogContextProvider from '../../../contexts/LogContextProvider'; +import { I_Installation } from '../../../interfaces/InstallationTypes'; -function InstallationSearch() { +interface installationSearchProps { + installations: I_Installation[]; +} + +function InstallationSearch(props: installationSearchProps) { const theme = useTheme(); const [searchTerm, setSearchTerm] = useState(''); - const { installations, fetchAllInstallations } = - useContext(InstallationsContext); const searchParams = new URLSearchParams(location.search); const installationId = parseInt(searchParams.get('installation')); - useEffect(() => { - fetchAllInstallations(); - }, []); - - const [filteredData, setFilteredData] = useState(installations); + const [filteredData, setFilteredData] = useState(props.installations); useEffect(() => { - const filtered = installations.filter( + const filtered = props.installations.filter( (item) => item.name.toLowerCase().includes(searchTerm.toLowerCase()) || item.location.toLowerCase().includes(searchTerm.toLowerCase()) ); setFilteredData(filtered); - }, [searchTerm, installations]); + }, [searchTerm, props.installations]); return ( <> @@ -41,7 +39,7 @@ function InstallationSearch() { diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/flatView.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/flatView.tsx deleted file mode 100644 index 03998cea6..000000000 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/flatView.tsx +++ /dev/null @@ -1,19 +0,0 @@ -import { Box, Grid, useTheme } from '@mui/material'; -import InstallationsContextProvider from 'src/contexts/InstallationsContextProvider'; -import InstallationSearch from './InstallationSearch'; - -function FlatView() { - const theme = useTheme(); - - return ( - - - - - - - - ); -} - -export default FlatView; diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx index 7a892956f..fc33e30eb 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx @@ -1,10 +1,9 @@ -import React, { ChangeEvent, useEffect, useState } from 'react'; +import React, { ChangeEvent, useContext, useEffect, useState } from 'react'; import Footer from 'src/components/Footer'; -import { Card, Container, Grid, Tab, Tabs, useTheme } from '@mui/material'; +import { Box, Card, Container, Grid, Tab, Tabs, useTheme } from '@mui/material'; import ListIcon from '@mui/icons-material/List'; import AccountTreeIcon from '@mui/icons-material/AccountTree'; import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper'; -import UsersContextProvider from 'src/contexts/UsersContextProvider'; import { Link, Route, @@ -12,50 +11,220 @@ import { useLocation, useNavigate } from 'react-router-dom'; -import FlatView from './flatView'; import TreeView from '../Tree/treeView'; import routes from 'src/Resources/routes.json'; +import InstallationSearch from './InstallationSearch'; +import { FormattedMessage } from 'react-intl'; +import { UserContext } from '../../../contexts/userContext'; +import { InstallationsContext } from '../../../contexts/InstallationsContextProvider'; +import LogContextProvider from '../../../contexts/LogContextProvider'; +import Installation from './Installation'; function InstallationTabs() { const theme = useTheme(); const location = useLocation(); const navigate = useNavigate(); - const tabs = [ - { - value: 'list', - label: 'Flat view', - icon: - }, - { - value: 'tree', - label: 'Tree view', - icon: - } - ]; + + const searchParams = new URLSearchParams(location.search); + const installationId = parseInt(searchParams.get('installation')); + const [singleInstallationID, setSingleInstallationID] = useState(-1); + const context = useContext(UserContext); + const { currentUser, setUser } = context; const [currentTab, setCurrentTab] = useState('list'); + const { installations, fetchAllInstallations } = + useContext(InstallationsContext); useEffect(() => { - //console.log(location.pathname); - if ( - location.pathname === '/installations' || - location.pathname === '/installations/' - ) { - navigate(routes.installations + routes.list, { + if (installations.length === 0) { + fetchAllInstallations(); + } + + if (installations.length === 1) { + navigate(`list?installation=${installations[0].id}&tab=live`, { replace: true }); - } else if (location.pathname === '/installations/tree') { - setCurrentTab('tree'); + setCurrentTab('live'); + } else { + if ( + location.pathname === '/installations' || + location.pathname === '/installations/' + ) { + navigate(routes.installations + routes.list, { + replace: true + }); + } else if (location.pathname === '/installations/tree/') { + setCurrentTab('tree'); + } else if (location.pathname === '/installations/list/') { + setCurrentTab('list'); + } + + if (installationId) { + navigate(`?installation=${installationId}&tab=live`, { + replace: true + }); + setCurrentTab('live'); + } } - }, [location.pathname, navigate]); + }, [location.pathname, navigate, installationId, installations]); const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => { setCurrentTab(value); - navigate(value); }; - return ( - - + const singleInstallationTabs = currentUser.hasWriteAccess + ? [ + { + value: 'live', + label: + }, + { + value: 'overview', + label: + }, + , + { + value: 'manage', + label: ( + + ) + }, + { + value: 'log', + label: + }, + { + value: 'information', + label: ( + + ) + }, + + { + value: 'configuration', + label: ( + + ) + } + ] + : [ + { + value: 'live', + label: + }, + { + value: 'overview', + label: + }, + , + { + value: 'log', + label: + }, + { + value: 'information', + label: ( + + ) + } + ]; + + const tabs = installationId + ? currentUser.hasWriteAccess + ? [ + { + value: 'list', + icon: + }, + { + value: 'tree', + icon: + }, + { + value: 'live', + label: + }, + { + value: 'overview', + label: + }, + , + { + value: 'manage', + label: ( + + ) + }, + { + value: 'log', + label: + }, + { + value: 'information', + label: ( + + ) + }, + + { + value: 'configuration', + label: ( + + ) + } + ] + : [ + { + value: 'list', + icon: + }, + { + value: 'tree', + icon: + }, + + { + value: 'live', + label: + }, + { + value: 'overview', + label: + }, + , + { + value: 'log', + label: + }, + { + value: 'information', + label: ( + + ) + } + ] + : [ + { + value: 'list', + icon: + }, + { + value: 'tree', + icon: + } + ]; + + return installations.length > 1 ? ( + <> + ))} @@ -85,15 +259,79 @@ function InstallationTabs() { spacing={0} > - } /> + + + + + + } + /> } />