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>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<head>
<meta charset="utf-8"/>
<link rel="icon" href="%PUBLIC_URL%/favicon.ico"/>
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<meta name="theme-color" content="#000000"/>
<meta
name="description"
content="Web site created using create-react-app"
name="description"
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
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.
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`.
-->
<title>Innovenergy</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root" style="height: 100vh; display: flex; flex-direction: column;"></div>
<!--
This HTML file is a template.
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.
The build step will place the bundled scripts into the <body> tag.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -2,7 +2,7 @@ import useToken from "./hooks/useToken";
import Login from "./Login";
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 { IntlProvider, MessageFormatElement } from "react-intl";
import { useContext, useState } from "react";
@ -12,11 +12,11 @@ import fr from "./lang/fr.json";
import LanguageSelect from "./components/Layout/LanguageSelect";
import LogoutButton from "./components/Layout/LogoutButton";
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 { UserContext } from "./components/Context/UserContextProvider";
import ResetPassword from "./ResetPassword";
import innovenergyLogo from "./resources/test.gif";
import innovenergyLogo from "./resources/innoveng_logo_on_orange.png";
const App = () => {
const { token, setToken, removeToken } = useToken();
@ -48,9 +48,9 @@ const App = () => {
locale={language}
defaultLocale="EN"
>
<Container maxWidth="xl" sx={{ marginTop: 2, height: "100vh" }}>
<Grid container maxHeight="20vh">
<Grid item xs={3} container justifyContent="flex-start">
<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
@ -64,25 +64,37 @@ const App = () => {
<LogoutButton removeToken={removeToken} />
</Grid>
</Grid>
<Grid container spacing={2} mt={2}>
<Grid item xs={3} container justifyContent="flex-start">
<NavigationButtons />
</Container>
<Container maxWidth="xl" sx={{ pt: 2 }}>
<Grid spacing={2} width="100%">
<Grid item xs={3}>
<NavigationTabs />
</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>
<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>
</BrowserRouter>
);

View File

@ -1,29 +1,89 @@
import { createContext, ReactNode, useState } from "react";
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 {
s3Credentials?: I_User;
setS3Credentials: (value: I_User) => void;
interface S3CredentialsContextProviderProps {
s3Credentials?: S3Credentials;
saveS3Credentials: (credentials: S3Credentials, id: string) => void;
fetchData: (timestamp: UnixTime) => Promise<FetchResult<DataRecord>>;
}
export const UserContext = createContext<InstallationContextProviderProps>({
s3Credentials: {} as I_User,
setS3Credentials: () => {},
});
export const S3CredentialsContext =
createContext<S3CredentialsContextProviderProps>({
s3Credentials: {} as S3Credentials,
saveS3Credentials: () => {},
fetchData: () => ({} as Promise<FetchResult<DataRecord>>),
});
const UserContextProvider = ({ children }: { children: ReactNode }) => {
const [s3Credentials, setS3Credentials] = useState<I_User>();
const S3CredentialsContextProvider = ({
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 (
<UserContext.Provider
<S3CredentialsContext.Provider
value={{
s3Credentials: s3Credentials,
setS3Credentials: setS3Credentials,
s3Credentials,
saveS3Credentials,
fetchData,
}}
>
{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 AccessManagement from "./AccessManagement/AccessManagement";
import Installation from "../Installations/Installation";
import useRouteMatch from "../../hooks/useRouteMatch";
import useInstallation from "../../hooks/useInstallation";
import { useEffect } from "react";
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 (
<GroupContextProvider>
<Grid container spacing={2}>
@ -25,7 +44,14 @@ const Groups = () => {
/>
<Route
path={routes.installation + ":id"}
element={<Installation hasMoveButton />}
element={
<Installation
hasMoveButton
error={error}
values={currentInstallation}
loading={loading}
/>
}
/>
</Routes>
</Grid>

View File

@ -11,6 +11,7 @@ import { instanceOfFolder } from "../../../util/group.util";
import { Grid, CircularProgress, useTheme } from "@mui/material";
import { useIntl } from "react-intl";
import { colors } from "../../..";
import { color } from "chart.js/helpers";
const GroupTree = () => {
const { setCurrentType, fetchData, data, loading } = useContext(GroupContext);
@ -54,7 +55,14 @@ const GroupTree = () => {
label={element.name}
onClick={() => setCurrentType(element.type)}
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,
borderRadius: 2,
}}
@ -68,7 +76,7 @@ const GroupTree = () => {
if (loading) {
return (
<Grid container justifyContent="center" width="100%">
<CircularProgress sx={{color: theme.palette.secondary.main}} />
<CircularProgress sx={{ color: theme.palette.secondary.main }} />
</Grid>
);
}

View File

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

View File

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

View File

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

View File

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

View File

@ -13,20 +13,47 @@ import LogContextProvider from "../Context/LogContextProvider";
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";
const Installations = () => {
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 (
<InstallationsContextProvider>
<LogContextProvider>
<Grid container spacing={2} height="100%">
<Grid container spacing={2}>
<Grid item xs={3}>
<SearchSidebar
id="installations-search-sidebar"
listComponent={InstallationList}
height={routeMatch ? "200px" : undefined}
height={routeMatch?.params?.view === "log" ? "200px" : undefined}
/>
<CheckboxTree />
</Grid>
@ -35,7 +62,13 @@ const Installations = () => {
<Routes>
<Route
path={routes.installation + ":id"}
element={<Installation />}
element={
<Installation
loading={loading}
values={currentInstallation}
error={error}
/>
}
index
/>
<Route path={routes.liveView + ":id"} element={<LiveView />} />

View File

@ -1,7 +1,28 @@
import TopologyView from "./Log/TopologyView";
import { Box, useTheme } from "@mui/material";
import { colors } from "../../index";
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;

View File

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

View File

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

View File

@ -1,11 +1,28 @@
import React from "react";
import ScalarGraph from "./ScalarGraph";
import { colors } from "../../../index";
import { Box, useTheme } from "@mui/material";
const Log = () => {
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,
}}
>
<ScalarGraph />
</>
</Box>
);
};

View File

@ -22,40 +22,10 @@ import { FormattedMessage } from "react-intl";
import DateRangePicker from "./DateRangePicker";
import { LocalizationProvider } from "@mui/x-date-pickers";
import { Alert, useTheme } from "@mui/material";
import { S3CredentialsContext } from "../../Context/S3CredentialsContextProvider";
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 timeRange = createTimes(
UnixTime.now() /* .fromTicks(1682085650) */
@ -70,6 +40,7 @@ const ScalarGraph = () => {
const [plotTitles, setPlotTitles] = useState<string[]>([]);
const { toggles, setToggles, checkedToggles } = useContext(LogContext);
const { fetchData } = useContext(S3CredentialsContext);
const times$ = useMemo(() => new BehaviorSubject(timeRange), []);
@ -93,10 +64,9 @@ const ScalarGraph = () => {
return () => subscription.unsubscribe();
}, [toggles]);
const cache = useMemo(
() => new DataCache(fetchData, TimeSpan.fromSeconds(2)),
[]
);
const cache = useMemo(() => {
return new DataCache(fetchData, TimeSpan.fromSeconds(2));
}, []);
const transformToGraphData = (input: RecordSeries): GraphData => {
const transformedObject: any = {};

View File

@ -20,7 +20,7 @@ const isInt = (value: number) => {
return value % 1 === 0;
};
export const BOX_SIZE = 85;
export const BOX_SIZE = 75;
const TopologyBox = (props: TopologyBoxProps) => {
const { titleColor, boxColor } = getBoxColor(props.title);
return (

View File

@ -7,13 +7,14 @@ import {
getAmount,
getHighestConnectionValue,
} from "../../../util/graph.util";
import { fetchData } from "./ScalarGraph";
import { useEffect, useState } from "react";
import { useContext, useEffect, useState } from "react";
import { FetchResult } from "../../../dataCache/dataCache";
import { FormattedMessage } from "react-intl";
import { S3CredentialsContext } from "../../Context/S3CredentialsContextProvider";
const TopologyView = () => {
const [values, setValues] = useState<TopologyValues | null>(null);
const { fetchData } = useContext(S3CredentialsContext);
useEffect(() => {
const interval = setInterval(() => {
@ -229,6 +230,7 @@ const TopologyView = () => {
/>
</Alert>
);
return <div>TopologyView</div>;
};
export default TopologyView;

View File

@ -17,13 +17,13 @@ const InnovenergyTab = (props: any) => {
marginRight: theme.spacing(1),
background: "0 0",
border: "2px solid transparent",
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": {
backgroundColor: "#eaecf1",
color: colors.black,
borderColor: theme.palette.text.disabled,
marginTop: "1px",

View File

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

View File

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

View File

@ -6,18 +6,22 @@ import InnovenergyTab from "./InnovenergyTab";
import InnovenergyTabs from "./InnovenergyTabs";
import { useTheme } from "@mui/material";
const NavigationButtons = () => {
const NavigationTabs = () => {
const theme = useTheme();
const routeMatch = useRouteMatch([
routes.installations + "*",
routes.users + "*",
]);
const intl = useIntl();
return (
<>
<InnovenergyTabs
sx={{ paddingTop: 0 }}
sx={{
paddingTop: 0,
"&.MuiTabs-root": {
borderBottom: "none",
},
}}
id="navigation-buttons-tabs"
value={routeMatch?.pattern?.path}
>
@ -25,7 +29,7 @@ const NavigationButtons = () => {
sx={{
bgcolor: theme.palette.primary.main,
"&.Mui-selected": {
backgroundColor: theme.palette.primary.main,
backgroundColor: "#eaecf1",
borderColor: theme.palette.text.disabled,
borderBottom: 0,
mb: -1 / 8,
@ -46,7 +50,7 @@ const NavigationButtons = () => {
sx={{
bgcolor: theme.palette.primary.main,
"&.Mui-selected": {
backgroundColor: theme.palette.primary.main,
backgroundColor: "#eaecf1",
borderColor: theme.palette.text.disabled,
borderBottom: 0,
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 { useIntl } from "react-intl";
interface SearchSidebarProps {
listComponent: FC<{ searchQuery: string }>;
id: string;
@ -22,7 +21,6 @@ const SearchInputTextfield = styled((props: TextFieldProps) => (
InputProps={{ disableUnderline: true } as Partial<OutlinedInputProps>}
{...props}
/>
))(({ theme }) => ({
"& .MuiFilledInput-root": {
overflow: "hidden",
@ -41,11 +39,9 @@ const SearchInputTextfield = styled((props: TextFieldProps) => (
backgroundColor: theme.palette.primary.dark,
},
"&.Mui-focused": {
backgroundColor: theme.palette.primary.dark,
borderColor: theme.palette.secondary.main,
},
},
}));
@ -55,28 +51,17 @@ const SearchSidebar = (props: SearchSidebarProps) => {
const intl = useIntl();
const theme = useTheme();
return (
<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
sx={{
".MuiInputLabel-root" :{
color: colors.black
}}}
sx={{
".MuiInputLabel-root": {
color: colors.black,
},
".Mui-focused": {
color: colors.black,
},
}}
id={id}
variant="filled"
onChange={(e) => setSearchQuery(e.target.value)}

View File

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

View File

@ -138,10 +138,13 @@ export default class DataCache<T extends Record<string, CsvEntry>> {
const onSuccess = (data: FetchResult<T>) => {
if (data === 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);
};

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 App from "./App";
import reportWebVitals from "./reportWebVitals";
import {createTheme, ThemeProvider} from "@mui/material";
import { createTheme, ThemeProvider } from "@mui/material";
import UserContextProvider from "./components/Context/UserContextProvider";
const root = ReactDOM.createRoot(
document.getElementById("root") as HTMLElement
document.getElementById("root") as HTMLElement
);
export const colors = {
black: "#000000",
blueBlack: "#2b3e54",
borderColor: "#a8b0be",
black: "#000000",
blueBlack: "#2b3e54",
borderColor: "#a8b0be",
background: "#f3f4f7",
//change this color in index.css too
background: "#f3f4f7",
//change this color in index.css too
greyDark: "#d1d7de",
greyLight: "#e1e4e7",
greyDark: "#d1d7de",
greyLight: "#e1e4e7",
orangeSelected: "#ffd280",
orangeHover: "#ffe4b3",
orangeSelected: "#f7b34d",
orangeHover: "#fad399",
orangeDark: "#ffc04d",
orangeDark: "#ffc04d",
};
const theme = createTheme({
components: {
MuiButtonBase: {
defaultProps: {
disableRipple: true,
},
},
components: {
MuiButtonBase: {
defaultProps: {
disableRipple: true,
},
},
palette: {
text: {
primary: colors.blueBlack,
secondary: "#000000",
disabled: colors.borderColor,
},
primary: {
main: colors.background,
light: colors.greyLight,
dark: colors.greyDark,
},
secondary: {
main: colors.orangeSelected,
light: colors.orangeHover,
dark: colors.orangeDark,
},
},
palette: {
text: {
primary: colors.black,
disabled: colors.borderColor,
},
typography: {
fontFamily: `"Ubuntu", sans-serif`,
fontWeightRegular: 300,
primary: {
main: colors.background,
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(
<ThemeProvider theme={theme}>
<UserContextProvider>
<App/>
</UserContextProvider>
</ThemeProvider>
<ThemeProvider theme={theme}>
<UserContextProvider>
<App />
</UserContextProvider>
</ThemeProvider>
);
// 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
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;
title?: string;
status?: number;
@ -15,12 +23,6 @@ export interface I_Installation {
name: string;
information: string;
parentId: number;
s3Bucket: string;
s3Region: string;
s3Provider: string;
s3Key: string;
s3Secret: string;
}
export interface I_Folder {