Merge remote-tracking branch 'origin/main'

This commit is contained in:
Kim 2023-07-20 12:44:32 +02:00
commit 66da08b407
41 changed files with 1165 additions and 4508 deletions

File diff suppressed because it is too large Load Diff

View File

@ -3,43 +3,30 @@
"version": "0.1.0", "version": "0.1.0",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@aws-sdk/client-s3": "^3.316.0",
"@emotion/styled": "^11.10.5", "@emotion/styled": "^11.10.5",
"@minoru/react-dnd-treeview": "^3.4.1",
"@mui/icons-material": "^5.11.0", "@mui/icons-material": "^5.11.0",
"@mui/lab": "^5.0.0-alpha.120", "@mui/lab": "^5.0.0-alpha.120",
"@mui/material": "^5.11.7", "@mui/material": "^5.11.7",
"@mui/x-date-pickers": "^6.5.0", "@mui/x-date-pickers": "^6.5.0",
"@testing-library/jest-dom": "^5.16.5", "@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"@types/jest": "^27.5.2",
"@types/node": "^16.18.11", "@types/node": "^16.18.11",
"@types/react": "^18.0.27", "@types/react": "^18.0.27",
"@types/react-dom": "^18.0.10", "@types/react-dom": "^18.0.10",
"@types/react-router-dom": "^5.3.3", "@types/react-router-dom": "^5.3.3",
"axios": "^1.3.1", "axios": "^1.3.1",
"chart.js": "^4.2.1", "chart.js": "^4.2.1",
"css-loader": "^6.7.3",
"dayjs": "^1.11.7", "dayjs": "^1.11.7",
"formik": "^2.2.9", "formik": "^2.2.9",
"linq-to-typescript": "^11.0.0", "linq-to-typescript": "^11.0.0",
"package.json": "^2.0.1",
"plotly.js": "^2.20.0", "plotly.js": "^2.20.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-chartjs-2": "^5.2.0",
"react-dnd": "^16.0.1",
"react-dnd-html5-backend": "^16.0.1",
"react-dnd-scrolling": "^1.3.3",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-intl": "^6.2.10", "react-intl": "^6.2.10",
"react-plotly.js": "^2.6.0", "react-plotly.js": "^2.6.0",
"react-router-dom": "^6.8.0", "react-router-dom": "^6.8.0",
"react-scripts": "5.0.1", "react-scripts": "5.0.1",
"reactflow": "^11.5.6",
"rxjs": "^7.8.0", "rxjs": "^7.8.0",
"sass": "^1.58.3",
"sass-loader": "^13.2.0",
"simplytyped": "^3.3.0", "simplytyped": "^3.3.0",
"style-loader": "^3.3.1", "style-loader": "^3.3.1",
"testcafe": "^2.4.0", "testcafe": "^2.4.0",
@ -83,7 +70,7 @@
"devDependencies": { "devDependencies": {
"@formatjs/cli": "^6.0.3", "@formatjs/cli": "^6.0.3",
"@types/react-plotly.js": "^2.6.0", "@types/react-plotly.js": "^2.6.0",
"@types/react-window": "^1.8.5", "depcheck": "^1.4.3",
"eslint-plugin-formatjs": "^4.10.1", "eslint-plugin-formatjs": "^4.10.1",
"prettier": "^2.8.8" "prettier": "^2.8.8"
} }

View File

@ -2,7 +2,7 @@ import useToken from "./hooks/useToken";
import Login from "./Login"; import Login from "./Login";
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom"; import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
import { Container, Grid, colors, Box } from "@mui/material"; import { Container, Grid, Box } from "@mui/material";
import routes from "./routes.json"; import routes from "./routes.json";
import { IntlProvider, MessageFormatElement } from "react-intl"; import { IntlProvider, MessageFormatElement } from "react-intl";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
@ -17,20 +17,21 @@ import InstallationPage from "./components/Installations/InstallationPage";
import { UserContext } from "./components/Context/UserContextProvider"; import { UserContext } from "./components/Context/UserContextProvider";
import ResetPassword from "./ResetPassword"; import ResetPassword from "./ResetPassword";
import innovenergyLogo from "./resources/innoveng_logo_on_orange.png"; import innovenergyLogo from "./resources/innoveng_logo_on_orange.png";
import { colors } from "./index";
const App = () => { const App = () => {
const { token, setToken, removeToken } = useToken(); const { token, setToken, removeToken } = useToken();
const [language, setLanguage] = useState("EN"); const [language, setLanguage] = useState("en");
const { getCurrentUser } = useContext(UserContext); const { getCurrentUser } = useContext(UserContext);
const getTranslations = () => { const getTranslations = () => {
switch (language) { switch (language) {
case "DE": case "en":
return de; return de;
case "EN": case "de":
return en; return en;
case "FR": case "fr":
return fr; return fr;
} }
}; };
@ -41,12 +42,13 @@ const App = () => {
if (token && getCurrentUser()?.mustResetPassword) { if (token && getCurrentUser()?.mustResetPassword) {
return <ResetPassword />; return <ResetPassword />;
} }
console.log("lang", language);
return ( return (
<BrowserRouter> <BrowserRouter>
<IntlProvider <IntlProvider
messages={getTranslations()} messages={getTranslations()}
locale={language} locale={language}
defaultLocale="EN" defaultLocale="en"
> >
<Container maxWidth="xl" sx={{ pt: 2 }}> <Container maxWidth="xl" sx={{ pt: 2 }}>
<Grid container> <Grid container>
@ -73,7 +75,7 @@ const App = () => {
</Grid> </Grid>
</Container> </Container>
<Box <Box
bgcolor="#eaecf1" bgcolor={colors.greyLight}
pt={3} pt={3}
borderTop="2px solid" borderTop="2px solid"
borderColor=" #a8b0be" borderColor=" #a8b0be"

View File

@ -1,10 +1,17 @@
import React, { useContext, useState } from "react"; import React, { useContext, useState } from "react";
import { Alert, CircularProgress, Grid, useTheme } from "@mui/material"; import {
Alert,
CircularProgress,
Grid,
Typography,
useTheme,
} from "@mui/material";
import Container from "@mui/material/Container"; import Container from "@mui/material/Container";
import { axiosConfigWithoutToken } from "./config/axiosConfig"; import { axiosConfigWithoutToken } from "./config/axiosConfig";
import InnovenergyTextfield from "./components/Layout/InnovenergyTextfield"; import InnovenergyTextfield from "./components/Layout/InnovenergyTextfield";
import InnovenergyButton from "./components/Layout/InnovenergyButton"; import InnovenergyButton from "./components/Layout/InnovenergyButton";
import { UserContext } from "./components/Context/UserContextProvider"; import { UserContext } from "./components/Context/UserContextProvider";
import innovenergyLogo from "./resources/innoveng_logo_on_orange.png";
const loginUser = async (username: string, password: string) => { const loginUser = async (username: string, password: string) => {
return axiosConfigWithoutToken.post("/Login", null, { return axiosConfigWithoutToken.post("/Login", null, {
@ -59,6 +66,14 @@ const Login = ({ setToken, setLanguage }: I_LoginProps) => {
}; };
return ( return (
<>
<Container maxWidth="xl" sx={{ pt: 2 }}>
<Grid container>
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
</Grid>
</Grid>
</Container>
<Container maxWidth="sm" sx={{ p: 2, alignContent: "center" }}> <Container maxWidth="sm" sx={{ p: 2, alignContent: "center" }}>
<InnovenergyTextfield <InnovenergyTextfield
id="username-textfield" id="username-textfield"
@ -75,14 +90,23 @@ const Login = ({ setToken, setLanguage }: I_LoginProps) => {
value={password} value={password}
handleChange={(e) => setPassword(e.target.value)} handleChange={(e) => setPassword(e.target.value)}
/> />
{error && <Alert severity="error">Incorrect username or password</Alert>} {error && (
<Alert severity="error">Incorrect username or password</Alert>
)}
<Grid container justifyContent="flex-end" sx={{ pt: 1 }}> <Grid container justifyContent="flex-end" sx={{ pt: 1 }}>
<InnovenergyButton onClick={handleSubmit} sx={{ my: 1 }}> <InnovenergyButton
onClick={handleSubmit}
sx={{ my: 1 }}
type="submit"
>
Login Login
</InnovenergyButton> </InnovenergyButton>
</Grid> </Grid>
{loading && <CircularProgress sx={{color: theme.palette.secondary.main}} />} {loading && (
<CircularProgress sx={{ color: theme.palette.secondary.main }} />
)}
</Container> </Container>
</>
); );
}; };

View File

@ -27,19 +27,16 @@ const S3CredentialsContextProvider = ({
}) => { }) => {
const [s3Credentials, setS3Credentials] = useState<S3Credentials>(); const [s3Credentials, setS3Credentials] = useState<S3Credentials>();
console.log("creds", s3Credentials);
const saveS3Credentials = (credentials: S3Credentials, id: string) => { const saveS3Credentials = (credentials: S3Credentials, id: string) => {
//const s3Bucket = id + "-3e5b3069-214a-43ee-8d85-57d72000c19d"; const s3Bucket = id + "-3e5b3069-214a-43ee-8d85-57d72000c19d";
/* const s3Bucket = "saliomameiringen"; setS3Credentials({ s3Bucket, ...credentials });
/* setS3Credentials({
setS3Credentials({ s3Bucket, ...credentials });*/
setS3Credentials({
s3Region: "sos-ch-dk-2", s3Region: "sos-ch-dk-2",
s3Provider: "exo.io", s3Provider: "exo.io",
s3Key: "EXO15c0bf710e158e9b83270f0a", s3Key: "EXO15c0bf710e158e9b83270f0a",
s3Secret: "Dd5jYSiZtt_Zt5Ba5mDmaiLCdASUaKLfduSKY-SU-lg", s3Secret: "Dd5jYSiZtt_Zt5Ba5mDmaiLCdASUaKLfduSKY-SU-lg",
s3Bucket: "saliomameiringen", s3Bucket: "saliomameiringen",
}); });*/
}; };
const fetchData = (timestamp: UnixTime): Promise<FetchResult<DataRecord>> => { const fetchData = (timestamp: UnixTime): Promise<FetchResult<DataRecord>> => {

View File

@ -5,6 +5,7 @@ import {
DialogTitle, DialogTitle,
TextField, TextField,
colors, colors,
Alert,
} from "@mui/material"; } from "@mui/material";
import DialogActions from "@mui/material/DialogActions"; import DialogActions from "@mui/material/DialogActions";
import { useContext, useState } from "react"; import { useContext, useState } from "react";
@ -19,6 +20,7 @@ import InnovenergyButton from "../../Layout/InnovenergyButton";
const AvailableUserDialog = () => { const AvailableUserDialog = () => {
const [selectedUsers, setSelectedUsers] = useState<I_User[]>([]); const [selectedUsers, setSelectedUsers] = useState<I_User[]>([]);
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [errors, setErrors] = useState<string[]>([]);
const { currentType } = useContext(GroupContext); const { currentType } = useContext(GroupContext);
const { id } = useParams(); const { id } = useParams();
@ -42,7 +44,8 @@ const AvailableUserDialog = () => {
.then(() => { .then(() => {
fetchUsersWithDirectAccessForResource(); fetchUsersWithDirectAccessForResource();
fetchUsersWithInheritedAccessForResource(); fetchUsersWithInheritedAccessForResource();
}); })
.catch(() => setErrors((errors) => [...errors, user.name]));
}); });
}; };
@ -63,6 +66,17 @@ const AvailableUserDialog = () => {
<FormattedMessage id="manageAccess" defaultMessage="Manage access" /> <FormattedMessage id="manageAccess" defaultMessage="Manage access" />
</DialogTitle> </DialogTitle>
<DialogContent id="available-user-dialog-content"> <DialogContent id="available-user-dialog-content">
{errors.length > 0 && (
<Alert sx={{ my: 1 }} severity="error">
<FormattedMessage
id="grantAccessError"
defaultMessage="Couldn't grant access to the following users: "
/>
{errors.map((error, index) =>
errors.length === index + 1 ? error : error + ", "
)}
</Alert>
)}
<Autocomplete <Autocomplete
sx={{ width: "500px", pt: 1 }} sx={{ width: "500px", pt: 1 }}
multiple multiple

View File

@ -1,4 +1,4 @@
import { List } from "@mui/material"; import { List, useTheme } from "@mui/material";
import { ReactNode } from "react"; import { ReactNode } from "react";
interface InnovenergyListProps { interface InnovenergyListProps {
@ -7,10 +7,11 @@ interface InnovenergyListProps {
} }
const InnovenergyList = (props: InnovenergyListProps) => { const InnovenergyList = (props: InnovenergyListProps) => {
const theme = useTheme();
return ( return (
<List <List
sx={{ sx={{
bgcolor: "white", bgcolor: theme.palette.primary.dark,
position: "relative", position: "relative",
overflow: "auto", overflow: "auto",
maxHeight: 500, maxHeight: 500,

View File

@ -44,7 +44,7 @@ const UsersWithDirectAccess = () => {
}); });
}; };
if (directAccessUsers && directAccessUsers.length) { if (directAccessUsers && directAccessUsers.length > 0) {
return ( return (
<> <>
{error && ( {error && (
@ -90,7 +90,14 @@ const UsersWithDirectAccess = () => {
</> </>
); );
} }
return null; return (
<Alert severity="info" sx={{ mx: 2 }}>
<FormattedMessage
id="noDirectAccessUsers"
defaultMessage="No direct access users were found"
/>
</Alert>
);
}; };
export default UsersWithDirectAccess; export default UsersWithDirectAccess;

View File

@ -4,6 +4,7 @@ import {
Divider, Divider,
ListItemAvatar, ListItemAvatar,
Avatar, Avatar,
Alert,
} from "@mui/material"; } from "@mui/material";
import { Fragment, useContext, useEffect } from "react"; import { Fragment, useContext, useEffect } from "react";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
@ -24,7 +25,7 @@ const UsersWithInheritedAccess = () => {
fetchUsersWithInheritedAccessForResource(); fetchUsersWithInheritedAccessForResource();
}, [fetchUsersWithInheritedAccessForResource]); }, [fetchUsersWithInheritedAccessForResource]);
if (inheritedAccessUsers && inheritedAccessUsers.length) { if (inheritedAccessUsers && inheritedAccessUsers.length > 0) {
return ( return (
<> <>
{filterDuplicateUsers(inheritedAccessUsers).map( {filterDuplicateUsers(inheritedAccessUsers).map(
@ -73,7 +74,14 @@ const UsersWithInheritedAccess = () => {
</> </>
); );
} }
return null; return (
<Alert severity="info" sx={{ m: 2 }}>
<FormattedMessage
id="noInheritedAccessUsers"
defaultMessage="No inherited access users were found"
/>
</Alert>
);
}; };
export default UsersWithInheritedAccess; export default UsersWithInheritedAccess;

View File

@ -1,43 +1,31 @@
import { Dialog, DialogContent, DialogTitle, IconButton } from "@mui/material"; import { Dialog, DialogContent, DialogTitle, IconButton } from "@mui/material";
import { useState } from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import axiosConfig from "../../config/axiosConfig";
import { I_Folder } from "../../util/types";
import FolderForm from "./FolderForm";
import CloseIcon from "@mui/icons-material/Close"; import CloseIcon from "@mui/icons-material/Close";
import { ReactNode, useState } from "react";
import InnovenergyButton from "../Layout/InnovenergyButton"; import InnovenergyButton from "../Layout/InnovenergyButton";
interface AddNewDialogProps { interface AddNewDialogProps {
values: I_Folder; form: ReactNode;
message: ReactNode;
id: number;
} }
const AddNewDialog = (props: AddNewDialogProps) => { const AddNewDialog = (props: AddNewDialogProps) => {
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const { form, id, message } = props;
const intl = useIntl(); const intl = useIntl();
const handleSubmit = (data: I_Folder, childData: Partial<I_Folder>) => {
return axiosConfig
.post("/CreateFolder", {
...childData,
parentId: data.id,
})
.then((res) => {
setOpen(false);
return res;
});
};
return ( return (
<> <>
<InnovenergyButton <InnovenergyButton
id={"add-new-child-dialog-button-" + props.values.id} id={"add-new-child-dialog-button-" + id}
sx={{ ml: 2 }} sx={{ ml: 2 }}
onClick={() => setOpen(true)} onClick={() => setOpen(true)}
> >
<FormattedMessage id="addNewChild" defaultMessage="Add new child" /> {message}
</InnovenergyButton> </InnovenergyButton>
<Dialog <Dialog
id={"add-new-child-dialog-" + props.values.id} id={"add-new-child-dialog-" + id}
onClose={() => setOpen(false)} onClose={() => setOpen(false)}
aria-labelledby="customized-dialog-title" aria-labelledby="customized-dialog-title"
open={open} open={open}
@ -50,12 +38,9 @@ const AddNewDialog = (props: AddNewDialogProps) => {
maxWidth="sm" maxWidth="sm"
> >
<DialogTitle> <DialogTitle>
<FormattedMessage <FormattedMessage id="createNew" defaultMessage="Create new" />
id="createNewFolder"
defaultMessage="Create new folder"
/>
<IconButton <IconButton
id={"add-new-child-dialog-icon-button-" + props.values.id} id={"add-new-child-dialog-icon-button-" + id}
edge="start" edge="start"
color="inherit" color="inherit"
onClick={() => setOpen(false)} onClick={() => setOpen(false)}
@ -69,14 +54,10 @@ const AddNewDialog = (props: AddNewDialogProps) => {
top: 8, top: 8,
}} }}
> >
<CloseIcon <CloseIcon id={"add-new-child-dialog-close-icon-" + id} />
id={"add-new-child-dialog-close-icon-" + props.values.id}
/>
</IconButton> </IconButton>
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>{form}</DialogContent>
<FolderForm values={props.values} handleSubmit={handleSubmit} />
</DialogContent>
</Dialog> </Dialog>
</> </>
); );

View File

@ -0,0 +1,83 @@
import { Dialog, DialogContent, DialogTitle, IconButton } from "@mui/material";
import { useState } from "react";
import { FormattedMessage, useIntl } from "react-intl";
import axiosConfig from "../../config/axiosConfig";
import { I_Folder, I_Installation } from "../../util/types";
import FolderForm from "./FolderForm";
import CloseIcon from "@mui/icons-material/Close";
import InnovenergyButton from "../Layout/InnovenergyButton";
import InstallationForm from "../Installations/InstallationForm";
import AddNewDialog from "./AddNew";
interface AddNewDialogProps {
values: I_Folder | I_Installation;
isFolder: boolean;
}
const AddNewButtons = (props: AddNewDialogProps) => {
const [open, setOpen] = useState(false);
const handleFolderSubmit = (data: I_Folder, childData: Partial<I_Folder>) => {
return axiosConfig
.post("/CreateFolder", {
...childData,
parentId: data.id,
})
.then((res) => {
setOpen(false);
return res;
});
};
const handleInstallationSubmit = (
data: I_Installation,
childData: Partial<I_Installation>
) => {
return axiosConfig
.post("/CreateInstallation", {
...childData,
parentId: data.id,
})
.then((res) => {
setOpen(false);
return res;
});
};
const folderForm = (
<FolderForm
values={props.values as I_Folder}
handleSubmit={handleFolderSubmit}
/>
);
const installationForm = (
<InstallationForm
values={props.values as I_Installation}
handleSubmit={handleInstallationSubmit}
/>
);
return (
<>
<AddNewDialog
form={installationForm}
message={
<FormattedMessage
id="addNewInstallation"
defaultMessage="Add new installation"
/>
}
id={props.values.id}
/>
<AddNewDialog
form={folderForm}
message={
<FormattedMessage id="addNewFolder" defaultMessage="Add new folder" />
}
id={props.values.id}
/>
</>
);
};
export default AddNewButtons;

View File

@ -4,7 +4,7 @@ import { useState, useEffect } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import axiosConfig from "../../config/axiosConfig"; import axiosConfig from "../../config/axiosConfig";
import { I_Folder } from "../../util/types"; import { I_Folder } from "../../util/types";
import AddNewDialog from "./AddNewDialog"; import AddNewButtons from "./AddNewButtons";
import FolderForm from "./FolderForm"; import FolderForm from "./FolderForm";
import MoveDialog from "./Tree/MoveDialog"; import MoveDialog from "./Tree/MoveDialog";
import { colors } from "index"; import { colors } from "index";
@ -36,7 +36,7 @@ const Folder = () => {
if (values && values.id && values.id.toString() === id) { if (values && values.id && values.id.toString() === id) {
const moveDialog = <MoveDialog values={values} />; const moveDialog = <MoveDialog values={values} />;
const addNewDialog = <AddNewDialog values={values} />; const addNewDialog = <AddNewButtons values={values} isFolder />;
return ( return (
<> <>

View File

@ -7,7 +7,7 @@ import { I_Folder } from "../../util/types";
import { GroupContext } from "../Context/GroupContextProvider"; import { GroupContext } from "../Context/GroupContextProvider";
import InnovenergySnackbar from "../InnovenergySnackbar"; import InnovenergySnackbar from "../InnovenergySnackbar";
import InnovenergyButton from "../Layout/InnovenergyButton"; import InnovenergyButton from "../Layout/InnovenergyButton";
import InnovenergyTextfield, { import {
I_InnovenergyTextfieldProps, I_InnovenergyTextfieldProps,
IePropertyGrid, IePropertyGrid,
} from "../Layout/InnovenergyTextfield"; } from "../Layout/InnovenergyTextfield";

View File

@ -11,7 +11,7 @@ import { instanceOfFolder } from "../../../util/group.util";
import { Grid, CircularProgress, useTheme } from "@mui/material"; import { Grid, CircularProgress, useTheme } from "@mui/material";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import { colors } from "../../.."; import { colors } from "../../..";
import { color } from "chart.js/helpers"; import InnovEnergyTreeItem from "../../Layout/InnovEnergyTreeItem";
const GroupTree = () => { const GroupTree = () => {
const { setCurrentType, fetchData, data, loading } = useContext(GroupContext); const { setCurrentType, fetchData, data, loading } = useContext(GroupContext);
@ -48,27 +48,15 @@ const GroupTree = () => {
}} }}
draggable={false} draggable={false}
> >
<TreeItem <InnovEnergyTreeItem
id={"group-tree-item-" + element.id} id={"group-tree-item-" + element.id}
key={element.type + element.id} key={element.type + element.id}
nodeId={element.type + element.id} nodeId={element.type + element.id}
label={element.name} label={element.name}
onClick={() => setCurrentType(element.type)} onClick={() => setCurrentType(element.type)}
sx={{
".MuiTreeItem-content": { py: "10px" },
".Mui-selected": {
backgroundColor: theme.palette.secondary.main,
},
".Mui-selected:hover": {
backgroundColor: theme.palette.secondary.main,
},
".Mui-focused": { backgroundColor: theme.palette.secondary.main },
bgcolor: colors.greyDark,
borderRadius: 2,
}}
> >
{getNodes(element)} {getNodes(element)}
</TreeItem> </InnovEnergyTreeItem>
</Link> </Link>
); );
}); });
@ -94,6 +82,9 @@ const GroupTree = () => {
flexGrow: 1, flexGrow: 1,
overflow: "auto", overflow: "auto",
overflowX: "hidden", overflowX: "hidden",
".Mui-selected": {
borderRadius: "4px",
},
}} }}
expanded={openNodes} expanded={openNodes}
onNodeToggle={(e, ids) => { onNodeToggle={(e, ids) => {

View File

@ -1,6 +1,6 @@
import { DialogActions, Dialog, DialogTitle } from "@mui/material"; import { DialogActions, Dialog, DialogTitle, Alert } from "@mui/material";
import DialogContent from "@mui/material/DialogContent"; import DialogContent from "@mui/material/DialogContent";
import { useContext, useState } from "react"; import React, { useContext, useState } from "react";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import axiosConfig from "../../../config/axiosConfig"; import axiosConfig from "../../../config/axiosConfig";
@ -12,15 +12,18 @@ import MoveTree from "./MoveTree";
interface MoveDialogProps { interface MoveDialogProps {
values: I_Folder; values: I_Folder;
} }
const MoveDialog = (props: MoveDialogProps) => { const MoveDialog = (props: MoveDialogProps) => {
const { values } = props; const { values } = props;
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const [selectedParentId, setSelectedParentId] = useState<number>( const [selectedParentId, setSelectedParentId] = useState<number>(
values.parentId values.parentId
); );
const { fetchData, currentType } = useContext(GroupContext); const { fetchData, currentType } = useContext(GroupContext);
const { id } = useParams(); const { id } = useParams();
const [error, setError] = useState();
const handleMove = () => { const handleMove = () => {
const route = const route =
@ -33,9 +36,9 @@ const MoveDialog = (props: MoveDialogProps) => {
.then((res) => { .then((res) => {
setOpen(false); setOpen(false);
fetchData(); fetchData();
}); })
.catch((err) => setError(err));
}; };
return ( return (
<div> <div>
<InnovenergyButton <InnovenergyButton
@ -59,7 +62,16 @@ const MoveDialog = (props: MoveDialogProps) => {
<DialogTitle> <DialogTitle>
<FormattedMessage id="moveTo" defaultMessage="Move to" /> <FormattedMessage id="moveTo" defaultMessage="Move to" />
</DialogTitle> </DialogTitle>
<DialogContent> <DialogContent>
{error && (
<Alert severity={"error"}>
<FormattedMessage
id="moveInstallationError"
defaultMessage="Couldn't move element, an error occured"
/>
</Alert>
)}
<MoveTree <MoveTree
parentId={selectedParentId} parentId={selectedParentId}
setSelectedParentId={setSelectedParentId} setSelectedParentId={setSelectedParentId}

View File

@ -2,11 +2,11 @@ import TreeView from "@mui/lab/TreeView";
import ExpandMoreIcon from "@mui/icons-material/ExpandMore"; import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
import ChevronRightIcon from "@mui/icons-material/ChevronRight"; import ChevronRightIcon from "@mui/icons-material/ChevronRight";
import { ReactNode, useContext } from "react"; import { ReactNode, useContext } from "react";
import { TreeItem } from "@mui/lab";
import { I_Folder, I_Installation } from "../../../util/types"; import { I_Folder, I_Installation } from "../../../util/types";
import { GroupContext } from "../../Context/GroupContextProvider"; import { GroupContext } from "../../Context/GroupContextProvider";
import { instanceOfFolder } from "../../../util/group.util"; import { instanceOfFolder } from "../../../util/group.util";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
import InnovEnergyTreeItem from "../../Layout/InnovEnergyTreeItem";
interface MoveTreeProps { interface MoveTreeProps {
setSelectedParentId: (value: number) => void; setSelectedParentId: (value: number) => void;
@ -29,14 +29,15 @@ const MoveTree = (props: MoveTreeProps) => {
.filter((element) => element.type === "Folder") .filter((element) => element.type === "Folder")
.map((element) => { .map((element) => {
return ( return (
<TreeItem <InnovEnergyTreeItem
id={"move-tree-item-" + element.id} id={"move-tree-item-" + element.id}
key={"move-tree-item-" + element.id} key={"move-tree-item-" + element.id}
nodeId={element.id.toString()} nodeId={element.id.toString()}
color="primary"
label={element.name} label={element.name}
> >
{getNodes(element)} {getNodes(element)}
</TreeItem> </InnovEnergyTreeItem>
); );
}); });
}; };

View File

@ -6,6 +6,7 @@ interface InnovenergySnackbarProps {
setOpen: (value: boolean) => void; setOpen: (value: boolean) => void;
error?: any; error?: any;
} }
const InnovenergySnackbar = (props: InnovenergySnackbarProps) => { const InnovenergySnackbar = (props: InnovenergySnackbarProps) => {
const { open, setOpen, error } = props; const { open, setOpen, error } = props;

View File

@ -3,10 +3,11 @@ import { AxiosError } from "axios";
import { useContext, useEffect, useState } from "react"; import { useContext, useEffect, useState } from "react";
import { useParams } from "react-router-dom"; import { useParams } from "react-router-dom";
import axiosConfig from "../../config/axiosConfig"; import axiosConfig from "../../config/axiosConfig";
import { I_Installation } from "../../util/types"; import { I_Folder, I_Installation } from "../../util/types";
import InstallationForm from "./InstallationForm"; import InstallationForm from "./InstallationForm";
import { colors } from "../.."; import { colors } from "../..";
import { S3CredentialsContext } from "../Context/S3CredentialsContextProvider"; import { S3CredentialsContext } from "../Context/S3CredentialsContextProvider";
import MoveDialog from "../Groups/Tree/MoveDialog";
interface I_InstallationProps { interface I_InstallationProps {
loading?: boolean; loading?: boolean;
@ -16,11 +17,16 @@ interface I_InstallationProps {
} }
const Installation = (props: I_InstallationProps) => { const Installation = (props: I_InstallationProps) => {
const { hasMoveButton, values, loading, error } = props; const { values, loading, error } = props;
const { id } = useParams(); const { id } = useParams();
const theme = useTheme(); const theme = useTheme();
const handleSubmit = (data: I_Folder, formikValues: Partial<I_Folder>) => {
return axiosConfig.put("/UpdateInstallation", { ...data, ...formikValues });
};
if (values && values.id && values.id.toString() === id) { if (values && values.id && values.id.toString() === id) {
const moveDialog = <MoveDialog values={values} />;
return ( return (
<Box <Box
sx={{ sx={{
@ -39,9 +45,15 @@ const Installation = (props: I_InstallationProps) => {
> >
<InstallationForm <InstallationForm
values={values} values={values}
id={id} handleSubmit={handleSubmit}
hasMoveButton={hasMoveButton} additionalButtons={props.hasMoveButton ? [moveDialog] : []}
/> />
<Box>
{values.s3WriteKey && <div>{`Write key: ${values.s3WriteKey}`}</div>}
{values.s3WriteSecret && (
<div>{`Write secret: ${values.s3WriteSecret}`}</div>
)}
</Box>
</Box> </Box>
); );
} else if (loading) { } else if (loading) {

View File

@ -1,9 +1,9 @@
import { Alert, Grid, Snackbar } from "@mui/material"; import { Alert, Grid, Snackbar } from "@mui/material";
import { useFormik } from "formik"; import { useFormik } from "formik";
import { useContext, useState } from "react"; import { ReactNode, useContext, useState } from "react";
import { FormattedMessage, useIntl } from "react-intl"; import { FormattedMessage, useIntl } from "react-intl";
import axiosConfig from "../../config/axiosConfig"; import axiosConfig from "../../config/axiosConfig";
import { I_Installation } from "../../util/types"; import { I_Folder, I_Installation } from "../../util/types";
import { InstallationsContext } from "../Context/InstallationsContextProvider"; import { InstallationsContext } from "../Context/InstallationsContextProvider";
import MoveDialog from "../Groups/Tree/MoveDialog"; import MoveDialog from "../Groups/Tree/MoveDialog";
import InnovenergyButton from "../Layout/InnovenergyButton"; import InnovenergyButton from "../Layout/InnovenergyButton";
@ -13,15 +13,19 @@ import InnovenergyTextfield, {
} from "../Layout/InnovenergyTextfield"; } from "../Layout/InnovenergyTextfield";
import { UserContext } from "../Context/UserContextProvider"; import { UserContext } from "../Context/UserContextProvider";
import * as Yup from "yup"; import * as Yup from "yup";
import { AxiosResponse } from "axios";
interface I_InstallationFormProps { interface I_InstallationFormProps {
values: I_Installation; values: I_Installation;
id: string | undefined; handleSubmit: (
hasMoveButton?: boolean; data: I_Installation,
formikValues: Partial<I_Installation>
) => Promise<AxiosResponse>;
additionalButtons?: ReactNode[];
} }
const InstallationForm = (props: I_InstallationFormProps) => { const InstallationForm = (props: I_InstallationFormProps) => {
const { values, id, hasMoveButton } = props; const { values, additionalButtons, handleSubmit } = props;
const [open, setOpen] = useState(false); const [open, setOpen] = useState(false);
const { fetchData } = useContext(InstallationsContext); const { fetchData } = useContext(InstallationsContext);
@ -53,22 +57,18 @@ const InstallationForm = (props: I_InstallationFormProps) => {
const formik = useFormik({ const formik = useFormik({
initialValues: { initialValues: {
name: values.name, name: values.region ? values.name : "",
region: values.region, region: values.region,
location: values.location, location: values.location,
country: values.country, country: values.country,
orderNumbers: values.orderNumbers, orderNumbers: values.orderNumbers,
}, },
onSubmit: (formikValues) => { onSubmit: (formikValues) => {
axiosConfig handleSubmit(values, formikValues)
.put("/UpdateInstallation", {
...formikValues,
id,
})
.then(() => { .then(() => {
setOpen(true);
fetchData(); fetchData();
}); })
.catch((err) => {});
}, },
validationSchema, validationSchema,
}); });
@ -152,7 +152,10 @@ const InstallationForm = (props: I_InstallationFormProps) => {
<form onSubmit={formik.handleSubmit}> <form onSubmit={formik.handleSubmit}>
<IePropertyGrid rows={rows} /> <IePropertyGrid rows={rows} />
<Grid container justifyContent="flex-end" sx={{ pt: 1 }}> <Grid container justifyContent="flex-end" sx={{ pt: 1 }}>
{hasMoveButton && !readOnly && <MoveDialog values={values} />} {/*{hasMoveButton && !readOnly && <MoveDialog values={values} />}*/}
{!readOnly &&
additionalButtons &&
additionalButtons.map((button) => button)}
{!readOnly && ( {!readOnly && (
<InnovenergyButton id="installation-form-submit-button" type="submit"> <InnovenergyButton id="installation-form-submit-button" type="submit">
<FormattedMessage <FormattedMessage

View File

@ -15,6 +15,7 @@ import { Fragment, useContext, useEffect } from "react";
import { I_Installation } from "../../util/types"; import { I_Installation } from "../../util/types";
import { InstallationsContext } from "../Context/InstallationsContextProvider"; import { InstallationsContext } from "../Context/InstallationsContextProvider";
import { colors } from "../.."; import { colors } from "../..";
import { FormattedMessage } from "react-intl";
interface InstallationListProps { interface InstallationListProps {
searchQuery: string; searchQuery: string;
@ -65,7 +66,7 @@ const InstallationList = (props: InstallationListProps) => {
<CircularProgress sx={{ m: 6, color: theme.palette.secondary.main }} /> <CircularProgress sx={{ m: 6, color: theme.palette.secondary.main }} />
</Grid> </Grid>
); );
} else if (data && data.length) { } else if (data && data.length > 0) {
return ( return (
<List <List
sx={{ sx={{
@ -90,7 +91,10 @@ const InstallationList = (props: InstallationListProps) => {
<Link <Link
id={"installation-list-link-" + installation.id} id={"installation-list-link-" + installation.id}
to={ to={
getPathWithoutId(routeMatch?.pattern?.path) + installation.id routes.installations +
routes.list +
routes.installation +
installation.id
} }
style={{ textDecoration: "none", color: colors.black }} style={{ textDecoration: "none", color: colors.black }}
> >
@ -131,6 +135,12 @@ const InstallationList = (props: InstallationListProps) => {
{error.message} {error.message}
</Alert> </Alert>
); );
} else if (data.length === 0) {
return (
<Alert severity="info" sx={{ mt: 1 }}>
<FormattedMessage id="noData" defaultMessage="No data found" />
</Alert>
);
} }
return null; return null;
}; };

View File

@ -11,22 +11,8 @@ import Installation from "./Installation";
import CheckboxTree from "./Log/CheckboxTree"; import CheckboxTree from "./Log/CheckboxTree";
import LogContextProvider from "../Context/LogContextProvider"; import LogContextProvider from "../Context/LogContextProvider";
import useRouteMatch from "../../hooks/useRouteMatch"; import useRouteMatch from "../../hooks/useRouteMatch";
import { Background } from "reactflow";
import { red } from "@mui/material/colors";
import S3CredentialsContextProvider, {
S3CredentialsContext,
} from "../Context/S3CredentialsContextProvider";
import { UnixTime } from "../../dataCache/time";
import { FetchResult } from "../../dataCache/dataCache";
import { DataRecord } from "../../dataCache/data";
import { S3Access } from "../../dataCache/S3/S3Access";
import { parseCsv } from "../../util/graph.util";
import { useParams } from "react-router-dom";
import { useContext, useEffect, useState } from "react";
import axiosConfig from "../../config/axiosConfig";
import { I_Installation } from "../../util/types";
import { AxiosError } from "axios/index";
import useInstallation from "../../hooks/useInstallation"; import useInstallation from "../../hooks/useInstallation";
import { useEffect } from "react";
const Installations = () => { const Installations = () => {
const routeMatch = useRouteMatch([ const routeMatch = useRouteMatch([

View File

@ -18,6 +18,7 @@ const LiveView = () => {
borderBottomLeftRadius: 4, borderBottomLeftRadius: 4,
borderBottomRightRadius: 4, borderBottomRightRadius: 4,
borderColor: theme.palette.text.disabled, borderColor: theme.palette.text.disabled,
overflowX: "auto",
}} }}
> >
<TopologyView /> <TopologyView />

View File

@ -8,6 +8,7 @@ import useRouteMatch from "../../../hooks/useRouteMatch";
import routes from "../../../routes.json"; import routes from "../../../routes.json";
import React from "react"; import React from "react";
import { colors } from "index"; import { colors } from "index";
import InnovEnergyTreeItem from "../../Layout/InnovEnergyTreeItem";
export interface ToggleElement { export interface ToggleElement {
[key: string]: boolean; [key: string]: boolean;
@ -54,7 +55,7 @@ const CheckboxTree = () => {
const checked = checkedToggles.find((toggle) => element.id === toggle); const checked = checkedToggles.find((toggle) => element.id === toggle);
const splitName = element.name.split("/"); const splitName = element.name.split("/");
return ( return (
<TreeItem <InnovEnergyTreeItem
id={"checkbox-tree-" + element.name} id={"checkbox-tree-" + element.name}
key={element.name} key={element.name}
nodeId={element.name} nodeId={element.name}
@ -74,16 +75,9 @@ const CheckboxTree = () => {
{splitName[splitName.length - 1]} {splitName[splitName.length - 1]}
</> </>
} }
sx={{
"& .MuiTreeItem-content": {
paddingY: "5px",
minHeight: "52px",
},
bgcolor: colors.greyDark,
}}
> >
{getNodes(element)} {getNodes(element)}
</TreeItem> </InnovEnergyTreeItem>
); );
}); });
}; };

View File

@ -28,8 +28,9 @@ const NUMBER_OF_NODES = 100;
const ScalarGraph = () => { const ScalarGraph = () => {
const timeRange = createTimes( const timeRange = createTimes(
UnixTime.now() /* .fromTicks(1682085650) */ UnixTime.now()
.rangeBefore(TimeSpan.fromDays(10)), /* .fromTicks(1682085650) */
.rangeBefore(TimeSpan.fromDays(1)),
NUMBER_OF_NODES NUMBER_OF_NODES
); );
const [timeSeries, setTimeSeries] = useState<RecordSeries>([]); const [timeSeries, setTimeSeries] = useState<RecordSeries>([]);
@ -56,6 +57,8 @@ const ScalarGraph = () => {
setTimeSeries(timeSeries); setTimeSeries(timeSeries);
const toggleValues = timeSeries.find((timeStamp) => timeStamp.value); const toggleValues = timeSeries.find((timeStamp) => timeStamp.value);
console.log("toggles", timeSeries, toggleValues);
if (toggles === null && toggleValues && toggleValues.value) { if (toggles === null && toggleValues && toggleValues.value) {
const treeElements = getTreeElements(toggleValues.value); const treeElements = getTreeElements(toggleValues.value);
setToggles(treeElements); setToggles(treeElements);
@ -123,7 +126,6 @@ const ScalarGraph = () => {
const handleRelayout = useCallback( const handleRelayout = useCallback(
(params: PlotRelayoutEvent) => { (params: PlotRelayoutEvent) => {
console.log("relayout");
const xaxisRange0 = params["xaxis.range[0]"]; const xaxisRange0 = params["xaxis.range[0]"];
const xaxisRange1 = params["xaxis.range[1]"]; const xaxisRange1 = params["xaxis.range[1]"];
if (xaxisRange0 && xaxisRange1) { if (xaxisRange0 && xaxisRange1) {
@ -138,6 +140,7 @@ const ScalarGraph = () => {
const theme = useTheme(); const theme = useTheme();
const renderGraphs = () => { const renderGraphs = () => {
console.log("toggles", toggles);
if (checkedToggles.length > 0) { if (checkedToggles.length > 0) {
const coordinateTimeSeries = transformToGraphData(timeSeries); const coordinateTimeSeries = transformToGraphData(timeSeries);
const visibleGraphs = Object.keys(coordinateTimeSeries).filter((path) => { const visibleGraphs = Object.keys(coordinateTimeSeries).filter((path) => {
@ -209,6 +212,16 @@ const ScalarGraph = () => {
</div> </div>
); );
} }
} else if (toggles === null) {
return (
<Alert sx={{ mt: 2 }} severity="error">
<FormattedMessage
id="noGraphData"
defaultMessage="Couldn't find any graph data"
/>
</Alert>
);
}
return ( return (
<Alert sx={{ mt: 2 }} severity="info"> <Alert sx={{ mt: 2 }} severity="info">
<FormattedMessage <FormattedMessage
@ -217,12 +230,6 @@ const ScalarGraph = () => {
/> />
</Alert> </Alert>
); );
}
return (
<Alert sx={{ mt: 2 }} severity="error">
<FormattedMessage id="error" defaultMessage="There was an error" />
</Alert>
);
}; };
return <>{renderGraphs()}</>; return <>{renderGraphs()}</>;

View File

@ -24,13 +24,16 @@ export const BOX_SIZE = 75;
const TopologyBox = (props: TopologyBoxProps) => { const TopologyBox = (props: TopologyBoxProps) => {
const { titleColor, boxColor } = getBoxColor(props.title); const { titleColor, boxColor } = getBoxColor(props.title);
return ( return (
<div style={{ height: BOX_SIZE, width: BOX_SIZE }}>
<Box <Box
sx={{ sx={{
visibility: props.title ? "visible" : "hidden", visibility: props.title ? "visible" : "hidden",
height: BOX_SIZE + "px", height: BOX_SIZE + "px",
width: BOX_SIZE + "px", width: BOX_SIZE + "px",
maxHeight: BOX_SIZE + "px",
borderRadius: "4px", borderRadius: "4px",
color: "white", color: "white",
overflow: "hidden",
}} }}
> >
<p <p
@ -43,6 +46,9 @@ const TopologyBox = (props: TopologyBoxProps) => {
borderTopRightRadius: "4px", borderTopRightRadius: "4px",
display: "flex", display: "flex",
justifyContent: "center", justifyContent: "center",
textOverflow: "ellipsis",
whiteSpace: "nowrap",
overflow: "hidden",
}} }}
> >
{props.title} {props.title}
@ -53,13 +59,12 @@ const TopologyBox = (props: TopologyBoxProps) => {
borderBottomLeftRadius: "4px", borderBottomLeftRadius: "4px",
borderBottomRightRadius: "4px", borderBottomRightRadius: "4px",
padding: "5px", padding: "5px",
height: "51px", height: BOX_SIZE - 34,
}} }}
> >
{props.data && ( {props.data && (
<> <>
{props.data.values.map((boxData, index) => { {props.data.values.map((boxData, index) => {
console.log("boxData", boxData);
return ( return (
<p <p
key={props.title + "-" + index} key={props.title + "-" + index}
@ -80,6 +85,7 @@ const TopologyBox = (props: TopologyBoxProps) => {
)} )}
</div> </div>
</Box> </Box>
</div>
); );
}; };

View File

@ -9,6 +9,7 @@ type TopologyColumnProps = {
centerConnection?: TopologyFlowProps; centerConnection?: TopologyFlowProps;
bottomBox?: TopologyBoxProps; bottomBox?: TopologyBoxProps;
bottomConnection?: TopologyFlowProps; bottomConnection?: TopologyFlowProps;
isLastColumn?: boolean;
}; };
const TopologyColumn = (props: TopologyColumnProps) => { const TopologyColumn = (props: TopologyColumnProps) => {
@ -37,6 +38,7 @@ const TopologyColumn = (props: TopologyColumnProps) => {
/> />
<TopologyBox {...props.bottomBox} /> <TopologyBox {...props.bottomBox} />
</div> </div>
{!props.isLastColumn && (
<div> <div>
<TopologyFlow <TopologyFlow
{...props.centerConnection} {...props.centerConnection}
@ -44,6 +46,7 @@ const TopologyColumn = (props: TopologyColumnProps) => {
hidden={!props.centerBox} hidden={!props.centerBox}
/> />
</div> </div>
)}
</Box> </Box>
); );
}; };

View File

@ -9,7 +9,9 @@ export type TopologyFlowProps = {
hidden?: boolean; hidden?: boolean;
}; };
const TopologyFlow = (props: TopologyFlowProps) => { const TopologyFlow = (props: TopologyFlowProps) => {
const length = Math.abs((props.amount ?? 1) * BOX_SIZE); const length = Math.abs(
((props.amount && props.amount < 0.1 ? 0.1 : props.amount) ?? 0) * BOX_SIZE
);
const values = props.data?.values; const values = props.data?.values;
return ( return (
<div <div
@ -45,7 +47,8 @@ const TopologyFlow = (props: TopologyFlowProps) => {
{values {values
?.filter((boxData) => (boxData.value as number) !== 0) ?.filter((boxData) => (boxData.value as number) !== 0)
.map( .map(
(boxData) => `${Math.round(boxData.value as number)} ${boxData.unit}` (boxData) =>
`${Math.round(boxData.value as number)} ${boxData.unit}`
)} )}
</p> </p>
<div <div

View File

@ -15,6 +15,7 @@ import { S3CredentialsContext } from "../../Context/S3CredentialsContextProvider
const TopologyView = () => { const TopologyView = () => {
const [values, setValues] = useState<TopologyValues | null>(null); const [values, setValues] = useState<TopologyValues | null>(null);
const { fetchData } = useContext(S3CredentialsContext); const { fetchData } = useContext(S3CredentialsContext);
const theme = useTheme();
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
@ -37,8 +38,7 @@ const TopologyView = () => {
}, 2000); }, 2000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, []); }, [fetchData]);
const theme = useTheme();
if (values) { if (values) {
const highestConnectionValue = getHighestConnectionValue(values); const highestConnectionValue = getHighestConnectionValue(values);
@ -47,19 +47,13 @@ const TopologyView = () => {
sx={{ sx={{
display: "flex", display: "flex",
flexDirection: "row", flexDirection: "row",
paddingTop: 3, padding: 3,
paddingBottom: 3,
bgcolor: "white", bgcolor: "white",
px: 3 / 8,
border: 2,
borderTop: 0,
borderBottomLeftRadius: 4,
borderBottomRightRadius: 4,
borderColor: theme.palette.text.disabled,
borderTopColor: "white",
marginTop: 0.05, marginTop: 0.05,
fontFamily: `"Ubuntu", sans-serif`, fontFamily: `"Ubuntu", sans-serif`,
fontSize: "12px", fontSize: "12px",
justifyContent: "center",
width: "fit-content",
}} }}
> >
<div> <div>
@ -217,6 +211,7 @@ const TopologyView = () => {
title: "Battery", title: "Battery",
data: values.battery, data: values.battery,
}} }}
isLastColumn
/> />
</div> </div>
</Box> </Box>

View File

@ -1,57 +0,0 @@
import { useState, useCallback } from "react";
import ReactFlow, {
addEdge,
FitViewOptions,
applyNodeChanges,
applyEdgeChanges,
Node,
Edge,
NodeChange,
EdgeChange,
Connection,
} from "reactflow";
const initialNodes: Node[] = [
{ id: "1", data: { label: "Node 1" }, position: { x: 5, y: 5 } },
{ id: "2", data: { label: "Node 2" }, position: { x: 5, y: 100 } },
];
const initialEdges: Edge[] = [{ id: "e1-2", source: "1", target: "2" }];
const fitViewOptions: FitViewOptions = {
padding: 0.2,
};
function Flow() {
const [nodes, setNodes] = useState<Node[]>(initialNodes);
const [edges, setEdges] = useState<Edge[]>(initialEdges);
const onNodesChange = useCallback(
(changes: NodeChange[]) =>
setNodes((nds) => applyNodeChanges(changes, nds)),
[setNodes]
);
const onEdgesChange = useCallback(
(changes: EdgeChange[]) =>
setEdges((eds) => applyEdgeChanges(changes, eds)),
[setEdges]
);
const onConnect = useCallback(
(connection: Connection) => setEdges((eds) => addEdge(connection, eds)),
[setEdges]
);
return (
<ReactFlow
nodes={nodes}
edges={edges}
onNodesChange={onNodesChange}
onEdgesChange={onEdgesChange}
onConnect={onConnect}
fitView
fitViewOptions={fitViewOptions}
/>
);
}
export default Flow;

View File

@ -0,0 +1,40 @@
import { TreeItem, TreeItemProps } from "@mui/lab";
import { colors } from "../../index";
import { useTheme } from "@mui/material";
const InnovEnergyTreeItem = (props: TreeItemProps) => {
const theme = useTheme();
return (
<TreeItem
id={props.id}
nodeId={props.nodeId}
label={props.label}
onClick={() => props.onClick}
sx={{
".MuiTreeItem-content": {
py: "10px",
borderRadius: "4px",
width: "inherit",
},
".Mui-selected": {
backgroundColor: theme.palette.secondary.main + "!important",
borderRadius: "4px",
},
".MuiTreeItem-content:hover": {
borderRadius: "4px",
backgroundColor: theme.palette.secondary.light + "!important",
},
".MuiTreeItem-content.Mui-selected:hover": {
borderRadius: "4px",
backgroundColor: theme.palette.secondary.main + "!important",
},
borderRadius: "4px",
bgcolor: colors.greyDark,
}}
>
{props.children}
</TreeItem>
);
};
export default InnovEnergyTreeItem;

View File

@ -1,12 +1,11 @@
import { Button, SxProps, Theme, colors, useTheme } from "@mui/material"; import { Button, SxProps, Theme, colors, useTheme } from "@mui/material";
import { DAY_MARGIN } from "@mui/x-date-pickers/internals";
import { ReactNode } from "react"; import { ReactNode } from "react";
import { Background } from "reactflow";
interface I_InnovenergyButtonProps { interface I_InnovenergyButtonProps {
children?: ReactNode; children?: ReactNode;
type?: "button" | "submit" | "reset" | undefined; type?: "button" | "submit" | "reset" | undefined;
onClick?: React.MouseEventHandler<HTMLButtonElement>; onClick?: React.MouseEventHandler<HTMLButtonElement>;
onKeyDown?: React.KeyboardEventHandler<HTMLButtonElement>;
sx?: SxProps<Theme> | undefined; sx?: SxProps<Theme> | undefined;
id?: string; id?: string;
} }
@ -19,13 +18,14 @@ const InnovenergyButton = (props: I_InnovenergyButtonProps) => {
variant="contained" variant="contained"
type={props.type} type={props.type}
onClick={props.onClick} onClick={props.onClick}
sx={{...props.sx, onKeyDown={props.onKeyDown}
sx={{
...props.sx,
textTransform: "none", textTransform: "none",
bgcolor: theme.palette.secondary.main, bgcolor: theme.palette.secondary.main,
":hover": { ":hover": {
bgcolor: theme.palette.secondary.dark, bgcolor: theme.palette.secondary.dark,
} },
}} }}
> >
{props.children} {props.children}
@ -33,6 +33,4 @@ const InnovenergyButton = (props: I_InnovenergyButtonProps) => {
); );
}; };
export default InnovenergyButton; export default InnovenergyButton;

View File

@ -1,5 +1,4 @@
import { styled, SxProps, Tab, Tabs, Theme, useTheme } from "@mui/material"; import { Tab, useTheme } from "@mui/material";
import { TabProps } from "@mui/material/Tab/Tab";
import { colors } from "index"; import { colors } from "index";
const InnovenergyTab = (props: any) => { const InnovenergyTab = (props: any) => {
@ -21,43 +20,8 @@ const InnovenergyTab = (props: any) => {
borderTopRightRadius: "0.3rem", borderTopRightRadius: "0.3rem",
padding: ".5rem 1rem", padding: ".5rem 1rem",
textDecoration: "none", textDecoration: "none",
transition: `color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out`,
"&.Mui-selected": {
backgroundColor: "#eaecf1",
color: colors.black,
borderColor: theme.palette.text.disabled,
marginTop: "1px",
bottom: -1,
...(props.sx ? props.sx["&.Mui-selected"] : {}),
},
}}
/>
);
};
const InnovenergyTabBorder = (props: any) => {
const theme = useTheme();
return (
<Tab
{...props}
disableRipple
sx={{
textTransform: "none",
...props.sx,
bottom: 0,
fontWeight: theme.typography.fontWeightRegular,
fontSize: theme.typography.pxToRem(14),
marginRight: theme.spacing(1),
background: "0 0",
border: "2px solid",
borderColor: "#c0c6d0",
bgcolor: theme.palette.primary.light,
borderTopLeftRadius: "0.3rem",
borderTopRightRadius: "0.3rem",
padding: ".5rem 1rem",
textDecoration: "none",
transition: `color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out`,
"&.Mui-selected": { "&.Mui-selected": {
backgroundColor: theme.palette.primary.light,
color: colors.black, color: colors.black,
borderColor: theme.palette.text.disabled, borderColor: theme.palette.text.disabled,
marginTop: "1px", marginTop: "1px",

View File

@ -24,7 +24,7 @@ const InnovenergyTabs = (props: AntTabsProps) => {
}, },
"&.Mui-selected": { "&.Mui-selected": {
color: colors.black, color: colors.black,
backgroundColor: "#eaecf1", backgroundColor: theme.palette.primary.light,
borderColor: `#90A7c5 #90A7c5 #fff`, borderColor: `#90A7c5 #90A7c5 #fff`,
}, },
"& .MuiTabs-indicator": { "& .MuiTabs-indicator": {

View File

@ -15,13 +15,13 @@ const LanguageSelect = (props: LanguageSelectProps) => {
label="Age" label="Age"
onChange={(e) => props.setLanguage(e.target.value)} onChange={(e) => props.setLanguage(e.target.value)}
> >
<MenuItem id="english-menu-item" value="EN"> <MenuItem id="english-menu-item" value="en">
<FormattedMessage id="english" defaultMessage="English" /> <FormattedMessage id="english" defaultMessage="English" />
</MenuItem> </MenuItem>
<MenuItem id="german-menu-item" value="DE"> <MenuItem id="german-menu-item" value="de">
<FormattedMessage id="german" defaultMessage="German" /> <FormattedMessage id="german" defaultMessage="German" />
</MenuItem> </MenuItem>
<MenuItem id="german-menu-item" value="FR"> <MenuItem id="german-menu-item" value="fr">
<FormattedMessage id="french" defaultMessage="French" /> <FormattedMessage id="french" defaultMessage="French" />
</MenuItem> </MenuItem>
</Select> </Select>

View File

@ -29,7 +29,6 @@ const NavigationTabs = () => {
sx={{ sx={{
bgcolor: theme.palette.primary.main, bgcolor: theme.palette.primary.main,
"&.Mui-selected": { "&.Mui-selected": {
backgroundColor: "#eaecf1",
borderColor: theme.palette.text.disabled, borderColor: theme.palette.text.disabled,
borderBottom: 0, borderBottom: 0,
mb: -1 / 8, mb: -1 / 8,
@ -50,7 +49,6 @@ const NavigationTabs = () => {
sx={{ sx={{
bgcolor: theme.palette.primary.main, bgcolor: theme.palette.primary.main,
"&.Mui-selected": { "&.Mui-selected": {
backgroundColor: "#eaecf1",
borderColor: theme.palette.text.disabled, borderColor: theme.palette.text.disabled,
borderBottom: 0, borderBottom: 0,
mb: -1 / 8, mb: -1 / 8,

View File

@ -1,7 +1,7 @@
import List from "@mui/material/List"; import List from "@mui/material/List";
import ListItemButton from "@mui/material/ListItemButton"; import ListItemButton from "@mui/material/ListItemButton";
import ListItemText from "@mui/material/ListItemText"; import ListItemText from "@mui/material/ListItemText";
import { Divider, useTheme } from "@mui/material"; import { Alert, Divider, useTheme } from "@mui/material";
import { Link } from "react-router-dom"; import { Link } from "react-router-dom";
import useRouteMatch from "../../hooks/useRouteMatch"; import useRouteMatch from "../../hooks/useRouteMatch";
import routes from "../../routes.json"; import routes from "../../routes.json";
@ -9,6 +9,7 @@ import { Fragment, useContext } from "react";
import { UsersContext } from "../Context/UsersContextProvider"; import { UsersContext } from "../Context/UsersContextProvider";
import { I_User } from "../../util/user.util"; import { I_User } from "../../util/user.util";
import { colors } from "../.."; import { colors } from "../..";
import { FormattedMessage } from "react-intl";
const getPathWithoutId = (path?: string) => { const getPathWithoutId = (path?: string) => {
if (path) { if (path) {
@ -38,7 +39,7 @@ const UserList = (props: UserListProps) => {
const routeMatch = useRouteMatch([routes.users + routes.user + ":id"]); const routeMatch = useRouteMatch([routes.users + routes.user + ":id"]);
const theme = useTheme(); const theme = useTheme();
if (filteredData && filteredData.length) { if (filteredData && filteredData.length > 0) {
return ( return (
<List <List
sx={{ sx={{
@ -87,6 +88,12 @@ const UserList = (props: UserListProps) => {
})} })}
</List> </List>
); );
} else if (filteredData?.length === 0) {
return (
<Alert severity="info" sx={{ mt: 1 }}>
<FormattedMessage id="noData" defaultMessage="No data found" />
</Alert>
);
} }
return null; return null;
}; };

View File

@ -2,44 +2,42 @@ import {sha1Hmac} from "./Sha1";
import { Utf8 } from "./Utf8"; import { Utf8 } from "./Utf8";
import { toBase64 } from "./UInt8Utils"; import { toBase64 } from "./UInt8Utils";
export class S3Access export class S3Access {
{ constructor(
constructor
(
readonly bucket: string, readonly bucket: string,
readonly region: string, readonly region: string,
readonly provider: string, readonly provider: string,
readonly key: string, readonly key: string,
readonly secret: string readonly secret: string
) ) {}
{}
get host() : string { return `${this.bucket}.${this.region}.${this.provider}` } get host(): string {
get url() : string { return `https://${this.host}` } return `${this.region}.${this.provider}`;
}
public get(s3Path : string): Promise<Response> get url(): string {
{ return `https://${this.host}`;
}
public get(s3Path: string): Promise<Response> {
const method = "GET"; const method = "GET";
const auth = this.createAuthorizationHeader(method, s3Path, ""); const auth = this.createAuthorizationHeader(method, s3Path, "");
const url = this.url + "/" + s3Path const url = this.url + "/" + this.bucket + "/" + s3Path;
const headers = {"Host": this.host, "Authorization": auth}; const headers = { Host: this.host, Authorization: auth };
try try {
{ return fetch(url, { method: method, mode: "cors", headers: headers });
return fetch(url, {method: method, mode: "cors", headers: headers}) } catch {
} return Promise.reject();
catch
{
return Promise.reject()
} }
} }
private createAuthorizationHeader(method: string, private createAuthorizationHeader(
method: string,
s3Path: string, s3Path: string,
date: string) date: string
{ ) {
return createAuthorizationHeader return createAuthorizationHeader(
(
method, method,
this.bucket, this.bucket,
s3Path, s3Path,
@ -50,15 +48,16 @@ export class S3Access
} }
} }
function createAuthorizationHeader(method: string, function createAuthorizationHeader(
method: string,
bucket: string, bucket: string,
s3Path: string, s3Path: string,
date: string, date: string,
s3Key: string, s3Key: string,
s3Secret: string, s3Secret: string,
contentType: string = "", contentType: string = "",
md5Hash: string = "") md5Hash: string = ""
{ ) {
// StringToSign = HTTP-Verb + "\n" + // StringToSign = HTTP-Verb + "\n" +
// Content-MD5 + "\n" + // Content-MD5 + "\n" +
// Content-Type + "\n" + // Content-Type + "\n" +
@ -66,12 +65,14 @@ function createAuthorizationHeader(method: string,
// CanonicalizedAmzHeaders + // CanonicalizedAmzHeaders +
// CanonicalizedResource; // CanonicalizedResource;
const payload = Utf8.encode(`${method}\n${md5Hash}\n${contentType}\n${date}\n/${bucket}/${s3Path}`) const payload = Utf8.encode(
`${method}\n${md5Hash}\n${contentType}\n${date}\n/${bucket}/${s3Path}`
);
//console.log(`${method}\n${md5Hash}\n${contentType}\n${date}\n/${bucket}/${s3Path}`) //console.log(`${method}\n${md5Hash}\n${contentType}\n${date}\n/${bucket}/${s3Path}`)
const secret = Utf8.encode(s3Secret) const secret = Utf8.encode(s3Secret);
const signature = toBase64(sha1Hmac(payload, secret)); const signature = toBase64(sha1Hmac(payload, secret));
return `AWS ${s3Key}:${signature}` return `AWS ${s3Key}:${signature}`;
} }

View File

@ -19,7 +19,7 @@ export const colors = {
//change this color in index.css too //change this color in index.css too
greyDark: "#d1d7de", greyDark: "#d1d7de",
greyLight: "#e1e4e7", greyLight: "#eaecf1",
orangeSelected: "#f7b34d", orangeSelected: "#f7b34d",
orangeHover: "#fad399", orangeHover: "#fad399",

View File

@ -197,12 +197,6 @@ export const getAmount = (
highestConnectionValue: number, highestConnectionValue: number,
values: BoxDataValue[] values: BoxDataValue[]
) => { ) => {
console.log(
"getamount",
Math.abs(values[0].value as number) / highestConnectionValue,
Math.abs(values[0].value as number),
highestConnectionValue
);
return Math.abs(values[0].value as number) / highestConnectionValue; return Math.abs(values[0].value as number) / highestConnectionValue;
}; };

View File

@ -1,142 +0,0 @@
import { styled, Tab, Tabs } from "@mui/material";
import { colors } from "index";
export const StyledTab = styled((props: any) => (
<Tab disableRipple {...props} />
))(({ theme }) => ({
bottom: -1,
fontWeight: theme.typography.fontWeightRegular,
fontSize: theme.typography.pxToRem(14),
marginRight: theme.spacing(1),
background: "0 0",
border: "1px solid transparent",
borderTopLeftRadius: "0.3rem",
borderTopRightRadius: "0.3rem",
padding: ".5rem 1rem",
textDecoration: "none",
transition: `color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out`,
"&.Mui-selected": {
color: colors.black,
backgroundColor: "#F2F4F8",
borderColor: "#90A7c5 #90A7c5 #F2F4F8",
marginTop: "1px",
bottom: -1,
},
}));
export const StyledTabBlue = styled((props: any) => (
<Tab disableRipple {...props} />
))(({ theme }) => ({
bottom: -1,
fontWeight: theme.typography.fontWeightRegular,
fontSize: theme.typography.pxToRem(14),
marginRight: theme.spacing(1),
background: "0 0",
border: "1px solid transparent",
borderBottom: "0px",
borderTopLeftRadius: "0.3rem",
borderTopRightRadius: "0.3rem",
padding: ".5rem 1rem",
textDecoration: "none",
transition: `color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out`,
"&.Mui-selected": {
bottom: -1,
color: colors.black,
backgroundColor: "#CCD6E4",
borderColor: "#90A7c5 #90A7c5 #CCD6E4",
marginTop: "1px",
},
}));
export const StyledTabBig = styled((props: any) => (
<Tab disableRipple {...props} />
))(({ theme }) => ({
bottom: -2,
fontWeight: theme.typography.fontWeightRegular,
fontSize: theme.typography.pxToRem(14),
marginRight: theme.spacing(1),
background: "0 0",
border: "2px solid transparent",
borderTopLeftRadius: "0.3rem",
borderTopRightRadius: "0.3rem",
padding: ".5rem 1rem",
textDecoration: "none",
transition: `color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out`,
"&.Mui-selected": {
color: colors.black,
backgroundColor: "#F2F4F8",
borderColor: "#90A7c5 #90A7c5 #F2F4F8",
marginTop: "1px",
bottom: -2,
},
}));
export const StyledTabWhite = styled((props: any) => (
<Tab disableRipple {...props} />
))(({ theme }) => ({
bottom: -1,
fontWeight: theme.typography.fontWeightRegular,
fontSize: theme.typography.pxToRem(14),
marginRight: theme.spacing(1),
background: "0 0",
border: "1px solid transparent",
borderTopLeftRadius: "0.3rem",
borderTopRightRadius: "0.3rem",
padding: ".5rem 1rem",
textDecoration: "none",
transition: `color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out`,
"&.Mui-selected": {
color: colors.black,
backgroundColor: "White",
borderColor: "#90A7c5 #90A7c5 White",
marginTop: "1px",
bottom: -1,
},
}));
export const AntTabsBig = styled(Tabs)({
borderBottom: "2px solid #90A7c5",
overflow: "visible!important",
"& div.MuiTabs-scroller": {
overflow: "visible!important",
},
"&.Mui-selected": {
color: colors.black,
backgroundColor: "red",
borderColor: `#90A7c5 #90A7c5 #fff`,
},
"& .MuiTabs-indicator": {
display: "flex",
justifyContent: "center",
backgroundColor: "transparent",
},
"&.MuiTabs-root": {
width: "100%",
},
});
export const AntTabs = styled(Tabs)({
borderBottom: "1px solid #90A7c5",
overflow: "visible!important",
"& div.MuiTabs-scroller": {
overflow: "visible!important",
},
"&.Mui-selected": {
color: colors.black,
backgroundColor: "red",
borderColor: `#90A7c5 #90A7c5 #fff`,
},
"& .MuiTabs-indicator": {
display: "flex",
justifyContent: "center",
backgroundColor: "transparent",
},
"&.MuiTabs-root": {
width: "100%",
},
});

View File

@ -23,6 +23,8 @@ export interface I_Installation extends S3Credentials {
name: string; name: string;
information: string; information: string;
parentId: number; parentId: number;
s3WriteKey: string;
s3WriteSecret: string;
} }
export interface I_Folder { export interface I_Folder {