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,19 +25,19 @@
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.
You can add webfonts, meta tags, or analytics to this file. You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag. The build step will place the bundled scripts into the <body> tag.
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,25 +64,37 @@ 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>
<Routes>
<Route
path="*"
element={
<Navigate to={routes.installations + routes.list} replace />
}
/>
<Route
path={routes.installations + "*"}
element={<InstallationPage />}
/>
<Route path={routes.users + "*"} element={<Users />} />
</Routes>
</Container> </Container>
<Box
bgcolor="#eaecf1"
pt={3}
borderTop="2px solid"
borderColor=" #a8b0be"
flex="1 0 auto"
>
<Container maxWidth="xl">
<Routes>
<Route
path="*"
element={
<Navigate to={routes.installations + routes.list} replace />
}
/>
<Route
path={routes.installations + "*"}
element={<InstallationPage />}
/>
<Route path={routes.users + "*"} element={<Users />} />
</Routes>
</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,75 +25,71 @@ 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"} >
> <InnovenergyTab
<InnovenergyTab sx={{
sx={{ "&.Mui-selected": {
"&.Mui-selected": { color: colors.black,
color: colors.black, backgroundColor: theme.palette.primary.dark,
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-installation"}
id={"installation-tab-installation"} label={intl.formatMessage({
label={intl.formatMessage({ id: "installation",
id: "installation", defaultMessage: "Installation",
defaultMessage: "Installation", })}
})} value={
value={ routes.installations + routes.list + routes.installation + ":id"
routes.installations + routes.list + routes.installation + ":id" }
} component={Link}
component={Link} to={routes.installation + id}
to={routes.installation + id} />
/>
<InnovenergyTab <InnovenergyTab
sx={{ id={"installation-tab-liveView"}
"&.Mui-selected": { sx={{
color: colors.black, "&.Mui-selected": {
backgroundColor: "white", color: colors.black,
borderColor: theme.palette.text.disabled, backgroundColor: theme.palette.primary.dark,
borderBottom: 0, borderColor: theme.palette.text.disabled,
mb: -1 / 8, borderBottom: 0,
}, 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}
} to={routes.liveView + id}
component={Link} />
to={routes.liveView + id} <InnovenergyTab
/> sx={{
<InnovenergyTab "&.Mui-selected": {
sx={{ color: colors.black,
"&.Mui-selected": { backgroundColor: theme.palette.primary.dark,
color: colors.black, borderColor: theme.palette.text.disabled,
backgroundColor: theme.palette.primary.main, borderBottom: 0,
borderColor: theme.palette.text.disabled, mb: -1 / 8,
borderBottom: 0, },
mb: -1 / 8, }}
}, id={"installation-tab-log"}
}} label={intl.formatMessage({
id={"installation-tab-log"} id: "log",
label={intl.formatMessage({ defaultMessage: "Log",
id: "log", })}
defaultMessage: "Log", value={routes.installations + routes.list + routes.log + ":id"}
})} component={Link}
value={routes.installations + routes.list + routes.log + ":id"} to={routes.log + id}
component={Link} />
to={routes.log + id} </InnovenergyTabs>
/>
</InnovenergyTabs>
</Box>
</Box> </Box>
); );
} }

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,67 +3,66 @@ 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(
document.getElementById("root") as HTMLElement document.getElementById("root") as HTMLElement
); );
export const colors = { export const colors = {
black: "#000000", black: "#000000",
blueBlack: "#2b3e54", blueBlack: "#2b3e54",
borderColor: "#a8b0be", borderColor: "#a8b0be",
background: "#f3f4f7", background: "#f3f4f7",
//change this color in index.css too //change this color in index.css too
greyDark: "#d1d7de", greyDark: "#d1d7de",
greyLight: "#e1e4e7", greyLight: "#e1e4e7",
orangeSelected: "#ffd280", orangeSelected: "#f7b34d",
orangeHover: "#ffe4b3", orangeHover: "#fad399",
orangeDark: "#ffc04d", orangeDark: "#ffc04d",
}; };
const theme = createTheme({ const theme = createTheme({
components: { components: {
MuiButtonBase: { MuiButtonBase: {
defaultProps: { defaultProps: {
disableRipple: true, disableRipple: true,
}, },
},
}, },
palette: { },
text: { palette: {
primary: colors.blueBlack, text: {
secondary: "#000000", primary: colors.black,
disabled: colors.borderColor, disabled: colors.borderColor,
},
primary: {
main: colors.background,
light: colors.greyLight,
dark: colors.greyDark,
},
secondary: {
main: colors.orangeSelected,
light: colors.orangeHover,
dark: colors.orangeDark,
},
}, },
typography: { primary: {
fontFamily: `"Ubuntu", sans-serif`, main: colors.background,
fontWeightRegular: 300, light: colors.greyLight,
dark: colors.greyDark,
}, },
secondary: {
main: colors.orangeSelected,
light: colors.orangeHover,
dark: colors.orangeDark,
},
},
typography: {
fontFamily: `"Ubuntu", sans-serif`,
fontWeightRegular: 300,
},
}); });
root.render( root.render(
<ThemeProvider theme={theme}> <ThemeProvider theme={theme}>
<UserContextProvider> <UserContextProvider>
<App/> <App />
</UserContextProvider> </UserContextProvider>
</ThemeProvider> </ThemeProvider>
); );
// If you want to start measuring performance in your app, pass a function // If you want to start measuring performance in your app, pass a function

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 {