change style for multiple pages, change how s3 credentials are create

This commit is contained in:
Sina Blattmann 2023-07-13 09:36:20 +02:00
parent 72ced4be3e
commit 3e099a68cb
28 changed files with 481 additions and 312 deletions

View File

@ -1,20 +1,20 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8"/>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
<meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="theme-color" content="#000000" /> <meta name="theme-color" content="#000000"/>
<meta <meta
name="description" name="description"
content="Web site created using create-react-app" content="Web site created using create-react-app"
/> />
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" /> <link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png"/>
<!-- <!--
manifest.json provides metadata used when your web app is installed on a manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/ user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
--> -->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" /> <link rel="manifest" href="%PUBLIC_URL%/manifest.json"/>
<!-- <!--
Notice the use of %PUBLIC_URL% in the tags above. Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build. It will be replaced with the URL of the `public` folder during the build.
@ -25,11 +25,11 @@
Learn how to configure a non-root public URL by running `npm run build`. Learn how to configure a non-root public URL by running `npm run build`.
--> -->
<title>Innovenergy</title> <title>Innovenergy</title>
</head> </head>
<body> <body>
<noscript>You need to enable JavaScript to run this app.</noscript> <noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div> <div id="root" style="height: 100vh; display: flex; flex-direction: column;"></div>
<!-- <!--
This HTML file is a template. This HTML file is a template.
If you open it directly in the browser, you will see an empty page. If you open it directly in the browser, you will see an empty page.
@ -38,6 +38,6 @@
To begin the development, run `npm start` or `yarn start`. To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`. To create a production bundle, use `npm run build` or `yarn build`.
--> -->
</body> </body>
</html> </html>

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 } from "@mui/material"; import { Container, Grid, colors, 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";
@ -12,11 +12,11 @@ import fr from "./lang/fr.json";
import LanguageSelect from "./components/Layout/LanguageSelect"; import LanguageSelect from "./components/Layout/LanguageSelect";
import LogoutButton from "./components/Layout/LogoutButton"; import LogoutButton from "./components/Layout/LogoutButton";
import Users from "./components/Users/Users"; import Users from "./components/Users/Users";
import NavigationButtons from "./components/Layout/NavigationButtons"; import NavigationTabs from "./components/Layout/NavigationTabs";
import InstallationPage from "./components/Installations/InstallationPage"; 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/test.gif"; import innovenergyLogo from "./resources/innoveng_logo_on_orange.png";
const App = () => { const App = () => {
const { token, setToken, removeToken } = useToken(); const { token, setToken, removeToken } = useToken();
@ -48,9 +48,9 @@ const App = () => {
locale={language} locale={language}
defaultLocale="EN" defaultLocale="EN"
> >
<Container maxWidth="xl" sx={{ marginTop: 2, height: "100vh" }}> <Container maxWidth="xl" sx={{ pt: 2 }}>
<Grid container maxHeight="20vh"> <Grid container>
<Grid item xs={3} container justifyContent="flex-start"> <Grid item xs={3} container justifyContent="flex-start" mb={2}>
<img src={innovenergyLogo} alt="innovenergy logo" height="100" /> <img src={innovenergyLogo} alt="innovenergy logo" height="100" />
</Grid> </Grid>
<Grid <Grid
@ -64,11 +64,22 @@ const App = () => {
<LogoutButton removeToken={removeToken} /> <LogoutButton removeToken={removeToken} />
</Grid> </Grid>
</Grid> </Grid>
<Grid container spacing={2} mt={2}> </Container>
<Grid item xs={3} container justifyContent="flex-start"> <Container maxWidth="xl" sx={{ pt: 2 }}>
<NavigationButtons /> <Grid spacing={2} width="100%">
<Grid item xs={3}>
<NavigationTabs />
</Grid> </Grid>
</Grid> </Grid>
</Container>
<Box
bgcolor="#eaecf1"
pt={3}
borderTop="2px solid"
borderColor=" #a8b0be"
flex="1 0 auto"
>
<Container maxWidth="xl">
<Routes> <Routes>
<Route <Route
path="*" path="*"
@ -83,6 +94,7 @@ const App = () => {
<Route path={routes.users + "*"} element={<Users />} /> <Route path={routes.users + "*"} element={<Users />} />
</Routes> </Routes>
</Container> </Container>
</Box>
</IntlProvider> </IntlProvider>
</BrowserRouter> </BrowserRouter>
); );

View File

@ -1,29 +1,89 @@
import { createContext, ReactNode, useState } from "react"; import { createContext, ReactNode, useState } from "react";
import { I_User } from "../../util/user.util"; import { I_User } from "../../util/user.util";
import { I_Installation, S3Credentials } from "../../util/types";
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";
interface InstallationContextProviderProps { interface S3CredentialsContextProviderProps {
s3Credentials?: I_User; s3Credentials?: S3Credentials;
setS3Credentials: (value: I_User) => void; saveS3Credentials: (credentials: S3Credentials, id: string) => void;
fetchData: (timestamp: UnixTime) => Promise<FetchResult<DataRecord>>;
} }
export const UserContext = createContext<InstallationContextProviderProps>({ export const S3CredentialsContext =
s3Credentials: {} as I_User, createContext<S3CredentialsContextProviderProps>({
setS3Credentials: () => {}, s3Credentials: {} as S3Credentials,
}); saveS3Credentials: () => {},
fetchData: () => ({} as Promise<FetchResult<DataRecord>>),
});
const UserContextProvider = ({ children }: { children: ReactNode }) => { const S3CredentialsContextProvider = ({
const [s3Credentials, setS3Credentials] = useState<I_User>(); children,
}: {
children: ReactNode;
}) => {
const [s3Credentials, setS3Credentials] = useState<S3Credentials>();
console.log("creds", s3Credentials);
const saveS3Credentials = (credentials: S3Credentials, id: string) => {
//const s3Bucket = id + "-3e5b3069-214a-43ee-8d85-57d72000c19d";
/* const s3Bucket = "saliomameiringen";
setS3Credentials({ s3Bucket, ...credentials });*/
setS3Credentials({
s3Region: "sos-ch-dk-2",
s3Provider: "exo.io",
s3Key: "EXO15c0bf710e158e9b83270f0a",
s3Secret: "Dd5jYSiZtt_Zt5Ba5mDmaiLCdASUaKLfduSKY-SU-lg",
s3Bucket: "saliomameiringen",
});
};
const fetchData = (timestamp: UnixTime): Promise<FetchResult<DataRecord>> => {
const s3Path = `${timestamp.ticks}.csv`;
if (s3Credentials && s3Credentials.s3Bucket) {
const s3Access = new S3Access(
s3Credentials.s3Bucket,
s3Credentials.s3Region,
s3Credentials.s3Provider,
s3Credentials.s3Key,
s3Credentials.s3Secret
);
return s3Access
.get(s3Path)
.then(async (r) => {
if (r.status === 404) {
return Promise.resolve(FetchResult.notAvailable);
} else if (r.status === 200) {
const text = await r.text();
return parseCsv(text);
} else {
console.error("unexpected status code");
return Promise.resolve(FetchResult.notAvailable);
}
})
.catch((e) => {
console.log("catch", e);
return Promise.resolve(FetchResult.tryLater);
});
}
return Promise.resolve(FetchResult.tryLater);
};
return ( return (
<UserContext.Provider <S3CredentialsContext.Provider
value={{ value={{
s3Credentials: s3Credentials, s3Credentials,
setS3Credentials: setS3Credentials, saveS3Credentials,
fetchData,
}} }}
> >
{children} {children}
</UserContext.Provider> </S3CredentialsContext.Provider>
); );
}; };
export default UserContextProvider; export default S3CredentialsContextProvider;

View File

@ -7,8 +7,27 @@ import GroupContextProvider from "../Context/GroupContextProvider";
import GroupTree from "./Tree/GroupTree"; import GroupTree from "./Tree/GroupTree";
import AccessManagement from "./AccessManagement/AccessManagement"; import AccessManagement from "./AccessManagement/AccessManagement";
import Installation from "../Installations/Installation"; import Installation from "../Installations/Installation";
import useRouteMatch from "../../hooks/useRouteMatch";
import useInstallation from "../../hooks/useInstallation";
import { useEffect } from "react";
const Groups = () => { const Groups = () => {
const routeMatch = useRouteMatch([
routes.installations + routes.tree + ":view/:id",
]);
const {
data: currentInstallation,
loading,
error,
getInstallation,
} = useInstallation();
const id = routeMatch?.params?.id;
useEffect(() => {
// TODO remove if
getInstallation(id);
}, [id]);
return ( return (
<GroupContextProvider> <GroupContextProvider>
<Grid container spacing={2}> <Grid container spacing={2}>
@ -25,7 +44,14 @@ const Groups = () => {
/> />
<Route <Route
path={routes.installation + ":id"} path={routes.installation + ":id"}
element={<Installation hasMoveButton />} element={
<Installation
hasMoveButton
error={error}
values={currentInstallation}
loading={loading}
/>
}
/> />
</Routes> </Routes>
</Grid> </Grid>

View File

@ -11,6 +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";
const GroupTree = () => { const GroupTree = () => {
const { setCurrentType, fetchData, data, loading } = useContext(GroupContext); const { setCurrentType, fetchData, data, loading } = useContext(GroupContext);
@ -54,7 +55,14 @@ const GroupTree = () => {
label={element.name} label={element.name}
onClick={() => setCurrentType(element.type)} onClick={() => setCurrentType(element.type)}
sx={{ sx={{
".MuiTreeItem-content": { paddingY: "5px" }, ".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, bgcolor: colors.greyDark,
borderRadius: 2, borderRadius: 2,
}} }}
@ -68,7 +76,7 @@ const GroupTree = () => {
if (loading) { if (loading) {
return ( return (
<Grid container justifyContent="center" width="100%"> <Grid container justifyContent="center" width="100%">
<CircularProgress sx={{color: theme.palette.secondary.main}} /> <CircularProgress sx={{ color: theme.palette.secondary.main }} />
</Grid> </Grid>
); );
} }

View File

@ -1,35 +1,24 @@
import { Alert, Box, CircularProgress, useTheme } from "@mui/material"; import { Alert, Box, CircularProgress, useTheme } from "@mui/material";
import { AxiosError } from "axios"; import { AxiosError } from "axios";
import { 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_Installation } from "../../util/types";
import InstallationForm from "./InstallationForm"; import InstallationForm from "./InstallationForm";
import { colors } from "../.."; import { colors } from "../..";
import { S3CredentialsContext } from "../Context/S3CredentialsContextProvider";
interface I_InstallationProps { interface I_InstallationProps {
loading?: boolean;
values?: I_Installation;
error?: AxiosError;
hasMoveButton?: boolean; hasMoveButton?: boolean;
} }
const Installation = (props: I_InstallationProps) => {
const { id } = useParams();
const [values, setValues] = useState<I_Installation>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<AxiosError>();
const theme = useTheme();
useEffect(() => { const Installation = (props: I_InstallationProps) => {
setLoading(true); const { hasMoveButton, values, loading, error } = props;
axiosConfig const { id } = useParams();
.get("/GetInstallationById?id=" + id) const theme = useTheme();
.then((res) => {
setValues(res.data);
setLoading(false);
})
.catch((err: AxiosError) => {
setError(err);
setLoading(false);
});
}, [id]);
if (values && values.id && values.id.toString() === id) { if (values && values.id && values.id.toString() === id) {
return ( return (
@ -51,7 +40,7 @@ const Installation = (props: I_InstallationProps) => {
<InstallationForm <InstallationForm
values={values} values={values}
id={id} id={id}
hasMoveButton={props.hasMoveButton} hasMoveButton={hasMoveButton}
/> />
</Box> </Box>
); );
@ -60,7 +49,7 @@ const Installation = (props: I_InstallationProps) => {
<Box <Box
sx={{ width: 1 / 2, justifyContent: "center", display: "flex", mt: 10 }} sx={{ width: 1 / 2, justifyContent: "center", display: "flex", mt: 10 }}
> >
<CircularProgress sx={{ m: 2 ,color: theme.palette.secondary.main}}/> <CircularProgress sx={{ m: 2, color: theme.palette.secondary.main }} />
</Box> </Box>
); );
} else if (error) { } else if (error) {

View File

@ -70,13 +70,12 @@ const InstallationList = (props: InstallationListProps) => {
<List <List
sx={{ sx={{
width: "100%", width: "100%",
bgcolor: "background.paper",
position: "relative", position: "relative",
overflow: "auto", overflow: "auto",
py: 0, py: 0,
mt: 1, mt: 1,
borderRadius: 1.5, borderRadius: 1.5,
height: maxHeight:
routeMatch?.pattern.path === routeMatch?.pattern.path ===
routes.installations + routes.list + routes.log + ":id" routes.installations + routes.list + routes.log + ":id"
? "130px" ? "130px"
@ -93,7 +92,7 @@ const InstallationList = (props: InstallationListProps) => {
to={ to={
getPathWithoutId(routeMatch?.pattern?.path) + installation.id getPathWithoutId(routeMatch?.pattern?.path) + installation.id
} }
style={{ textDecoration: "none", color: colors.blueBlack }} style={{ textDecoration: "none", color: colors.black }}
> >
<ListItemButton <ListItemButton
id={"installation-list-button-" + installation.id} id={"installation-list-button-" + installation.id}

View File

@ -4,18 +4,26 @@ import ModeButtons from "../Layout/ModeButtons";
import Installations from "./Installations"; import Installations from "./Installations";
import routes from "../../routes.json"; import routes from "../../routes.json";
import { Grid } from "@mui/material"; import { Grid } from "@mui/material";
import S3CredentialsContextProvider from "../Context/S3CredentialsContextProvider";
const InstallationPage = () => { const InstallationPage = () => {
return ( return (
<> <>
<Grid container spacing={2} marginTop={1}> <Grid container spacing={2}>
<Grid item xs={3}> <Grid item xs={3}>
<ModeButtons /> <ModeButtons />
</Grid> </Grid>
</Grid> </Grid>
<Routes> <Routes>
<Route path={routes.tree + "*"} element={<Groups />} /> <Route path={routes.tree + "*"} element={<Groups />} />
<Route path={routes.list + "*"} element={<Installations />} /> <Route
path={routes.list + "*"}
element={
<S3CredentialsContextProvider>
<Installations />
</S3CredentialsContextProvider>
}
/>
</Routes> </Routes>
</> </>
); );

View File

@ -25,7 +25,6 @@ const InstallationTabs = () => {
if (id) { if (id) {
return ( return (
<Box sx={{ width: "100%" }}> <Box sx={{ width: "100%" }}>
<Box sx={{}}>
<InnovenergyTabs <InnovenergyTabs
id="installation-tabs" id="installation-tabs"
value={routeMatch?.pattern?.path ?? routes.installation + ":id"} value={routeMatch?.pattern?.path ?? routes.installation + ":id"}
@ -53,23 +52,21 @@ const InstallationTabs = () => {
/> />
<InnovenergyTab <InnovenergyTab
id={"installation-tab-liveView"}
sx={{ sx={{
"&.Mui-selected": { "&.Mui-selected": {
color: colors.black, color: colors.black,
backgroundColor: "white", backgroundColor: theme.palette.primary.dark,
borderColor: theme.palette.text.disabled, borderColor: theme.palette.text.disabled,
borderBottom: 0, borderBottom: 0,
mb: -1 / 8, mb: -1 / 8,
}, },
}} }}
id={"installation-tab-liveView"}
label={intl.formatMessage({ label={intl.formatMessage({
id: "liveView", id: "liveView",
defaultMessage: "Live view", defaultMessage: "Live view",
})} })}
value={ value={routes.installations + routes.list + routes.liveView + ":id"}
routes.installations + routes.list + routes.liveView + ":id"
}
component={Link} component={Link}
to={routes.liveView + id} to={routes.liveView + id}
/> />
@ -77,7 +74,7 @@ const InstallationTabs = () => {
sx={{ sx={{
"&.Mui-selected": { "&.Mui-selected": {
color: colors.black, color: colors.black,
backgroundColor: theme.palette.primary.main, backgroundColor: theme.palette.primary.dark,
borderColor: theme.palette.text.disabled, borderColor: theme.palette.text.disabled,
borderBottom: 0, borderBottom: 0,
mb: -1 / 8, mb: -1 / 8,
@ -94,7 +91,6 @@ const InstallationTabs = () => {
/> />
</InnovenergyTabs> </InnovenergyTabs>
</Box> </Box>
</Box>
); );
} }
return null; return null;

View File

@ -13,20 +13,47 @@ import LogContextProvider from "../Context/LogContextProvider";
import useRouteMatch from "../../hooks/useRouteMatch"; import useRouteMatch from "../../hooks/useRouteMatch";
import { Background } from "reactflow"; import { Background } from "reactflow";
import { red } from "@mui/material/colors"; 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";
const Installations = () => { const Installations = () => {
const routeMatch = useRouteMatch([ const routeMatch = useRouteMatch([
routes.installations + routes.list + routes.log + ":id", routes.installations + routes.list + ":view/:id",
]); ]);
const {
data: currentInstallation,
loading,
error,
getInstallation,
} = useInstallation();
const id = routeMatch?.params?.id;
useEffect(() => {
// TODO remove if
getInstallation(id);
}, [id]);
return ( return (
<InstallationsContextProvider> <InstallationsContextProvider>
<LogContextProvider> <LogContextProvider>
<Grid container spacing={2} height="100%"> <Grid container spacing={2}>
<Grid item xs={3}> <Grid item xs={3}>
<SearchSidebar <SearchSidebar
id="installations-search-sidebar" id="installations-search-sidebar"
listComponent={InstallationList} listComponent={InstallationList}
height={routeMatch ? "200px" : undefined} height={routeMatch?.params?.view === "log" ? "200px" : undefined}
/> />
<CheckboxTree /> <CheckboxTree />
</Grid> </Grid>
@ -35,7 +62,13 @@ const Installations = () => {
<Routes> <Routes>
<Route <Route
path={routes.installation + ":id"} path={routes.installation + ":id"}
element={<Installation />} element={
<Installation
loading={loading}
values={currentInstallation}
error={error}
/>
}
index index
/> />
<Route path={routes.liveView + ":id"} element={<LiveView />} /> <Route path={routes.liveView + ":id"} element={<LiveView />} />

View File

@ -1,7 +1,28 @@
import TopologyView from "./Log/TopologyView"; import TopologyView from "./Log/TopologyView";
import { Box, useTheme } from "@mui/material";
import { colors } from "../../index";
const LiveView = () => { const LiveView = () => {
return <TopologyView />; const theme = useTheme();
return (
<Box
sx={{
py: 3,
bgcolor: colors.greyDark,
px: 1,
borderLeft: 2,
borderRight: 2,
borderBottom: 2,
borderTopLeftRadius: 0,
WebkitBorderTopRightRadius: 0,
borderBottomLeftRadius: 4,
borderBottomRightRadius: 4,
borderColor: theme.palette.text.disabled,
}}
>
<TopologyView />
</Box>
);
}; };
export default LiveView; export default LiveView;

View File

@ -49,7 +49,6 @@ const CheckboxTree = () => {
) => { ) => {
event.stopPropagation(); event.stopPropagation();
}; };
const theme = useTheme();
const renderTree = (data: TreeElement[]): ReactNode => { const renderTree = (data: TreeElement[]): ReactNode => {
return data.map((element) => { return data.map((element) => {
const checked = checkedToggles.find((toggle) => element.id === toggle); const checked = checkedToggles.find((toggle) => element.id === toggle);
@ -66,19 +65,20 @@ const CheckboxTree = () => {
<Checkbox <Checkbox
checked={!!checked} checked={!!checked}
onClick={(e) => handleClick(e, element, checked)} onClick={(e) => handleClick(e, element, checked)}
style={{
color: theme.palette.text.primary,
}}
sx={{ sx={{
paddingY: 0, paddingY: 0,
}} }}
color="secondary"
/> />
)} )}
{splitName[splitName.length - 1]} {splitName[splitName.length - 1]}
</> </>
} }
sx={{ sx={{
".MuiTreeItem-content": { paddingY: "5px" }, "& .MuiTreeItem-content": {
paddingY: "5px",
minHeight: "52px",
},
bgcolor: colors.greyDark, bgcolor: colors.greyDark,
}} }}
> >

View File

@ -14,6 +14,7 @@ interface DateRangePickerProps {
range: Date[]; range: Date[];
getCacheSeries: (xaxisRange0: Date, xaxisRange1: Date) => void; getCacheSeries: (xaxisRange0: Date, xaxisRange1: Date) => void;
} }
const DateRangePicker = (props: DateRangePickerProps) => { const DateRangePicker = (props: DateRangePickerProps) => {
const theme = useTheme(); const theme = useTheme();
const { setRange, range, getCacheSeries } = props; const { setRange, range, getCacheSeries } = props;
@ -42,10 +43,6 @@ const DateRangePicker = (props: DateRangePickerProps) => {
label="From date" label="From date"
value={dayjs(range[0])} value={dayjs(range[0])}
sx={{ sx={{
".MuiInputLabel-root": {
color: colors.blueBlack,
},
".Mui-disabled": { ".Mui-disabled": {
color: "red", color: "red",
}, },
@ -69,13 +66,12 @@ const DateRangePicker = (props: DateRangePickerProps) => {
".MuiInputLabel-root": { ".MuiInputLabel-root": {
color: colors.blueBlack, color: colors.blueBlack,
}, },
".Mui-disabled": { ".Mui-disabled": {
color: "red", color: "red",
}, },
".Mui-focused fieldset.MuiOutlinedInput-notchedOutline": { ".Mui-focused fieldset.MuiOutlinedInput-notchedOutline": {
borderColor: theme.palette.secondary.main, }, borderColor: theme.palette.secondary.main,
},
}} }}
onChange={(newValue) => { onChange={(newValue) => {
if (newValue) { if (newValue) {

View File

@ -1,11 +1,28 @@
import React from "react"; import React from "react";
import ScalarGraph from "./ScalarGraph"; import ScalarGraph from "./ScalarGraph";
import { colors } from "../../../index";
import { Box, useTheme } from "@mui/material";
const Log = () => { const Log = () => {
const theme = useTheme();
return ( return (
<> <Box
sx={{
py: 3,
bgcolor: colors.greyDark,
px: 1,
borderLeft: 2,
borderRight: 2,
borderBottom: 2,
borderTopLeftRadius: 0,
WebkitBorderTopRightRadius: 0,
borderBottomLeftRadius: 4,
borderBottomRightRadius: 4,
borderColor: theme.palette.text.disabled,
}}
>
<ScalarGraph /> <ScalarGraph />
</> </Box>
); );
}; };

View File

@ -22,40 +22,10 @@ import { FormattedMessage } from "react-intl";
import DateRangePicker from "./DateRangePicker"; import DateRangePicker from "./DateRangePicker";
import { LocalizationProvider } from "@mui/x-date-pickers"; import { LocalizationProvider } from "@mui/x-date-pickers";
import { Alert, useTheme } from "@mui/material"; import { Alert, useTheme } from "@mui/material";
import { S3CredentialsContext } from "../../Context/S3CredentialsContextProvider";
const NUMBER_OF_NODES = 100; const NUMBER_OF_NODES = 100;
const s3Access = new S3Access(
"saliomameiringen",
"sos-ch-dk-2",
"exo.io",
"EXO464a9ff62fdfa407aa742855", // key
"f2KtCWN4EHFqtvH2kotdyI0w5SjjdHVPAADdcD3ik8g" // secret
);
export const fetchData = (
timestamp: UnixTime
): Promise<FetchResult<DataRecord>> => {
const s3Path = `${timestamp.ticks}.csv`;
return s3Access
.get(s3Path)
.then(async (r) => {
if (r.status === 404) {
return Promise.resolve(FetchResult.notAvailable);
} else if (r.status === 200) {
const text = await r.text();
console.log("parsecsv", text, parseCsv(text));
return parseCsv(text);
} else {
console.error("unexpected status code");
return Promise.resolve(FetchResult.notAvailable);
}
})
.catch((e) => {
return Promise.resolve(FetchResult.tryLater);
});
};
const ScalarGraph = () => { const ScalarGraph = () => {
const timeRange = createTimes( const timeRange = createTimes(
UnixTime.now() /* .fromTicks(1682085650) */ UnixTime.now() /* .fromTicks(1682085650) */
@ -70,6 +40,7 @@ const ScalarGraph = () => {
const [plotTitles, setPlotTitles] = useState<string[]>([]); const [plotTitles, setPlotTitles] = useState<string[]>([]);
const { toggles, setToggles, checkedToggles } = useContext(LogContext); const { toggles, setToggles, checkedToggles } = useContext(LogContext);
const { fetchData } = useContext(S3CredentialsContext);
const times$ = useMemo(() => new BehaviorSubject(timeRange), []); const times$ = useMemo(() => new BehaviorSubject(timeRange), []);
@ -93,10 +64,9 @@ const ScalarGraph = () => {
return () => subscription.unsubscribe(); return () => subscription.unsubscribe();
}, [toggles]); }, [toggles]);
const cache = useMemo( const cache = useMemo(() => {
() => new DataCache(fetchData, TimeSpan.fromSeconds(2)), return new DataCache(fetchData, TimeSpan.fromSeconds(2));
[] }, []);
);
const transformToGraphData = (input: RecordSeries): GraphData => { const transformToGraphData = (input: RecordSeries): GraphData => {
const transformedObject: any = {}; const transformedObject: any = {};

View File

@ -20,7 +20,7 @@ const isInt = (value: number) => {
return value % 1 === 0; return value % 1 === 0;
}; };
export const BOX_SIZE = 85; 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 (

View File

@ -7,13 +7,14 @@ import {
getAmount, getAmount,
getHighestConnectionValue, getHighestConnectionValue,
} from "../../../util/graph.util"; } from "../../../util/graph.util";
import { fetchData } from "./ScalarGraph"; import { useContext, useEffect, useState } from "react";
import { useEffect, useState } from "react";
import { FetchResult } from "../../../dataCache/dataCache"; import { FetchResult } from "../../../dataCache/dataCache";
import { FormattedMessage } from "react-intl"; import { FormattedMessage } from "react-intl";
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);
useEffect(() => { useEffect(() => {
const interval = setInterval(() => { const interval = setInterval(() => {
@ -229,6 +230,7 @@ const TopologyView = () => {
/> />
</Alert> </Alert>
); );
return <div>TopologyView</div>;
}; };
export default TopologyView; export default TopologyView;

View File

@ -17,13 +17,13 @@ const InnovenergyTab = (props: any) => {
marginRight: theme.spacing(1), marginRight: theme.spacing(1),
background: "0 0", background: "0 0",
border: "2px solid transparent", border: "2px solid transparent",
bgcolor: theme.palette.primary.light,
borderTopLeftRadius: "0.3rem", borderTopLeftRadius: "0.3rem",
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`, transition: `color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out`,
"&.Mui-selected": { "&.Mui-selected": {
backgroundColor: "#eaecf1",
color: colors.black, color: colors.black,
borderColor: theme.palette.text.disabled, borderColor: theme.palette.text.disabled,
marginTop: "1px", marginTop: "1px",

View File

@ -5,7 +5,7 @@ import { colors } from "index";
interface AntTabsProps { interface AntTabsProps {
id: string; id: string;
value?: string; value?: string;
sx?: SxProps<Theme>; sx?: any;
children: ReactNode; children: ReactNode;
} }
@ -24,7 +24,7 @@ const InnovenergyTabs = (props: AntTabsProps) => {
}, },
"&.Mui-selected": { "&.Mui-selected": {
color: colors.black, color: colors.black,
backgroundColor: " #90A7c5", backgroundColor: "#eaecf1",
borderColor: `#90A7c5 #90A7c5 #fff`, borderColor: `#90A7c5 #90A7c5 #fff`,
}, },
"& .MuiTabs-indicator": { "& .MuiTabs-indicator": {
@ -34,6 +34,7 @@ const InnovenergyTabs = (props: AntTabsProps) => {
}, },
"&.MuiTabs-root": { "&.MuiTabs-root": {
width: "100%", width: "100%",
...(props.sx ? props.sx["&.MuiTabs-root"] : {}),
}, },
}} }}
> >

View File

@ -11,6 +11,7 @@ import {
TextField, TextField,
useTheme, useTheme,
} from "@mui/material"; } from "@mui/material";
import { colors } from "../../index";
export interface I_InnovenergyTextfieldProps { export interface I_InnovenergyTextfieldProps {
id: string; id: string;
@ -28,18 +29,6 @@ export interface I_InnovenergyTextfieldProps {
export const IePropertyGrid = (props: { export const IePropertyGrid = (props: {
rows: I_InnovenergyTextfieldProps[]; rows: I_InnovenergyTextfieldProps[];
}) => { }) => {
const theme = useTheme();
const findLongestLength = () => {
return (
11 *
props.rows.reduce(
(acc, curr) => (acc > curr.label.length ? acc : curr.label.length),
0
)
);
};
return ( return (
<div style={{ display: "flex", flexDirection: "row" }}> <div style={{ display: "flex", flexDirection: "row" }}>
<div <div
@ -51,13 +40,14 @@ export const IePropertyGrid = (props: {
}} }}
> >
{props.rows.map((prop) => ( {props.rows.map((prop) => (
<InputLabel>{prop.label}</InputLabel> <InputLabel key={prop.label}>{prop.label}</InputLabel>
))} ))}
</div> </div>
<div> <div>
{props.rows.map((element) => { {props.rows.map((element) => {
return ( return (
<TextField <TextField
key={element.label}
color="secondary" color="secondary"
id={element.id} id={element.id}
variant="outlined" variant="outlined"
@ -72,7 +62,12 @@ export const IePropertyGrid = (props: {
height: 15, height: 15,
}, },
my: 0.5, my: 0.5,
borderColor: "red", ".Mui-focused": {
borderRadius: 1,
},
"MuiInputLabel-root": {
color: colors.black,
},
}} }}
value={element.value || ""} value={element.value || ""}
onChange={element.handleChange} onChange={element.handleChange}

View File

@ -6,18 +6,22 @@ import InnovenergyTab from "./InnovenergyTab";
import InnovenergyTabs from "./InnovenergyTabs"; import InnovenergyTabs from "./InnovenergyTabs";
import { useTheme } from "@mui/material"; import { useTheme } from "@mui/material";
const NavigationButtons = () => { const NavigationTabs = () => {
const theme = useTheme(); const theme = useTheme();
const routeMatch = useRouteMatch([ const routeMatch = useRouteMatch([
routes.installations + "*", routes.installations + "*",
routes.users + "*", routes.users + "*",
]); ]);
const intl = useIntl();
return ( return (
<> <>
<InnovenergyTabs <InnovenergyTabs
sx={{ paddingTop: 0 }} sx={{
paddingTop: 0,
"&.MuiTabs-root": {
borderBottom: "none",
},
}}
id="navigation-buttons-tabs" id="navigation-buttons-tabs"
value={routeMatch?.pattern?.path} value={routeMatch?.pattern?.path}
> >
@ -25,7 +29,7 @@ const NavigationButtons = () => {
sx={{ sx={{
bgcolor: theme.palette.primary.main, bgcolor: theme.palette.primary.main,
"&.Mui-selected": { "&.Mui-selected": {
backgroundColor: theme.palette.primary.main, backgroundColor: "#eaecf1",
borderColor: theme.palette.text.disabled, borderColor: theme.palette.text.disabled,
borderBottom: 0, borderBottom: 0,
mb: -1 / 8, mb: -1 / 8,
@ -46,7 +50,7 @@ const NavigationButtons = () => {
sx={{ sx={{
bgcolor: theme.palette.primary.main, bgcolor: theme.palette.primary.main,
"&.Mui-selected": { "&.Mui-selected": {
backgroundColor: theme.palette.primary.main, backgroundColor: "#eaecf1",
borderColor: theme.palette.text.disabled, borderColor: theme.palette.text.disabled,
borderBottom: 0, borderBottom: 0,
mb: -1 / 8, mb: -1 / 8,
@ -63,4 +67,4 @@ const NavigationButtons = () => {
); );
}; };
export default NavigationButtons; export default NavigationTabs;

View File

@ -10,7 +10,6 @@ import { colors } from "index";
import { FC, useState } from "react"; import { FC, useState } from "react";
import { useIntl } from "react-intl"; import { useIntl } from "react-intl";
interface SearchSidebarProps { interface SearchSidebarProps {
listComponent: FC<{ searchQuery: string }>; listComponent: FC<{ searchQuery: string }>;
id: string; id: string;
@ -22,7 +21,6 @@ const SearchInputTextfield = styled((props: TextFieldProps) => (
InputProps={{ disableUnderline: true } as Partial<OutlinedInputProps>} InputProps={{ disableUnderline: true } as Partial<OutlinedInputProps>}
{...props} {...props}
/> />
))(({ theme }) => ({ ))(({ theme }) => ({
"& .MuiFilledInput-root": { "& .MuiFilledInput-root": {
overflow: "hidden", overflow: "hidden",
@ -41,11 +39,9 @@ const SearchInputTextfield = styled((props: TextFieldProps) => (
backgroundColor: theme.palette.primary.dark, backgroundColor: theme.palette.primary.dark,
}, },
"&.Mui-focused": { "&.Mui-focused": {
backgroundColor: theme.palette.primary.dark, backgroundColor: theme.palette.primary.dark,
borderColor: theme.palette.secondary.main, borderColor: theme.palette.secondary.main,
}, },
}, },
})); }));
@ -55,28 +51,17 @@ const SearchSidebar = (props: SearchSidebarProps) => {
const intl = useIntl(); const intl = useIntl();
const theme = useTheme(); const theme = useTheme();
return ( return (
<div style={{ height: height ?? "750px", overflow: "hidden" }}> <div style={{ height: height ?? "750px", overflow: "hidden" }}>
{/* <RedditTextField
style={{ backgroundColor: "#577ba840" }}
color="secondary"
id={id}
label={intl.formatMessage({
id: "search",
defaultMessage: "Search",
})}
type="search"
fullWidth
value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)}
/> */}
<SearchInputTextfield <SearchInputTextfield
sx={{ sx={{
".MuiInputLabel-root" :{ ".MuiInputLabel-root": {
color: colors.black color: colors.black,
}}} },
".Mui-focused": {
color: colors.black,
},
}}
id={id} id={id}
variant="filled" variant="filled"
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}

View File

@ -1,11 +1,11 @@
import axios from "axios"; import axios from "axios";
export const axiosConfigWithoutToken = axios.create({ export const axiosConfigWithoutToken = axios.create({
baseURL: "https://monitor.innov.energy:5000/api", baseURL: "https://localhost:7087/api",
}); });
const axiosConfig = axios.create({ const axiosConfig = axios.create({
baseURL: "https://monitor.innov.energy:5000/api", baseURL: "https://localhost:7087/api",
}); });
axiosConfig.defaults.params = {}; axiosConfig.defaults.params = {};
axiosConfig.interceptors.request.use( axiosConfig.interceptors.request.use(

View File

@ -138,10 +138,13 @@ export default class DataCache<T extends Record<string, CsvEntry>> {
const onSuccess = (data: FetchResult<T>) => { const onSuccess = (data: FetchResult<T>) => {
if (data === FetchResult.tryLater) { if (data === FetchResult.tryLater) {
console.warn(FetchResult.tryLater); console.warn(FetchResult.tryLater);
return;
} }
const value = data === FetchResult.notAvailable ? undefined : data; const value =
data === FetchResult.notAvailable || data === FetchResult.tryLater
? undefined
: data;
// @ts-ignore
this.cache.insert(value, t); this.cache.insert(value, t);
}; };

View File

@ -0,0 +1,43 @@
import { useContext, useState } from "react";
import axiosConfig from "../config/axiosConfig";
import { I_Installation } from "../util/types";
import { AxiosError } from "axios";
import { S3CredentialsContext } from "../components/Context/S3CredentialsContextProvider";
const useInstallation = () => {
const [data, setData] = useState<I_Installation>();
const [loading, setLoading] = useState(false);
const [error, setError] = useState<AxiosError>();
const { saveS3Credentials } = useContext(S3CredentialsContext);
const getInstallation = (id?: string) => {
if (id) {
setLoading(true);
axiosConfig
.get("/GetInstallationById?id=" + id)
.then(({ data }: { data: I_Installation }) => {
const { s3Region, s3Provider, s3Secret, s3Key } = data;
setData(data);
setLoading(false);
// TODO remove if
if (id) {
saveS3Credentials({ s3Region, s3Provider, s3Secret, s3Key }, id);
}
})
.catch((err: AxiosError) => {
setError(err);
setLoading(false);
});
}
};
return {
data,
loading,
error,
getInstallation,
};
};
export default useInstallation;

View File

@ -3,7 +3,7 @@ import ReactDOM from "react-dom/client";
import "./index.css"; import "./index.css";
import App from "./App"; import App from "./App";
import reportWebVitals from "./reportWebVitals"; import reportWebVitals from "./reportWebVitals";
import {createTheme, ThemeProvider} from "@mui/material"; import { createTheme, ThemeProvider } from "@mui/material";
import UserContextProvider from "./components/Context/UserContextProvider"; import UserContextProvider from "./components/Context/UserContextProvider";
const root = ReactDOM.createRoot( const root = ReactDOM.createRoot(
@ -21,8 +21,8 @@ export const colors = {
greyDark: "#d1d7de", greyDark: "#d1d7de",
greyLight: "#e1e4e7", greyLight: "#e1e4e7",
orangeSelected: "#ffd280", orangeSelected: "#f7b34d",
orangeHover: "#ffe4b3", orangeHover: "#fad399",
orangeDark: "#ffc04d", orangeDark: "#ffc04d",
}; };
@ -37,8 +37,7 @@ const theme = createTheme({
}, },
palette: { palette: {
text: { text: {
primary: colors.blueBlack, primary: colors.black,
secondary: "#000000",
disabled: colors.borderColor, disabled: colors.borderColor,
}, },
primary: { primary: {
@ -61,7 +60,7 @@ const theme = createTheme({
root.render( root.render(
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<UserContextProvider> <UserContextProvider>
<App/> <App />
</UserContextProvider> </UserContextProvider>
</ThemeProvider> </ThemeProvider>
); );

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.9 KiB

View File

@ -1,5 +1,13 @@
// TODO add if required or not // TODO add if required or not
export interface I_Installation { export interface S3Credentials {
s3Region: string;
s3Provider: string;
s3Key: string;
s3Secret: string;
s3Bucket?: string;
}
export interface I_Installation extends S3Credentials {
type: string; type: string;
title?: string; title?: string;
status?: number; status?: number;
@ -15,12 +23,6 @@ export interface I_Installation {
name: string; name: string;
information: string; information: string;
parentId: number; parentId: number;
s3Bucket: string;
s3Region: string;
s3Provider: string;
s3Key: string;
s3Secret: string;
} }
export interface I_Folder { export interface I_Folder {