Merge remote-tracking branch 'origin/marios'
This commit is contained in:
commit
ec1681311a
|
@ -1,21 +0,0 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 BloomUI
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
File diff suppressed because it is too large
Load Diff
|
@ -14,7 +14,9 @@
|
|||
"@types/react-dom": "17.0.13",
|
||||
"apexcharts": "3.35.3",
|
||||
"axios": "^1.5.0",
|
||||
"chart.js": "^4.4.0",
|
||||
"clsx": "1.1.1",
|
||||
"cytoscape": "^3.26.0",
|
||||
"date-fns": "2.28.0",
|
||||
"history": "5.3.0",
|
||||
"linq-to-typescript": "^11.0.0",
|
||||
|
@ -23,9 +25,14 @@
|
|||
"prop-types": "15.8.1",
|
||||
"react": "17.0.2",
|
||||
"react-apexcharts": "1.4.0",
|
||||
"react-chartjs-2": "^5.2.0",
|
||||
"react-custom-scrollbars-2": "4.4.0",
|
||||
"react-cytoscapejs": "^2.0.0",
|
||||
"react-dom": "17.0.2",
|
||||
"react-flow-renderer": "^10.3.17",
|
||||
"react-helmet-async": "1.3.0",
|
||||
"react-icons": "^4.11.0",
|
||||
"react-icons-converter": "^1.1.4",
|
||||
"react-intl": "^6.4.4",
|
||||
"react-router": "6.3.0",
|
||||
"react-router-dom": "6.3.0",
|
||||
|
|
|
@ -15,13 +15,13 @@ import SidebarLayout from './layouts/SidebarLayout';
|
|||
import { TokenContext } from './contexts/tokenContext';
|
||||
import ResetPassword from './components/ResetPassword';
|
||||
import ForgotPassword from './components/ForgotPassword';
|
||||
import InstallationTabs from './content/dashboards/Installations/index';
|
||||
import routes from 'src/Resources/routes.json';
|
||||
import './App.css';
|
||||
|
||||
function App() {
|
||||
//const content = useRoutes(router);
|
||||
|
||||
const context = useContext(UserContext);
|
||||
const { currentUser, setUser } = context;
|
||||
|
||||
const tokencontext = useContext(TokenContext);
|
||||
const { token, setNewToken, removeToken } = tokencontext;
|
||||
const [forgotPassword, setForgotPassword] = useState(false);
|
||||
|
@ -79,14 +79,14 @@ function App() {
|
|||
lazy(() => import('src/content/pages/Status/Maintenance'))
|
||||
);
|
||||
|
||||
const routes: RouteObject[] = [
|
||||
const routesArray: RouteObject[] = [
|
||||
{
|
||||
path: '',
|
||||
element: <BaseLayout />,
|
||||
children: [
|
||||
{
|
||||
path: '/',
|
||||
element: <Navigate to="installations" replace />
|
||||
element: <Navigate to="installations/" replace />
|
||||
},
|
||||
{
|
||||
path: 'status',
|
||||
|
@ -118,39 +118,6 @@ function App() {
|
|||
element: <Status404 />
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'ResetPassword',
|
||||
element: <ResetPassword></ResetPassword>
|
||||
},
|
||||
{
|
||||
path: 'Login',
|
||||
element: <Login></Login>
|
||||
},
|
||||
{
|
||||
path: 'installations',
|
||||
element: (
|
||||
<SidebarLayout language={language} onSelectLanguage={setLanguage} />
|
||||
),
|
||||
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
element: <Installations />
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
path: 'users',
|
||||
element: (
|
||||
<SidebarLayout language={language} onSelectLanguage={setLanguage} />
|
||||
),
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
element: <Users />
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
if (forgotPassword) {
|
||||
|
@ -189,7 +156,7 @@ function App() {
|
|||
>
|
||||
<CssBaseline />
|
||||
<Routes>
|
||||
{routes.map((route, index) => (
|
||||
{routesArray.map((route, index) => (
|
||||
<Route key={index} path={route.path} element={route.element}>
|
||||
{route.children &&
|
||||
route.children.map((childRoute, childIndex) => (
|
||||
|
@ -201,6 +168,23 @@ function App() {
|
|||
))}
|
||||
</Route>
|
||||
))}
|
||||
<Route
|
||||
path="/"
|
||||
element={
|
||||
<SidebarLayout
|
||||
language={language}
|
||||
onSelectLanguage={setLanguage}
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Route
|
||||
path={routes.installations + '*'}
|
||||
element={<InstallationTabs />}
|
||||
/>
|
||||
<Route path={routes.users + '*'} element={<Users />} />
|
||||
<Route path="ResetPassword" element={<ResetPassword />}></Route>
|
||||
<Route path="Login" element={<Login />}></Route>
|
||||
</Route>
|
||||
</Routes>
|
||||
</IntlProvider>
|
||||
</ThemeProvider>
|
||||
|
|
|
@ -1,8 +1,7 @@
|
|||
import React, { useContext, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import {
|
||||
Card,
|
||||
CircularProgress,
|
||||
Divider,
|
||||
Grid,
|
||||
Table,
|
||||
TableBody,
|
||||
|
@ -18,26 +17,44 @@ import Installation from './Installation';
|
|||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import { LogContext } from 'src/contexts/LogContextProvider';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
import routes from 'src/Resources/routes.json';
|
||||
|
||||
interface FlatInstallationViewProps {
|
||||
installations: I_Installation[];
|
||||
}
|
||||
|
||||
const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||
const [selectedInstallation, setSelectedInstallation] = useState<number>(-1);
|
||||
const selectedBulkActions = selectedInstallation === -1 ? false : true;
|
||||
const [isRowHovered, setHoveredRow] = useState(-1);
|
||||
const logContext = useContext(LogContext);
|
||||
const { getStatus } = logContext;
|
||||
const navigate = useNavigate();
|
||||
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const installationId = parseInt(searchParams.get('installation'));
|
||||
const [selectedInstallation, setSelectedInstallation] = useState<number>(-1);
|
||||
|
||||
const handleSelectOneInstallation = (installationID: number): void => {
|
||||
if (selectedInstallation != installationID) {
|
||||
setSelectedInstallation(installationID);
|
||||
navigate(
|
||||
routes.installations +
|
||||
routes.list +
|
||||
'?installation=' +
|
||||
installationID.toString(),
|
||||
{
|
||||
replace: true
|
||||
}
|
||||
);
|
||||
} else {
|
||||
setSelectedInstallation(-1);
|
||||
}
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
setSelectedInstallation(installationId);
|
||||
}, [installationId]);
|
||||
|
||||
const theme = useTheme();
|
||||
|
||||
const findInstallation = (id: number) => {
|
||||
|
@ -54,20 +71,27 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
|||
|
||||
return (
|
||||
<Grid container spacing={1} sx={{ marginTop: 0.1 }}>
|
||||
<Grid item xs={12} md={3}>
|
||||
<Grid item sx={{ display: !installationId ? 'block' : 'none' }}>
|
||||
<Card>
|
||||
<Divider />
|
||||
<TableContainer>
|
||||
<Table>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell padding="checkbox"></TableCell>
|
||||
<TableCell>
|
||||
<FormattedMessage id="name" defaultMessage="Name" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<FormattedMessage id="location" defaultMessage="Location" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<FormattedMessage id="country" defaultMessage="Country" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<FormattedMessage
|
||||
id="order"
|
||||
defaultMessage="Order Values"
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<FormattedMessage id="status" defaultMessage="Status" />
|
||||
</TableCell>
|
||||
|
@ -76,7 +100,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
|||
<TableBody>
|
||||
{props.installations.map((installation) => {
|
||||
const isInstallationSelected =
|
||||
installation.id === selectedInstallation ? true : false;
|
||||
installation.id === selectedInstallation;
|
||||
|
||||
const status = getStatus(installation.id);
|
||||
|
||||
|
@ -100,31 +124,58 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
|||
onMouseEnter={() => handleRowMouseEnter(installation.id)}
|
||||
onMouseLeave={() => handleRowMouseLeave()}
|
||||
>
|
||||
<TableCell padding="checkbox"></TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="body1"
|
||||
variant="body2"
|
||||
fontWeight="bold"
|
||||
color="text.primary"
|
||||
gutterBottom
|
||||
noWrap
|
||||
sx={{ marginTop: '10px' }}
|
||||
sx={{ marginTop: '10px', fontSize: 'small' }}
|
||||
>
|
||||
{installation.name}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="body1"
|
||||
variant="body2"
|
||||
fontWeight="bold"
|
||||
color="text.primary"
|
||||
gutterBottom
|
||||
noWrap
|
||||
sx={{ marginTop: '10px' }}
|
||||
sx={{ marginTop: '10px', fontSize: 'small' }}
|
||||
>
|
||||
{installation.location}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight="bold"
|
||||
color="text.primary"
|
||||
gutterBottom
|
||||
noWrap
|
||||
sx={{ marginTop: '10px', fontSize: 'small' }}
|
||||
>
|
||||
{installation.country}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="body2"
|
||||
fontWeight="bold"
|
||||
color="text.primary"
|
||||
gutterBottom
|
||||
noWrap
|
||||
sx={{ marginTop: '10px', fontSize: 'small' }}
|
||||
>
|
||||
{installation.orderNumbers}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
|
||||
<TableCell>
|
||||
<div
|
||||
style={{
|
||||
|
@ -161,6 +212,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
|||
style={{
|
||||
width: '20px',
|
||||
height: '20px',
|
||||
marginLeft: '2px',
|
||||
borderRadius: '50%',
|
||||
backgroundColor:
|
||||
status === 2
|
||||
|
@ -182,15 +234,11 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
|||
</TableContainer>
|
||||
</Card>
|
||||
</Grid>
|
||||
|
||||
{props.installations.map((installation) => (
|
||||
<Installation
|
||||
key={installation.id}
|
||||
current_installation={findInstallation(installation.id)}
|
||||
type="installation"
|
||||
style={{
|
||||
display: installation.id === selectedInstallation ? 'block' : 'none'
|
||||
}}
|
||||
></Installation>
|
||||
))}
|
||||
</Grid>
|
||||
|
|
|
@ -25,45 +25,56 @@ import Access from '../ManageAccess/Access';
|
|||
import Log from 'src/content/dashboards/Log/Log';
|
||||
import { TimeSpan, UnixTime } from 'src/dataCache/time';
|
||||
import { FetchResult } from 'src/dataCache/dataCache';
|
||||
import { DataRecord } from 'src/dataCache/data';
|
||||
import { S3Access } from 'src/dataCache/S3/S3Access';
|
||||
import { parseCsv } from 'src/content/dashboards/Log/graph.util';
|
||||
import { I_S3Credentials, Notification } from 'src/interfaces/S3Types';
|
||||
import {
|
||||
extractValues,
|
||||
TopologyValues
|
||||
} from 'src/content/dashboards/Log/graph.util';
|
||||
import { Notification } from 'src/interfaces/S3Types';
|
||||
import { LogContext } from 'src/contexts/LogContextProvider';
|
||||
import LiveView from '../LiveView/LiveView';
|
||||
import Topology from '../Topology/Topology';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import Overview from '../Overview/overview';
|
||||
import Configuration from '../Configuration/Configuration';
|
||||
import { fetchData } from 'src/content/dashboards/Installations/fetchData';
|
||||
|
||||
interface singleInstallationProps {
|
||||
current_installation: I_Installation;
|
||||
type: string;
|
||||
style?: React.CSSProperties;
|
||||
}
|
||||
|
||||
function Installation(props: singleInstallationProps) {
|
||||
const tabs = [
|
||||
{
|
||||
value: 'installation',
|
||||
label: (
|
||||
<FormattedMessage id="installation" defaultMessage="Installation" />
|
||||
)
|
||||
value: 'live',
|
||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||
},
|
||||
{
|
||||
value: 'overview',
|
||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||
},
|
||||
,
|
||||
{
|
||||
value: 'manage',
|
||||
label: (
|
||||
<FormattedMessage id="manageAccess" defaultMessage="Manage Access" />
|
||||
)
|
||||
},
|
||||
{
|
||||
value: 'live',
|
||||
label: <FormattedMessage id="live" defaultMessage="Live View" />
|
||||
label: <FormattedMessage id="manage" defaultMessage="Access Management" />
|
||||
},
|
||||
{
|
||||
value: 'log',
|
||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||
},
|
||||
{
|
||||
value: 'information',
|
||||
label: <FormattedMessage id="information" defaultMessage="Information" />
|
||||
},
|
||||
|
||||
{
|
||||
value: 'configuration',
|
||||
label: (
|
||||
<FormattedMessage id="configuration" defaultMessage="Configuration" />
|
||||
)
|
||||
}
|
||||
];
|
||||
const theme = useTheme();
|
||||
const [currentTab, setCurrentTab] = useState<string>(tabs[0].value);
|
||||
const [currentTab, setCurrentTab] = useState<string>('live');
|
||||
const [formValues, setFormValues] = useState(props.current_installation);
|
||||
const requiredFields = ['name', 'region', 'location', 'country'];
|
||||
const context = useContext(UserContext);
|
||||
|
@ -87,37 +98,9 @@ function Installation(props: singleInstallationProps) {
|
|||
const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false);
|
||||
const logContext = useContext(LogContext);
|
||||
const { installationStatus, handleLogWarningOrError, getStatus } = logContext;
|
||||
|
||||
const fetchData = (
|
||||
timestamp: UnixTime,
|
||||
s3Credentials: I_S3Credentials
|
||||
): 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 {
|
||||
return Promise.resolve(FetchResult.notAvailable);
|
||||
}
|
||||
})
|
||||
.catch((e) => {
|
||||
return Promise.resolve(FetchResult.tryLater);
|
||||
});
|
||||
}
|
||||
};
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const installationId = parseInt(searchParams.get('installation'));
|
||||
const [values, setValues] = useState<TopologyValues | null>(null);
|
||||
|
||||
if (formValues == undefined) {
|
||||
return null;
|
||||
|
@ -156,77 +139,127 @@ function Installation(props: singleInstallationProps) {
|
|||
return true;
|
||||
};
|
||||
|
||||
const S3data = {
|
||||
s3Region: props.current_installation.s3Region,
|
||||
s3Provider: props.current_installation.s3Provider,
|
||||
s3Key: props.current_installation.s3Key,
|
||||
s3Secret: props.current_installation.s3Secret
|
||||
};
|
||||
|
||||
const s3Bucket =
|
||||
props.current_installation.id.toString() +
|
||||
'-3e5b3069-214a-43ee-8d85-57d72000c19d';
|
||||
|
||||
const s3Credentials = { s3Bucket, ...S3data };
|
||||
|
||||
useEffect(() => {
|
||||
let isMounted = true;
|
||||
setFormValues(props.current_installation);
|
||||
|
||||
const S3data = {
|
||||
s3Region: props.current_installation.s3Region,
|
||||
s3Provider: props.current_installation.s3Provider,
|
||||
s3Key: props.current_installation.s3Key,
|
||||
s3Secret: props.current_installation.s3Secret
|
||||
};
|
||||
|
||||
const s3Bucket =
|
||||
props.current_installation.id.toString() +
|
||||
'-3e5b3069-214a-43ee-8d85-57d72000c19d';
|
||||
const s3Credentials = { s3Bucket, ...S3data };
|
||||
|
||||
setErrorLoadingS3Data(false);
|
||||
|
||||
const interval = setInterval(() => {
|
||||
const fetchDataPeriodically = async () => {
|
||||
const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20));
|
||||
const date = now.toDate();
|
||||
|
||||
fetchData(now, s3Credentials)
|
||||
.then((res) => {
|
||||
try {
|
||||
const res = await fetchData(now, s3Credentials);
|
||||
|
||||
if (installationId == 2) {
|
||||
console.log('Fetched data from unix timestamp ' + now);
|
||||
const newWarnings: Notification[] = [];
|
||||
const newErrors: Notification[] = [];
|
||||
if (res === FetchResult.notAvailable || res == FetchResult.tryLater) {
|
||||
setErrorLoadingS3Data(true);
|
||||
handleLogWarningOrError(props.current_installation.id, -1);
|
||||
} else {
|
||||
setErrorLoadingS3Data(false);
|
||||
for (const key in res) {
|
||||
if (
|
||||
(res.hasOwnProperty(key) &&
|
||||
key.includes('/Alarms') &&
|
||||
res[key].value != '') ||
|
||||
(key.includes('/Warnings') && res[key].value != '')
|
||||
) {
|
||||
if (key.includes('/Warnings')) {
|
||||
newWarnings.push({
|
||||
key,
|
||||
value: res[key].value.toString()
|
||||
});
|
||||
} else if (key.includes('/Alarms')) {
|
||||
newErrors.push({
|
||||
key,
|
||||
value: res[key].value.toString()
|
||||
});
|
||||
}
|
||||
}
|
||||
if (!isMounted) {
|
||||
return;
|
||||
}
|
||||
|
||||
const newWarnings: Notification[] = [];
|
||||
const newErrors: Notification[] = [];
|
||||
|
||||
if (res === FetchResult.notAvailable || res === FetchResult.tryLater) {
|
||||
setErrorLoadingS3Data(true);
|
||||
handleLogWarningOrError(props.current_installation.id, -1);
|
||||
} else {
|
||||
setErrorLoadingS3Data(false);
|
||||
setValues(
|
||||
extractValues({
|
||||
time: now,
|
||||
value: res
|
||||
})
|
||||
);
|
||||
|
||||
for (const key in res) {
|
||||
if (
|
||||
(res.hasOwnProperty(key) &&
|
||||
key.includes('/Alarms') &&
|
||||
res[key].value !== '') ||
|
||||
(key.includes('/Warnings') && res[key].value !== '')
|
||||
) {
|
||||
if (key.includes('/Warnings')) {
|
||||
newWarnings.push({
|
||||
device: key.substring(1, key.lastIndexOf('/')),
|
||||
description: res[key].value.toString(),
|
||||
date:
|
||||
date.getFullYear() +
|
||||
'-' +
|
||||
date.getMonth() +
|
||||
'-' +
|
||||
date.getDay(),
|
||||
time:
|
||||
date.getHours() +
|
||||
':' +
|
||||
date.getMinutes() +
|
||||
':' +
|
||||
date.getSeconds()
|
||||
});
|
||||
} else if (key.includes('/Alarms')) {
|
||||
newErrors.push({
|
||||
device: key.substring(1, key.lastIndexOf('/')),
|
||||
description: res[key].value.toString(),
|
||||
date:
|
||||
date.getFullYear() +
|
||||
'-' +
|
||||
date.getMonth() +
|
||||
'-' +
|
||||
date.getDay(),
|
||||
time:
|
||||
date.getHours() +
|
||||
':' +
|
||||
date.getMinutes() +
|
||||
':' +
|
||||
date.getSeconds()
|
||||
});
|
||||
}
|
||||
}
|
||||
setWarnings(newWarnings);
|
||||
setErrors(newErrors);
|
||||
|
||||
if (newErrors.length > 0) {
|
||||
handleLogWarningOrError(props.current_installation.id, 2);
|
||||
} else if (newWarnings.length > 0) {
|
||||
handleLogWarningOrError(props.current_installation.id, 1);
|
||||
} else {
|
||||
handleLogWarningOrError(props.current_installation.id, 0);
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
setErrorLoadingS3Data(true);
|
||||
});
|
||||
}, 2000);
|
||||
|
||||
setWarnings(newWarnings);
|
||||
setErrors(newErrors);
|
||||
|
||||
if (newErrors.length > 0) {
|
||||
handleLogWarningOrError(props.current_installation.id, 2);
|
||||
} else if (newWarnings.length > 0) {
|
||||
handleLogWarningOrError(props.current_installation.id, 1);
|
||||
} else {
|
||||
handleLogWarningOrError(props.current_installation.id, 0);
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
setErrorLoadingS3Data(true);
|
||||
}
|
||||
};
|
||||
|
||||
const interval = setInterval(fetchDataPeriodically, 2000);
|
||||
|
||||
// Cleanup function to cancel interval and update isMounted when unmounted
|
||||
return () => {
|
||||
isMounted = false;
|
||||
clearInterval(interval);
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid item xs={12} md={9} style={props.style}>
|
||||
if (installationId == props.current_installation.id) {
|
||||
return (
|
||||
<Grid item xs={12} md={12}>
|
||||
<TabsContainerWrapper>
|
||||
<Tabs
|
||||
onChange={handleTabsChange}
|
||||
|
@ -249,7 +282,7 @@ function Installation(props: singleInstallationProps) {
|
|||
alignItems="stretch"
|
||||
spacing={0}
|
||||
>
|
||||
{currentTab === 'installation' && (
|
||||
{currentTab === 'information' && (
|
||||
<Container maxWidth="xl">
|
||||
<Grid
|
||||
container
|
||||
|
@ -350,6 +383,44 @@ function Installation(props: singleInstallationProps) {
|
|||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
{currentUser.hasWriteAccess && (
|
||||
<>
|
||||
<div>
|
||||
<TextField
|
||||
label="S3 Write Key"
|
||||
name="s3writekey"
|
||||
value={formValues.s3WriteKey}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
label="S3 Write Secret Key"
|
||||
name="s3writesecretkey"
|
||||
value={formValues.s3WriteSecret}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<TextField
|
||||
label="S3 Bucket Name"
|
||||
name="s3writesecretkey"
|
||||
value={
|
||||
formValues.id +
|
||||
'-3e5b3069-214a-43ee-8d85-57d72000c19d'
|
||||
}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
|
@ -410,7 +481,7 @@ function Installation(props: singleInstallationProps) {
|
|||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => setError(false)} // Set error state to false on click
|
||||
onClick={() => setError(false)}
|
||||
sx={{ marginLeft: '4px' }}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
|
@ -448,6 +519,12 @@ function Installation(props: singleInstallationProps) {
|
|||
</Grid>
|
||||
</Container>
|
||||
)}
|
||||
{currentTab === 'overview' && (
|
||||
<Overview s3Credentials={s3Credentials}></Overview>
|
||||
)}
|
||||
{currentTab === 'configuration' && currentUser.hasWriteAccess && (
|
||||
<Configuration values={values}></Configuration>
|
||||
)}
|
||||
{currentTab === 'manage' && currentUser.hasWriteAccess && (
|
||||
<AccessContextProvider>
|
||||
<Access
|
||||
|
@ -456,13 +533,7 @@ function Installation(props: singleInstallationProps) {
|
|||
></Access>
|
||||
</AccessContextProvider>
|
||||
)}
|
||||
{currentTab === 'live' && (
|
||||
<LiveView
|
||||
warnings={warnings}
|
||||
errors={errors}
|
||||
errorLoadingS3Data={errorLoadingS3Data}
|
||||
></LiveView>
|
||||
)}
|
||||
{currentTab === 'live' && <Topology values={values}></Topology>}
|
||||
{currentTab === 'log' && (
|
||||
<Log
|
||||
warnings={warnings}
|
||||
|
@ -473,8 +544,10 @@ function Installation(props: singleInstallationProps) {
|
|||
</Grid>
|
||||
</Card>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default Installation;
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { useContext, useEffect, useState } from 'react';
|
||||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import {
|
||||
FormControl,
|
||||
Grid,
|
||||
|
@ -9,32 +9,42 @@ import {
|
|||
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
||||
import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone';
|
||||
import FlatInstallationView from 'src/content/dashboards/Installations/FlatInstallationView';
|
||||
import LogContextProvider from 'src/contexts/LogContextProvider';
|
||||
|
||||
function InstallationSearch() {
|
||||
const theme = useTheme();
|
||||
const [searchTerm, setSearchTerm] = useState('');
|
||||
const { data, fetchAllInstallations } = useContext(InstallationsContext);
|
||||
const { installations, fetchAllInstallations } =
|
||||
useContext(InstallationsContext);
|
||||
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const installationId = parseInt(searchParams.get('installation'));
|
||||
|
||||
useEffect(() => {
|
||||
fetchAllInstallations();
|
||||
}, []);
|
||||
|
||||
const [filteredData, setFilteredData] = useState(data);
|
||||
const [filteredData, setFilteredData] = useState(installations);
|
||||
|
||||
useEffect(() => {
|
||||
const filtered = data.filter(
|
||||
const filtered = installations.filter(
|
||||
(item) =>
|
||||
item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||
item.location.toLowerCase().includes(searchTerm.toLowerCase())
|
||||
);
|
||||
setFilteredData(filtered);
|
||||
}, [searchTerm, data]);
|
||||
}, [searchTerm, installations]);
|
||||
|
||||
return (
|
||||
<>
|
||||
<Grid container spacing={4}>
|
||||
<Grid item xs={12} md={3}>
|
||||
<FormControl variant="outlined" fullWidth>
|
||||
<Grid container>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
md={4}
|
||||
sx={{ display: !installationId ? 'block' : 'none' }}
|
||||
>
|
||||
<FormControl variant="outlined">
|
||||
<TextField
|
||||
placeholder="Search"
|
||||
value={searchTerm}
|
||||
|
@ -51,7 +61,9 @@ function InstallationSearch() {
|
|||
</FormControl>
|
||||
</Grid>
|
||||
</Grid>
|
||||
<FlatInstallationView installations={filteredData} />
|
||||
<LogContextProvider>
|
||||
<FlatInstallationView installations={filteredData} />
|
||||
</LogContextProvider>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
|
|
@ -1,25 +1,30 @@
|
|||
import { ChangeEvent, useState } from 'react';
|
||||
import React, { ChangeEvent, useEffect, useState } from 'react';
|
||||
import Footer from 'src/components/Footer';
|
||||
import { Box, Card, Container, Grid, Tab, Tabs, useTheme } from '@mui/material';
|
||||
import InstallationsContextProvider from 'src/contexts/InstallationsContextProvider';
|
||||
import InstallationSearch from './InstallationSearch';
|
||||
import { Card, Container, Grid, Tab, Tabs, useTheme } from '@mui/material';
|
||||
import ListIcon from '@mui/icons-material/List';
|
||||
import AccountTreeIcon from '@mui/icons-material/AccountTree';
|
||||
import InstallationTree from '../Tree/InstallationTree';
|
||||
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
||||
import UsersContextProvider from 'src/contexts/UsersContextProvider';
|
||||
import LogContextProvider from '../../../contexts/LogContextProvider';
|
||||
import {
|
||||
Link,
|
||||
Route,
|
||||
Routes,
|
||||
useLocation,
|
||||
useNavigate
|
||||
} from 'react-router-dom';
|
||||
import FlatView from './flatView';
|
||||
import TreeView from '../Tree/treeView';
|
||||
import routes from 'src/Resources/routes.json';
|
||||
|
||||
function InstallationTabs() {
|
||||
const theme = useTheme();
|
||||
|
||||
const [currentTab, setCurrentTab] = useState<string>('flat');
|
||||
const location = useLocation();
|
||||
const navigate = useNavigate();
|
||||
const tabs = [
|
||||
{
|
||||
value: 'flat',
|
||||
value: 'list',
|
||||
label: 'Flat view',
|
||||
icon: <ListIcon id="mode-toggle-button-list-icon" />,
|
||||
component: {}
|
||||
icon: <ListIcon id="mode-toggle-button-list-icon" />
|
||||
},
|
||||
{
|
||||
value: 'tree',
|
||||
|
@ -27,9 +32,25 @@ function InstallationTabs() {
|
|||
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
||||
}
|
||||
];
|
||||
const [currentTab, setCurrentTab] = useState<string>('list');
|
||||
|
||||
useEffect(() => {
|
||||
//console.log(location.pathname);
|
||||
if (
|
||||
location.pathname === '/installations' ||
|
||||
location.pathname === '/installations/'
|
||||
) {
|
||||
navigate(routes.installations + routes.list, {
|
||||
replace: true
|
||||
});
|
||||
} else if (location.pathname === '/installations/tree') {
|
||||
setCurrentTab('tree');
|
||||
}
|
||||
}, [location.pathname, navigate]);
|
||||
|
||||
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
||||
setCurrentTab(value);
|
||||
navigate(value);
|
||||
};
|
||||
|
||||
return (
|
||||
|
@ -45,40 +66,30 @@ function InstallationTabs() {
|
|||
indicatorColor="primary"
|
||||
>
|
||||
{tabs.map((tab) => (
|
||||
<Tab key={tab.value} value={tab.value} icon={tab.icon} />
|
||||
<Tab
|
||||
key={tab.value}
|
||||
value={tab.value}
|
||||
icon={tab.icon}
|
||||
component={Link}
|
||||
to={routes[tab.value]}
|
||||
/>
|
||||
))}
|
||||
</Tabs>
|
||||
</TabsContainerWrapper>
|
||||
<InstallationsContextProvider>
|
||||
<LogContextProvider>
|
||||
<Card variant="outlined">
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="stretch"
|
||||
spacing={0}
|
||||
>
|
||||
{currentTab === 'tree' && (
|
||||
<>
|
||||
<Grid item xs={12}>
|
||||
<Box p={4}>
|
||||
<InstallationTree />
|
||||
</Box>
|
||||
</Grid>
|
||||
</>
|
||||
)}
|
||||
{currentTab === 'flat' && (
|
||||
<Grid item xs={12}>
|
||||
<Box p={4}>
|
||||
<InstallationSearch />
|
||||
</Box>
|
||||
</Grid>
|
||||
)}
|
||||
</Grid>
|
||||
</Card>
|
||||
</LogContextProvider>
|
||||
</InstallationsContextProvider>
|
||||
<Card variant="outlined">
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="stretch"
|
||||
spacing={0}
|
||||
>
|
||||
<Routes>
|
||||
<Route path={routes.list + '*'} element={<FlatView />} />
|
||||
<Route path={routes.tree + '*'} element={<TreeView />} />
|
||||
</Routes>
|
||||
</Grid>
|
||||
</Card>
|
||||
</Container>
|
||||
<Footer />
|
||||
</UsersContextProvider>
|
||||
|
|
|
@ -13,6 +13,7 @@ import { Close as CloseIcon } from '@mui/icons-material';
|
|||
import { I_Installation } from 'src/interfaces/InstallationTypes';
|
||||
import { TokenContext } from 'src/contexts/tokenContext';
|
||||
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
interface installationFormProps {
|
||||
cancel: () => void;
|
||||
|
@ -96,7 +97,12 @@ function installationForm(props: installationFormProps) {
|
|||
>
|
||||
<div>
|
||||
<TextField
|
||||
label="Customer Name"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="customerName"
|
||||
defaultMessage="Customer Name"
|
||||
/>
|
||||
}
|
||||
name="name"
|
||||
value={formValues.name}
|
||||
onChange={handleChange}
|
||||
|
@ -107,7 +113,7 @@ function installationForm(props: installationFormProps) {
|
|||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
label="Region"
|
||||
label={<FormattedMessage id="region" defaultMessage="Region" />}
|
||||
name="region"
|
||||
value={formValues.region}
|
||||
onChange={handleChange}
|
||||
|
@ -118,7 +124,9 @@ function installationForm(props: installationFormProps) {
|
|||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
label="Location"
|
||||
label={
|
||||
<FormattedMessage id="location" defaultMessage="Location" />
|
||||
}
|
||||
name="location"
|
||||
value={formValues.location}
|
||||
onChange={handleChange}
|
||||
|
@ -130,7 +138,9 @@ function installationForm(props: installationFormProps) {
|
|||
|
||||
<div>
|
||||
<TextField
|
||||
label="Country"
|
||||
label={
|
||||
<FormattedMessage id="country" defaultMessage="Country" />
|
||||
}
|
||||
name="country"
|
||||
value={formValues.country}
|
||||
onChange={handleChange}
|
||||
|
@ -141,7 +151,12 @@ function installationForm(props: installationFormProps) {
|
|||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
label="Order Numbers"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="orderNumbers"
|
||||
defaultMessage="Order Numbers"
|
||||
/>
|
||||
}
|
||||
name="orderNumbers"
|
||||
value={formValues.orderNumbers}
|
||||
onChange={handleChange}
|
||||
|
@ -164,7 +179,7 @@ function installationForm(props: installationFormProps) {
|
|||
}}
|
||||
disabled={!areRequiredFieldsFilled()}
|
||||
>
|
||||
Submit
|
||||
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
|
@ -174,7 +189,7 @@ function installationForm(props: installationFormProps) {
|
|||
marginLeft: '10px'
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
<FormattedMessage id="cancel" defaultMessage="Cancel" />
|
||||
</Button>
|
||||
|
||||
{loading && (
|
||||
|
@ -195,11 +210,14 @@ function installationForm(props: installationFormProps) {
|
|||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
An error has occurred
|
||||
<FormattedMessage
|
||||
id="errorOccured"
|
||||
defaultMessage="An error has occured"
|
||||
/>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => setError(false)} // Set error state to false on click
|
||||
onClick={() => setError(false)}
|
||||
sx={{ marginLeft: '4px' }}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
|
|
|
@ -1,184 +0,0 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Container,
|
||||
Divider,
|
||||
Grid,
|
||||
IconButton,
|
||||
ListItem,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import ListItemAvatar from '@mui/material/ListItemAvatar';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import ErrorIcon from '@mui/icons-material/Error';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { Notification } from 'src/interfaces/S3Types';
|
||||
|
||||
interface LiveViewProps {
|
||||
warnings: Notification[];
|
||||
errors: Notification[];
|
||||
errorLoadingS3Data: boolean;
|
||||
}
|
||||
|
||||
function LiveView(props: LiveViewProps) {
|
||||
const theme = useTheme();
|
||||
|
||||
return (
|
||||
<Container maxWidth="xl">
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={12}>
|
||||
{props.errors.length > 0 &&
|
||||
props.errors.map((error) => {
|
||||
return (
|
||||
<Fragment key={error.key + error.value}>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar
|
||||
sx={{
|
||||
backgroundColor: 'red',
|
||||
width: 28,
|
||||
height: 28
|
||||
}}
|
||||
>
|
||||
<ErrorIcon
|
||||
sx={{
|
||||
color: 'white',
|
||||
width: 16,
|
||||
height: 16
|
||||
}}
|
||||
/>
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography variant="body1" sx={{ fontWeight: 'bold' }}>
|
||||
{'Error from: ' +
|
||||
error.key +
|
||||
' device: ' +
|
||||
error.value}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
{!props.errorLoadingS3Data && props.errors.length == 0 && (
|
||||
<Alert
|
||||
severity="error"
|
||||
sx={{
|
||||
ml: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: '20px',
|
||||
marginBottom: '20px'
|
||||
}}
|
||||
>
|
||||
There are no errors
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
sx={{ marginLeft: '4px' }}
|
||||
></IconButton>
|
||||
</Alert>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} md={12}>
|
||||
{props.errors.length > 0 && props.warnings.length > 0 && (
|
||||
<Divider
|
||||
sx={{
|
||||
borderBottomWidth: '2px',
|
||||
borderColor: '#ffffff',
|
||||
'&.MuiDivider-root': {
|
||||
backgroundColor: theme.palette.secondary.light // Change the color as needed
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} md={12} style={{ marginBottom: '20px' }}>
|
||||
{props.errorLoadingS3Data && (
|
||||
<Alert
|
||||
severity="error"
|
||||
sx={{
|
||||
ml: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: '20px'
|
||||
//marginBottom: '20px'
|
||||
}}
|
||||
>
|
||||
Cannot load logging data
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
sx={{ marginLeft: '4px' }}
|
||||
></IconButton>
|
||||
</Alert>
|
||||
)}
|
||||
{props.warnings.length > 0 &&
|
||||
props.warnings.map((warning) => {
|
||||
return (
|
||||
<Fragment key={warning.key + warning.value}>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar
|
||||
sx={{
|
||||
backgroundColor: '#ffc04d',
|
||||
width: 28,
|
||||
height: 28
|
||||
}}
|
||||
>
|
||||
<WarningIcon
|
||||
sx={{
|
||||
color: '#ffffff',
|
||||
width: 16,
|
||||
height: 16
|
||||
}}
|
||||
/>
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography variant="body1" sx={{ fontWeight: 'bold' }}>
|
||||
{'Warning from: ' +
|
||||
warning.key +
|
||||
' device: ' +
|
||||
warning.value}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
{!props.errorLoadingS3Data && props.warnings.length == 0 && (
|
||||
<Alert
|
||||
severity="error"
|
||||
sx={{
|
||||
ml: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginBottom: '20px'
|
||||
}}
|
||||
>
|
||||
There are no warnings
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
sx={{ marginLeft: '4px' }}
|
||||
></IconButton>
|
||||
</Alert>
|
||||
)}
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default LiveView;
|
|
@ -1,20 +1,24 @@
|
|||
import React, { Fragment } from 'react';
|
||||
import React from 'react';
|
||||
import {
|
||||
Alert,
|
||||
Card,
|
||||
Container,
|
||||
Divider,
|
||||
Grid,
|
||||
IconButton,
|
||||
ListItem,
|
||||
Table,
|
||||
TableBody,
|
||||
TableCell,
|
||||
TableContainer,
|
||||
TableHead,
|
||||
TableRow,
|
||||
useTheme
|
||||
} from '@mui/material';
|
||||
import ListItemAvatar from '@mui/material/ListItemAvatar';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
import WarningIcon from '@mui/icons-material/Warning';
|
||||
import ErrorIcon from '@mui/icons-material/Error';
|
||||
import Typography from '@mui/material/Typography';
|
||||
import { Notification } from 'src/interfaces/S3Types';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
import ErrorIcon from '@mui/icons-material/Error';
|
||||
|
||||
interface LogProps {
|
||||
warnings: Notification[];
|
||||
|
@ -29,43 +33,171 @@ function Log(props: LogProps) {
|
|||
<Container maxWidth="xl">
|
||||
<Grid container>
|
||||
<Grid item xs={12} md={12}>
|
||||
{props.errors.length > 0 &&
|
||||
props.errors.map((error) => {
|
||||
return (
|
||||
<Fragment key={error.key + error.value}>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar
|
||||
sx={{
|
||||
backgroundColor: 'red',
|
||||
width: 28,
|
||||
height: 28
|
||||
}}
|
||||
>
|
||||
<ErrorIcon
|
||||
sx={{
|
||||
color: 'white',
|
||||
width: 16,
|
||||
height: 16
|
||||
}}
|
||||
{(props.errors.length > 0 || props.warnings.length > 0) && (
|
||||
<Card>
|
||||
<Divider />
|
||||
<TableContainer>
|
||||
<Table sx={{ height: 10 }}>
|
||||
<TableHead>
|
||||
<TableRow>
|
||||
<TableCell>
|
||||
<FormattedMessage id="type" defaultMessage="Type" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<FormattedMessage id="device" defaultMessage="Device" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<FormattedMessage
|
||||
id="description"
|
||||
defaultMessage="Description"
|
||||
/>
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography variant="body1" sx={{ fontWeight: 'bold' }}>
|
||||
{'Error from: ' +
|
||||
error.key +
|
||||
' device: ' +
|
||||
error.value}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<FormattedMessage id="date" defaultMessage="Date" />
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<FormattedMessage id="time" defaultMessage="Time" />
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
</TableHead>
|
||||
<TableBody>
|
||||
{props.errors.map((error, index) => {
|
||||
return (
|
||||
<TableRow hover key={index}>
|
||||
<TableCell>
|
||||
<ErrorIcon
|
||||
sx={{
|
||||
color: 'red',
|
||||
width: 25,
|
||||
height: 25,
|
||||
marginLeft: '5px',
|
||||
marginTop: '8px'
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight="bold"
|
||||
color="text.primary"
|
||||
gutterBottom
|
||||
noWrap
|
||||
sx={{ marginTop: '10px' }}
|
||||
>
|
||||
{error.device}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight="bold"
|
||||
color="text.primary"
|
||||
gutterBottom
|
||||
noWrap
|
||||
sx={{ marginTop: '10px' }}
|
||||
>
|
||||
{error.description}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight="bold"
|
||||
color="text.primary"
|
||||
gutterBottom
|
||||
noWrap
|
||||
sx={{ marginTop: '10px' }}
|
||||
>
|
||||
{error.date}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight="bold"
|
||||
color="text.primary"
|
||||
gutterBottom
|
||||
noWrap
|
||||
sx={{ marginTop: '10px' }}
|
||||
>
|
||||
{error.time}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
|
||||
{props.warnings.map((warning, index) => {
|
||||
return (
|
||||
<TableRow hover key={index}>
|
||||
<TableCell>
|
||||
<WarningIcon
|
||||
sx={{
|
||||
color: '#ffc04d',
|
||||
width: 25,
|
||||
height: 25,
|
||||
marginLeft: '5px',
|
||||
marginTop: '8px'
|
||||
}}
|
||||
/>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight="bold"
|
||||
color="text.primary"
|
||||
gutterBottom
|
||||
noWrap
|
||||
sx={{ marginTop: '10px' }}
|
||||
>
|
||||
{warning.device}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight="bold"
|
||||
color="text.primary"
|
||||
gutterBottom
|
||||
noWrap
|
||||
sx={{ marginTop: '10px' }}
|
||||
>
|
||||
{warning.description}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight="bold"
|
||||
color="text.primary"
|
||||
gutterBottom
|
||||
noWrap
|
||||
sx={{ marginTop: '10px' }}
|
||||
>
|
||||
{warning.date}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
<TableCell>
|
||||
<Typography
|
||||
variant="body1"
|
||||
fontWeight="bold"
|
||||
color="text.primary"
|
||||
gutterBottom
|
||||
noWrap
|
||||
sx={{ marginTop: '10px' }}
|
||||
>
|
||||
{warning.time}
|
||||
</Typography>
|
||||
</TableCell>
|
||||
</TableRow>
|
||||
);
|
||||
})}
|
||||
</TableBody>
|
||||
</Table>
|
||||
</TableContainer>
|
||||
</Card>
|
||||
)}
|
||||
|
||||
{!props.errorLoadingS3Data && props.errors.length == 0 && (
|
||||
<Alert
|
||||
severity="error"
|
||||
|
@ -77,7 +209,10 @@ function Log(props: LogProps) {
|
|||
marginBottom: '20px'
|
||||
}}
|
||||
>
|
||||
There are no errors
|
||||
<FormattedMessage
|
||||
id="noerrors"
|
||||
defaultMessage="There are no errors"
|
||||
/>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
|
@ -86,19 +221,7 @@ function Log(props: LogProps) {
|
|||
</Alert>
|
||||
)}
|
||||
</Grid>
|
||||
<Grid item xs={12} md={12}>
|
||||
{props.errors.length > 0 && props.warnings.length > 0 && (
|
||||
<Divider
|
||||
sx={{
|
||||
borderBottomWidth: '2px',
|
||||
borderColor: '#ffffff',
|
||||
'&.MuiDivider-root': {
|
||||
backgroundColor: theme.palette.secondary.light // Change the color as needed
|
||||
}
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={12} style={{ marginBottom: '20px' }}>
|
||||
{props.errorLoadingS3Data && (
|
||||
<Alert
|
||||
|
@ -108,10 +231,12 @@ function Log(props: LogProps) {
|
|||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: '20px'
|
||||
//marginBottom: '20px'
|
||||
}}
|
||||
>
|
||||
Cannot load logging data
|
||||
<FormattedMessage
|
||||
id="cannotloadloggingdata"
|
||||
defaultMessage="Cannot load logging data"
|
||||
/>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
|
@ -119,43 +244,6 @@ function Log(props: LogProps) {
|
|||
></IconButton>
|
||||
</Alert>
|
||||
)}
|
||||
{props.warnings.length > 0 &&
|
||||
props.warnings.map((warning) => {
|
||||
return (
|
||||
<Fragment key={warning.key + warning.value}>
|
||||
<ListItem>
|
||||
<ListItemAvatar>
|
||||
<Avatar
|
||||
sx={{
|
||||
backgroundColor: '#ffc04d',
|
||||
width: 28,
|
||||
height: 28
|
||||
}}
|
||||
>
|
||||
<WarningIcon
|
||||
sx={{
|
||||
color: '#ffffff',
|
||||
width: 16,
|
||||
height: 16
|
||||
}}
|
||||
/>
|
||||
</Avatar>
|
||||
</ListItemAvatar>
|
||||
<ListItemText
|
||||
primary={
|
||||
<Typography variant="body1" sx={{ fontWeight: 'bold' }}>
|
||||
{'Warning from: ' +
|
||||
warning.key +
|
||||
' device: ' +
|
||||
warning.value}
|
||||
</Typography>
|
||||
}
|
||||
/>
|
||||
</ListItem>
|
||||
<Divider />
|
||||
</Fragment>
|
||||
);
|
||||
})}
|
||||
|
||||
{!props.errorLoadingS3Data && props.warnings.length == 0 && (
|
||||
<Alert
|
||||
|
@ -163,11 +251,14 @@ function Log(props: LogProps) {
|
|||
sx={{
|
||||
ml: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginBottom: '20px'
|
||||
alignItems: 'center'
|
||||
//marginBottom: '20px'
|
||||
}}
|
||||
>
|
||||
There are no warnings
|
||||
<FormattedMessage
|
||||
id="nowarnings"
|
||||
defaultMessage="There are no warnings"
|
||||
/>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { DataRecord } from 'src/dataCache/data';
|
||||
import { DataPoint, DataRecord } from 'src/dataCache/data';
|
||||
import { TimeRange, UnixTime } from 'src/dataCache/time';
|
||||
|
||||
export interface I_CsvEntry {
|
||||
value: string | number;
|
||||
|
@ -22,3 +23,173 @@ export const parseCsv = (text: string): DataRecord => {
|
|||
})
|
||||
.reduce((acc, current) => ({ ...acc, ...current }), {} as DataRecord);
|
||||
};
|
||||
|
||||
export interface I_BoxDataValue {
|
||||
unit: string;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
export type BoxData = {
|
||||
label: string;
|
||||
values: I_BoxDataValue[];
|
||||
};
|
||||
|
||||
export type TopologyValues = {
|
||||
grid: BoxData;
|
||||
gridToAcInConnection: BoxData;
|
||||
gridBus: BoxData;
|
||||
islandBus: BoxData;
|
||||
dcBus: BoxData;
|
||||
dcBusToDcDcConnection: BoxData;
|
||||
dcDCToBatteryConnection: BoxData;
|
||||
battery: BoxData;
|
||||
dcBusToLoadOnDcConnection: BoxData;
|
||||
islandBusToLoadOnIslandBusConnection: BoxData;
|
||||
gridBusToPvOnGridbusConnection: BoxData;
|
||||
gridBusToLoadOnGridBusConnection: BoxData;
|
||||
inverter: BoxData;
|
||||
dcDc: BoxData;
|
||||
islandBusToInverter: BoxData;
|
||||
inverterToDcBus: BoxData;
|
||||
gridBusToIslandBusConnection: BoxData;
|
||||
pvOnDcBusToDcBusConnection: BoxData;
|
||||
pvOnIslandBusToIslandBusConnection: BoxData;
|
||||
minimumSoC: BoxData;
|
||||
installedDcDcPower: BoxData;
|
||||
gridSetPoint: BoxData;
|
||||
maximumDischargePower: BoxData;
|
||||
calibrationChargeForced: BoxData;
|
||||
};
|
||||
|
||||
type TopologyPaths = { [key in keyof TopologyValues]: string[] };
|
||||
|
||||
export const topologyPaths: TopologyPaths = {
|
||||
grid: [
|
||||
'/GridMeter/Ac/L1/Power/Active',
|
||||
'/GridMeter/Ac/L2/Power/Active',
|
||||
'/GridMeter/Ac/L3/Power/Active'
|
||||
],
|
||||
gridToAcInConnection: ['/GridMeter/Ac/Power/Active'],
|
||||
|
||||
gridBus: [
|
||||
'/GridMeter/Ac/L1/Power/Active',
|
||||
'/GridMeter/Ac/L2/Power/Active',
|
||||
'/GridMeter/Ac/L3/Power/Active'
|
||||
],
|
||||
gridBusToPvOnGridbusConnection: ['/PvOnAcGrid/Power/Active'],
|
||||
|
||||
gridBusToLoadOnGridBusConnection: ['/LoadOnAcGrid/Power/Active'],
|
||||
gridBusToIslandBusConnection: ['/AcGridToAcIsland/Power/Active'],
|
||||
|
||||
islandBus: [
|
||||
'/AcDc/Ac/L1/Power/Active',
|
||||
'/AcDc/Ac/L2/Power/Active',
|
||||
'/AcDc/Ac/L3/Power/Active'
|
||||
],
|
||||
islandBusToLoadOnIslandBusConnection: ['/LoadOnAcIsland/Power/Active'],
|
||||
islandBusToInverter: ['/AcDc/Dc/Power'],
|
||||
pvOnIslandBusToIslandBusConnection: ['/PvOnAcIsland/Power/Active'],
|
||||
|
||||
inverter: [
|
||||
'/AcDc/Devices/1/Status/Ac/Power/Active',
|
||||
'/AcDc/Devices/2/Status/Ac/Power/Active',
|
||||
'/AcDc/Devices/3/Status/Ac/Power/Active',
|
||||
'/AcDc/Devices/4/Status/Ac/Power/Active'
|
||||
],
|
||||
inverterToDcBus: ['/AcDc/Dc/Power'],
|
||||
|
||||
dcBus: ['/DcDc/Dc/Link/Voltage'],
|
||||
dcBusToDcDcConnection: ['/DcDc/Dc/Link/Power'],
|
||||
pvOnDcBusToDcBusConnection: ['/PvOnDc/Dc/Power'],
|
||||
dcBusToLoadOnDcConnection: ['/LoadOnDc/Power'],
|
||||
|
||||
dcDc: ['/DcDc/Dc/Battery/Voltage'],
|
||||
|
||||
dcDCToBatteryConnection: ['/DcDc/Dc/Link/Power'],
|
||||
battery: [
|
||||
'/Battery/Soc',
|
||||
'/Battery/Dc/Voltage',
|
||||
'/Battery/Dc/Current',
|
||||
'/Battery/Temperature',
|
||||
'/Battery/Devices/1/Dc/Voltage',
|
||||
'/Battery/Devices/2/Dc/Voltage',
|
||||
'/Battery/Devices/3/Dc/Voltage',
|
||||
'/Battery/Devices/4/Dc/Voltage',
|
||||
'/Battery/Devices/5/Dc/Voltage',
|
||||
'/Battery/Devices/6/Dc/Voltage',
|
||||
'/Battery/Devices/7/Dc/Voltage',
|
||||
'/Battery/Devices/8/Dc/Voltage',
|
||||
'/Battery/Devices/9/Dc/Voltage',
|
||||
'/Battery/Devices/10/Dc/Voltage'
|
||||
],
|
||||
|
||||
minimumSoC: ['/Config/MinSoc'],
|
||||
installedDcDcPower: ['/DcDc/SystemControl/TargetSlave'],
|
||||
gridSetPoint: ['/Config/GridSetPoint'],
|
||||
maximumDischargePower: ['/Config/MaxBatteryDischargingCurrent'],
|
||||
calibrationChargeForced: ['/Config/ForceCalibrationCharge']
|
||||
};
|
||||
|
||||
export const extractValues = (
|
||||
timeSeriesData: DataPoint
|
||||
): TopologyValues | null => {
|
||||
const extractedValues: TopologyValues = {} as TopologyValues;
|
||||
|
||||
for (const topologyKey of Object.keys(topologyPaths)) {
|
||||
const paths = topologyPaths[topologyKey];
|
||||
let topologyValues: { unit: string; value: string | number }[] = [];
|
||||
//console.log('paths is ', paths);
|
||||
|
||||
// Check if any of the specified paths exist in the dataRecord
|
||||
for (const path of paths) {
|
||||
//console.log(' path is ', path);
|
||||
if (timeSeriesData.value.hasOwnProperty(path)) {
|
||||
//console.log('matching path is ', path);
|
||||
//console.log(timeSeriesData.value[path]);
|
||||
topologyValues.push({
|
||||
unit: timeSeriesData.value[path].unit,
|
||||
value: timeSeriesData.value[path].value
|
||||
});
|
||||
}
|
||||
}
|
||||
if (topologyValues.length > 0) {
|
||||
extractedValues[topologyKey] = {
|
||||
label: topologyPaths[topologyKey as keyof TopologyValues][0]
|
||||
.split('/')
|
||||
.pop(),
|
||||
values: topologyValues
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return extractedValues;
|
||||
};
|
||||
|
||||
export const getHighestConnectionValue = (values: TopologyValues) =>
|
||||
Object.keys(values)
|
||||
.filter((value) => value.includes('Connection'))
|
||||
.reduce((acc, curr) => {
|
||||
const value = Math.abs(
|
||||
values[curr as keyof TopologyValues].values[0].value as number
|
||||
);
|
||||
return value > acc ? value : acc;
|
||||
}, 0);
|
||||
|
||||
export const getAmount = (
|
||||
highestConnectionValue: number,
|
||||
values: I_BoxDataValue[]
|
||||
) => {
|
||||
return Math.abs(values[0].value as number) / highestConnectionValue;
|
||||
};
|
||||
|
||||
export const createTimes = (
|
||||
range: TimeRange,
|
||||
numberOfNodes: number
|
||||
): UnixTime[] => {
|
||||
const oneSpan = range.duration.divide(numberOfNodes);
|
||||
const roundedRange = TimeRange.fromTimes(
|
||||
range.start.round(oneSpan),
|
||||
range.end.round(oneSpan)
|
||||
);
|
||||
return roundedRange.sample(oneSpan);
|
||||
};
|
||||
|
|
|
@ -17,7 +17,7 @@ import {
|
|||
import { TokenContext } from 'src/contexts/tokenContext';
|
||||
import { UserContext } from 'src/contexts/userContext';
|
||||
import { I_Folder, I_Installation } from 'src/interfaces/InstallationTypes';
|
||||
import axiosConfig from '../../../Resources/axiosConfig';
|
||||
import axiosConfig from 'src/Resources/axiosConfig';
|
||||
import ListItemAvatar from '@mui/material/ListItemAvatar';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
import ListItemText from '@mui/material/ListItemText';
|
||||
|
@ -26,7 +26,8 @@ import PersonIcon from '@mui/icons-material/Person';
|
|||
import Button from '@mui/material/Button';
|
||||
import { Close as CloseIcon } from '@mui/icons-material';
|
||||
import { UsersContext } from 'src/contexts/UsersContextProvider';
|
||||
import { AccessContext } from '../../../contexts/AccessContextProvider';
|
||||
import { AccessContext } from 'src/contexts/AccessContextProvider';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
interface AccessProps {
|
||||
currentResource: I_Folder | I_Installation;
|
||||
|
@ -157,15 +158,32 @@ function Access(props: AccessProps) {
|
|||
|
||||
if (NotGrantedAccessUsers.length > 0) {
|
||||
setError(true);
|
||||
setErrorMessage(
|
||||
'Unable to grant access to: ' + NotGrantedAccessUsers.join(', ')
|
||||
);
|
||||
|
||||
const message =
|
||||
(
|
||||
<FormattedMessage
|
||||
id="unableToGrantAccess"
|
||||
defaultMessage="Unable to grant access to: "
|
||||
/>
|
||||
).props.defaultMessage +
|
||||
' ' +
|
||||
NotGrantedAccessUsers.join(', ');
|
||||
|
||||
setErrorMessage(message);
|
||||
}
|
||||
|
||||
if (grantedAccessUsers.length > 0) {
|
||||
setUpdatedMessage(
|
||||
'Granted access to users: ' + grantedAccessUsers.join(', ')
|
||||
);
|
||||
const message =
|
||||
(
|
||||
<FormattedMessage
|
||||
id="grantedAccessToUsers"
|
||||
defaultMessage="Granted access to users: "
|
||||
/>
|
||||
).props.defaultMessage +
|
||||
' ' +
|
||||
grantedAccessUsers.join(', ');
|
||||
setUpdatedMessage(message);
|
||||
|
||||
setUpdated(true);
|
||||
|
||||
setTimeout(() => {
|
||||
|
@ -248,7 +266,10 @@ function Access(props: AccessProps) {
|
|||
//backgroundColor: 'white'
|
||||
}}
|
||||
>
|
||||
Select users
|
||||
<FormattedMessage
|
||||
id="selectUsers"
|
||||
defaultMessage="Select Users"
|
||||
/>
|
||||
</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
|
@ -301,7 +322,7 @@ function Access(props: AccessProps) {
|
|||
}}
|
||||
onClick={handleSubmit}
|
||||
>
|
||||
Submit
|
||||
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
|
@ -317,7 +338,7 @@ function Access(props: AccessProps) {
|
|||
padding: '6px 8px'
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
<FormattedMessage id="cancel" defaultMessage="Cancel" />
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
@ -333,7 +354,7 @@ function Access(props: AccessProps) {
|
|||
'&:hover': { bgcolor: '#f7b34d' }
|
||||
}}
|
||||
>
|
||||
Grant Access
|
||||
<FormattedMessage id="grantAccess" defaultMessage="Grant Access" />
|
||||
</Button>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={12}>
|
||||
|
@ -342,7 +363,10 @@ function Access(props: AccessProps) {
|
|||
onClick={handleDirectButtonPressed}
|
||||
sx={{ marginTop: '20px' }}
|
||||
>
|
||||
Users with Direct Access
|
||||
<FormattedMessage
|
||||
id="UserswithDirectAccess"
|
||||
defaultMessage="Users with Direct Access"
|
||||
/>
|
||||
</Button>
|
||||
|
||||
{directButtonPressed &&
|
||||
|
@ -382,8 +406,11 @@ function Access(props: AccessProps) {
|
|||
marginTop: '20px'
|
||||
}}
|
||||
>
|
||||
There are no users with direct access to this {props.resourceType}
|
||||
.
|
||||
<FormattedMessage
|
||||
id="noUsersWithDirectAccessToThis"
|
||||
defaultMessage="No users with direct access to this "
|
||||
/>
|
||||
{props.resourceType}
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
|
@ -398,7 +425,10 @@ function Access(props: AccessProps) {
|
|||
onClick={handleInheritedButtonPressed}
|
||||
sx={{ marginTop: '20px', marginBottom: '20px' }}
|
||||
>
|
||||
Users with Inherited Access
|
||||
<FormattedMessage
|
||||
id="UserswithInheritedAccess"
|
||||
defaultMessage="Users with Inherited Access"
|
||||
/>
|
||||
</Button>
|
||||
{inheritedButtonPressed && usersWithInheritedAccess.length == 0 && (
|
||||
<Alert
|
||||
|
@ -410,7 +440,10 @@ function Access(props: AccessProps) {
|
|||
marginBottom: '20px'
|
||||
}}
|
||||
>
|
||||
There are no users with inherited access to this{' '}
|
||||
<FormattedMessage
|
||||
id="noUsersWithDirectAccessToThis"
|
||||
defaultMessage="No users with direct access to this "
|
||||
/>
|
||||
{props.resourceType}.
|
||||
<IconButton
|
||||
color="inherit"
|
||||
|
|
|
@ -0,0 +1,290 @@
|
|||
import React, { useState } from 'react';
|
||||
import { Container, Grid, Switch } from '@mui/material';
|
||||
import TopologyColumn from './topologyColumn';
|
||||
import {
|
||||
getAmount,
|
||||
getHighestConnectionValue,
|
||||
TopologyValues
|
||||
} from '../Log/graph.util';
|
||||
|
||||
interface TopologyProps {
|
||||
values: TopologyValues;
|
||||
}
|
||||
|
||||
function Topology(props: TopologyProps) {
|
||||
if (props.values === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const highestConnectionValue = getHighestConnectionValue(props.values);
|
||||
const [showValues, setShowValues] = useState(false);
|
||||
|
||||
const handleSwitch = () => () => {
|
||||
setShowValues(!showValues);
|
||||
};
|
||||
|
||||
return (
|
||||
<Container maxWidth="xl" style={{ backgroundColor: 'white' }}>
|
||||
<Grid container>
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
md={12}
|
||||
style={{
|
||||
marginTop: '10px',
|
||||
height: '20px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'right',
|
||||
justifyContent: 'right'
|
||||
}}
|
||||
>
|
||||
<Switch
|
||||
edge="start"
|
||||
color="secondary"
|
||||
onChange={handleSwitch()}
|
||||
sx={{
|
||||
'& .MuiSwitch-thumb': {
|
||||
backgroundColor: 'orange'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Grid>
|
||||
|
||||
<Grid
|
||||
item
|
||||
xs={12}
|
||||
md={12}
|
||||
style={{
|
||||
height: '600px',
|
||||
display: 'flex',
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center'
|
||||
}}
|
||||
>
|
||||
<TopologyColumn
|
||||
centerBox={{
|
||||
title: 'Grid',
|
||||
data: props.values.grid
|
||||
}}
|
||||
centerConnection={{
|
||||
orientation: 'horizontal',
|
||||
data: props.values.gridToAcInConnection,
|
||||
amount: props.values.gridToAcInConnection
|
||||
? getAmount(
|
||||
highestConnectionValue,
|
||||
props.values.gridToAcInConnection.values
|
||||
)
|
||||
: 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
isLast={false}
|
||||
isFirst={true}
|
||||
/>
|
||||
<TopologyColumn
|
||||
topBox={{
|
||||
title: 'Pv Inverter',
|
||||
data: props.values.gridBusToPvOnGridbusConnection
|
||||
}}
|
||||
topConnection={{
|
||||
orientation: 'vertical',
|
||||
position: 'top',
|
||||
data: props.values.gridBusToPvOnGridbusConnection,
|
||||
amount: props.values.gridBusToPvOnGridbusConnection
|
||||
? getAmount(
|
||||
highestConnectionValue,
|
||||
props.values.gridBusToPvOnGridbusConnection.values
|
||||
)
|
||||
: 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
centerBox={{
|
||||
title: 'Grid Bus',
|
||||
data: props.values.gridBus
|
||||
}}
|
||||
centerConnection={{
|
||||
orientation: 'horizontal',
|
||||
data: props.values.gridBusToIslandBusConnection,
|
||||
amount: props.values.gridBusToIslandBusConnection
|
||||
? getAmount(
|
||||
highestConnectionValue,
|
||||
props.values.gridBusToIslandBusConnection.values
|
||||
)
|
||||
: 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
bottomBox={{
|
||||
title: 'AC Loads',
|
||||
data: props.values.gridBusToLoadOnGridBusConnection
|
||||
}}
|
||||
bottomConnection={{
|
||||
orientation: 'vertical',
|
||||
position: 'bottom',
|
||||
data: props.values.gridBusToLoadOnGridBusConnection,
|
||||
amount: props.values.gridBusToLoadOnGridBusConnection
|
||||
? getAmount(
|
||||
highestConnectionValue,
|
||||
props.values.gridBusToLoadOnGridBusConnection.values
|
||||
)
|
||||
: 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
isLast={false}
|
||||
isFirst={false}
|
||||
/>
|
||||
<TopologyColumn
|
||||
topBox={{
|
||||
title: 'Pv Inverter',
|
||||
data: props.values.pvOnIslandBusToIslandBusConnection
|
||||
}}
|
||||
topConnection={{
|
||||
orientation: 'vertical',
|
||||
position: 'top',
|
||||
data: props.values.pvOnIslandBusToIslandBusConnection,
|
||||
amount: props.values.pvOnIslandBusToIslandBusConnection
|
||||
? getAmount(
|
||||
highestConnectionValue,
|
||||
props.values.pvOnIslandBusToIslandBusConnection.values
|
||||
)
|
||||
: 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
centerBox={{
|
||||
title: 'Island Bus',
|
||||
data: props.values.islandBus
|
||||
}}
|
||||
centerConnection={{
|
||||
orientation: 'horizontal',
|
||||
data: props.values.islandBusToInverter,
|
||||
amount: props.values.islandBusToInverter
|
||||
? getAmount(
|
||||
highestConnectionValue,
|
||||
props.values.islandBusToInverter.values
|
||||
)
|
||||
: 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
bottomBox={{
|
||||
title: 'AC Loads',
|
||||
data: props.values.islandBusToLoadOnIslandBusConnection
|
||||
}}
|
||||
bottomConnection={{
|
||||
orientation: 'vertical',
|
||||
position: 'bottom',
|
||||
data: props.values.islandBusToLoadOnIslandBusConnection,
|
||||
amount: props.values.islandBusToLoadOnIslandBusConnection
|
||||
? getAmount(
|
||||
highestConnectionValue,
|
||||
props.values.islandBusToLoadOnIslandBusConnection.values
|
||||
)
|
||||
: 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
isLast={false}
|
||||
isFirst={false}
|
||||
/>
|
||||
<TopologyColumn
|
||||
centerBox={{
|
||||
title: 'AC-DC',
|
||||
data: props.values.inverter
|
||||
}}
|
||||
centerConnection={{
|
||||
orientation: 'horizontal',
|
||||
data: props.values.inverterToDcBus,
|
||||
amount: props.values.inverterToDcBus
|
||||
? getAmount(
|
||||
highestConnectionValue,
|
||||
props.values.inverterToDcBus.values
|
||||
)
|
||||
: 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
isLast={false}
|
||||
isFirst={false}
|
||||
/>
|
||||
<TopologyColumn
|
||||
topBox={{
|
||||
title: 'Pv DcDc',
|
||||
data: props.values.pvOnDcBusToDcBusConnection
|
||||
}}
|
||||
topConnection={{
|
||||
orientation: 'vertical',
|
||||
position: 'top',
|
||||
data: props.values.pvOnDcBusToDcBusConnection,
|
||||
amount: props.values.pvOnDcBusToDcBusConnection
|
||||
? getAmount(
|
||||
highestConnectionValue,
|
||||
props.values.pvOnDcBusToDcBusConnection.values
|
||||
)
|
||||
: 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
centerBox={{
|
||||
title: 'DC Link',
|
||||
data: props.values.dcBus
|
||||
}}
|
||||
centerConnection={{
|
||||
orientation: 'horizontal',
|
||||
data: props.values.dcBusToDcDcConnection,
|
||||
amount: props.values.dcBusToDcDcConnection
|
||||
? getAmount(
|
||||
highestConnectionValue,
|
||||
props.values.dcBusToDcDcConnection.values
|
||||
)
|
||||
: 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
bottomBox={{
|
||||
title: 'DC Loads',
|
||||
data: props.values.dcBusToLoadOnDcConnection
|
||||
}}
|
||||
bottomConnection={{
|
||||
orientation: 'vertical',
|
||||
position: 'bottom',
|
||||
data: props.values.dcBusToLoadOnDcConnection,
|
||||
amount: props.values.dcBusToLoadOnDcConnection
|
||||
? getAmount(
|
||||
highestConnectionValue,
|
||||
props.values.dcBusToLoadOnDcConnection.values
|
||||
)
|
||||
: 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
isLast={false}
|
||||
isFirst={false}
|
||||
/>
|
||||
<TopologyColumn
|
||||
centerBox={{
|
||||
title: 'DC-DC',
|
||||
data: props.values.dcDc
|
||||
}}
|
||||
centerConnection={{
|
||||
orientation: 'horizontal',
|
||||
data: props.values.dcDCToBatteryConnection,
|
||||
amount: props.values.dcDCToBatteryConnection
|
||||
? getAmount(
|
||||
highestConnectionValue,
|
||||
props.values.dcDCToBatteryConnection.values
|
||||
)
|
||||
: 0,
|
||||
showValues: showValues
|
||||
}}
|
||||
isLast={false}
|
||||
isFirst={false}
|
||||
/>
|
||||
<TopologyColumn
|
||||
centerBox={{
|
||||
title: 'Battery',
|
||||
data: props.values.battery
|
||||
}}
|
||||
isLast={true}
|
||||
isFirst={false}
|
||||
/>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
);
|
||||
}
|
||||
|
||||
export default Topology;
|
|
@ -1,4 +1,4 @@
|
|||
import React, { ReactNode } from 'react';
|
||||
import React, { ReactNode, useContext, useState } from 'react';
|
||||
import { CircularProgress, ListItemIcon, useTheme } from '@mui/material';
|
||||
import { TreeItem } from '@mui/lab';
|
||||
import { I_Folder, I_Installation } from 'src/interfaces/InstallationTypes';
|
||||
|
@ -7,13 +7,14 @@ import InsertDriveFileIcon from '@mui/icons-material/InsertDriveFile';
|
|||
import Typography from '@mui/material/Typography';
|
||||
import { makeStyles } from '@mui/styles';
|
||||
import CancelIcon from '@mui/icons-material/Cancel';
|
||||
import { LogContext } from 'src/contexts/LogContextProvider';
|
||||
import routes from 'src/Resources/routes.json';
|
||||
import { useNavigate } from 'react-router-dom';
|
||||
|
||||
interface CustomTreeItemProps {
|
||||
node: I_Installation | I_Folder;
|
||||
parent_id: number;
|
||||
children?: ReactNode;
|
||||
handleSelectedInstallation: () => void;
|
||||
status: number;
|
||||
}
|
||||
|
||||
const useTreeItemStyles = makeStyles((theme) => ({
|
||||
|
@ -37,6 +38,41 @@ const useTreeItemStyles = makeStyles((theme) => ({
|
|||
function CustomTreeItem(props: CustomTreeItemProps) {
|
||||
const theme = useTheme();
|
||||
const classes = useTreeItemStyles();
|
||||
const logContext = useContext(LogContext);
|
||||
const { getStatus } = logContext;
|
||||
const status = getStatus(props.node.id);
|
||||
const navigate = useNavigate();
|
||||
const [selected, setSelected] = useState(false);
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const installationId = parseInt(searchParams.get('installation'));
|
||||
|
||||
const handleSelectOneInstallation = (): void => {
|
||||
let installation = props.node;
|
||||
if (installation.type != 'Folder') {
|
||||
navigate(
|
||||
routes.installations +
|
||||
routes.tree +
|
||||
'?installation=' +
|
||||
installation.id.toString(),
|
||||
{
|
||||
replace: true
|
||||
}
|
||||
);
|
||||
setSelected(!selected);
|
||||
} else {
|
||||
navigate(
|
||||
routes.installations +
|
||||
routes.tree +
|
||||
'?folder=' +
|
||||
installation.id.toString(),
|
||||
{
|
||||
replace: true
|
||||
}
|
||||
);
|
||||
setSelected(false);
|
||||
}
|
||||
};
|
||||
|
||||
const renderIcon = () => {
|
||||
if (props.node.type === 'Folder') {
|
||||
return <FolderIcon />;
|
||||
|
@ -45,13 +81,14 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
|||
}
|
||||
return null;
|
||||
};
|
||||
|
||||
const itemClasses = [classes.labelRoot];
|
||||
|
||||
return (
|
||||
<TreeItem
|
||||
nodeId={
|
||||
props.node.id.toString() + props.parent_id.toString() + props.node.type
|
||||
}
|
||||
nodeId={props.node.id.toString() + props.node.type}
|
||||
label={
|
||||
<div className={classes.labelRoot}>
|
||||
<div className={itemClasses.join(' ')}>
|
||||
<ListItemIcon color="inherit" className={classes.labelIcon}>
|
||||
{renderIcon()}
|
||||
</ListItemIcon>
|
||||
|
@ -63,16 +100,17 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
|||
>
|
||||
{props.node.name}
|
||||
</Typography>
|
||||
|
||||
{props.node.type === 'Installation' && (
|
||||
<div>
|
||||
{props.status === -1 ? (
|
||||
{status === -1 ? (
|
||||
<CancelIcon
|
||||
style={{
|
||||
width: '23px',
|
||||
height: '23px',
|
||||
color: 'red',
|
||||
borderRadius: '50%',
|
||||
marginRight: '40px',
|
||||
marginLeft: '21px',
|
||||
marginTop: '30px'
|
||||
}}
|
||||
/>
|
||||
|
@ -80,12 +118,12 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
|||
''
|
||||
)}
|
||||
|
||||
{props.status === -2 ? (
|
||||
{status === -2 ? (
|
||||
<CircularProgress
|
||||
size={20}
|
||||
sx={{
|
||||
color: '#f7b34d',
|
||||
marginRight: '40px',
|
||||
marginLeft: '20px',
|
||||
marginTop: '30px'
|
||||
}}
|
||||
/>
|
||||
|
@ -98,13 +136,13 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
|||
width: '20px',
|
||||
height: '20px',
|
||||
borderRadius: '50%',
|
||||
marginRight: '40px',
|
||||
marginLeft: '20px',
|
||||
backgroundColor:
|
||||
props.status === 2
|
||||
status === 2
|
||||
? 'red'
|
||||
: props.status === 1
|
||||
: status === 1
|
||||
? 'orange'
|
||||
: props.status === -1 || props.status === -2
|
||||
: status === -1 || status === -2
|
||||
? 'transparent'
|
||||
: 'green'
|
||||
}}
|
||||
|
@ -114,6 +152,7 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
|||
</div>
|
||||
}
|
||||
sx={{
|
||||
display: !installationId ? 'block' : 'none',
|
||||
'.MuiTreeItem-content': {
|
||||
width: 'inherit',
|
||||
|
||||
|
@ -123,9 +162,10 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
|||
cursor: 'pointer',
|
||||
backgroundColor: theme.colors.primary.lighter
|
||||
}
|
||||
}
|
||||
},
|
||||
backgroundColor: selected ? '#111111' : '#ffffff'
|
||||
}}
|
||||
onClick={() => props.handleSelectedInstallation()}
|
||||
onClick={() => handleSelectOneInstallation()}
|
||||
>
|
||||
{props.children}
|
||||
</TreeItem>
|
||||
|
|
|
@ -24,6 +24,7 @@ import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
|||
import AccessContextProvider from 'src/contexts/AccessContextProvider';
|
||||
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
||||
import Access from '../ManageAccess/Access';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
interface singleFolderProps {
|
||||
current_folder: I_Folder;
|
||||
|
@ -43,6 +44,8 @@ function Folder(props: singleFolderProps) {
|
|||
const [isRowHovered, setHoveredRow] = useState(-1);
|
||||
const [selectedUser, setSelectedUser] = useState<number>(-1);
|
||||
const selectedBulkActions = selectedUser !== -1;
|
||||
const searchParams = new URLSearchParams(location.search);
|
||||
const folderId = parseInt(searchParams.get('folder'));
|
||||
|
||||
const installationContext = useContext(InstallationsContext);
|
||||
const {
|
||||
|
@ -65,8 +68,16 @@ function Folder(props: singleFolderProps) {
|
|||
}
|
||||
|
||||
const tabs = [
|
||||
{ value: 'folder', label: 'Folder' },
|
||||
{ value: 'manage', label: 'Manage Access' }
|
||||
{
|
||||
value: 'folder',
|
||||
label: <FormattedMessage id="folder" defaultMessage="Folder" />
|
||||
},
|
||||
{
|
||||
value: 'manage',
|
||||
label: (
|
||||
<FormattedMessage id="manageAccess" defaultMessage="Manage Access" />
|
||||
)
|
||||
}
|
||||
];
|
||||
|
||||
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
||||
|
@ -140,210 +151,243 @@ function Folder(props: singleFolderProps) {
|
|||
}
|
||||
return true;
|
||||
};
|
||||
return (
|
||||
<>
|
||||
{openModalFolder && (
|
||||
<FolderForm
|
||||
cancel={handleFormCancel}
|
||||
submit={handleFolderFormSubmit}
|
||||
parentid={props.current_folder.id}
|
||||
/>
|
||||
)}
|
||||
{openModalInstallation && (
|
||||
<InstallationForm
|
||||
cancel={handleFormCancel}
|
||||
submit={handleInstallationFormSubmit}
|
||||
parentid={props.current_folder.id}
|
||||
/>
|
||||
)}
|
||||
<Grid item xs={12} md={9}>
|
||||
<TabsContainerWrapper>
|
||||
<Tabs
|
||||
onChange={handleTabsChange}
|
||||
value={currentTab}
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
textColor="primary"
|
||||
indicatorColor="primary"
|
||||
>
|
||||
{tabs.map((tab) => (
|
||||
<Tab key={tab.value} label={tab.label} value={tab.value} />
|
||||
))}
|
||||
</Tabs>
|
||||
</TabsContainerWrapper>
|
||||
<Card variant="outlined">
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="stretch"
|
||||
spacing={0}
|
||||
>
|
||||
{currentTab === 'folder' && (
|
||||
<Container maxWidth="xl">
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="stretch"
|
||||
spacing={3}
|
||||
>
|
||||
<Grid item xs={12} md={12}>
|
||||
<CardContent>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{
|
||||
'& .MuiTextField-root': { m: 1, width: '50ch' }
|
||||
}}
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
>
|
||||
<div>
|
||||
<TextField
|
||||
label="Name"
|
||||
name="name"
|
||||
value={formValues.name}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
required
|
||||
error={formValues.name === ''}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
label="Information"
|
||||
name="information"
|
||||
value={formValues.information}
|
||||
onChange={handleChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: 10
|
||||
if (folderId == props.current_folder.id) {
|
||||
return (
|
||||
<>
|
||||
{openModalFolder && (
|
||||
<FolderForm
|
||||
cancel={handleFormCancel}
|
||||
submit={handleFolderFormSubmit}
|
||||
parentid={props.current_folder.id}
|
||||
/>
|
||||
)}
|
||||
{openModalInstallation && (
|
||||
<InstallationForm
|
||||
cancel={handleFormCancel}
|
||||
submit={handleInstallationFormSubmit}
|
||||
parentid={props.current_folder.id}
|
||||
/>
|
||||
)}
|
||||
<Grid item xs={12} md={9}>
|
||||
<TabsContainerWrapper>
|
||||
<Tabs
|
||||
onChange={handleTabsChange}
|
||||
value={currentTab}
|
||||
variant="scrollable"
|
||||
scrollButtons="auto"
|
||||
textColor="primary"
|
||||
indicatorColor="primary"
|
||||
>
|
||||
{tabs.map((tab) => (
|
||||
<Tab key={tab.value} label={tab.label} value={tab.value} />
|
||||
))}
|
||||
</Tabs>
|
||||
</TabsContainerWrapper>
|
||||
<Card variant="outlined">
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="stretch"
|
||||
spacing={0}
|
||||
>
|
||||
{currentTab === 'folder' && (
|
||||
<Container maxWidth="xl">
|
||||
<Grid
|
||||
container
|
||||
direction="row"
|
||||
justifyContent="center"
|
||||
alignItems="stretch"
|
||||
spacing={3}
|
||||
>
|
||||
<Grid item xs={12} md={12}>
|
||||
<CardContent>
|
||||
<Box
|
||||
component="form"
|
||||
sx={{
|
||||
'& .MuiTextField-root': { m: 1, width: '50ch' }
|
||||
}}
|
||||
noValidate
|
||||
autoComplete="off"
|
||||
>
|
||||
{currentUser.hasWriteAccess && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleFolderInformationUpdate}
|
||||
sx={{
|
||||
marginLeft: '10px'
|
||||
}}
|
||||
disabled={!areRequiredFieldsFilled()}
|
||||
>
|
||||
Apply Changes
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{currentUser.hasWriteAccess && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleNewInstallationInsertion}
|
||||
sx={{
|
||||
marginLeft: '10px'
|
||||
}}
|
||||
>
|
||||
Add new installation
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{currentUser.hasWriteAccess && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleNewFolderInsertion}
|
||||
sx={{
|
||||
marginLeft: '10px'
|
||||
}}
|
||||
>
|
||||
Add new folder
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{currentUser.hasWriteAccess && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleDeleteFolder}
|
||||
sx={{
|
||||
marginLeft: '10px'
|
||||
}}
|
||||
>
|
||||
Delete Folder
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{loading && (
|
||||
<CircularProgress
|
||||
sx={{
|
||||
color: theme.palette.primary.main,
|
||||
marginLeft: '20px'
|
||||
}}
|
||||
<div>
|
||||
<TextField
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="name"
|
||||
defaultMessage="Name"
|
||||
/>
|
||||
}
|
||||
name="name"
|
||||
value={formValues.name}
|
||||
onChange={handleChange}
|
||||
fullWidth
|
||||
required
|
||||
error={formValues.name === ''}
|
||||
/>
|
||||
)}
|
||||
{error && (
|
||||
<Alert
|
||||
severity="error"
|
||||
sx={{
|
||||
ml: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
An error has occurred
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => setError(false)} // Set error state to false on click
|
||||
sx={{ marginLeft: '4px' }}
|
||||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="information"
|
||||
defaultMessage="Information"
|
||||
/>
|
||||
}
|
||||
name="information"
|
||||
value={formValues.information}
|
||||
onChange={handleChange}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div
|
||||
style={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
marginTop: 10
|
||||
}}
|
||||
>
|
||||
{currentUser.hasWriteAccess && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleFolderInformationUpdate}
|
||||
sx={{
|
||||
marginLeft: '10px'
|
||||
}}
|
||||
disabled={!areRequiredFieldsFilled()}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Alert>
|
||||
)}
|
||||
{updated && (
|
||||
<Alert
|
||||
severity="success"
|
||||
sx={{
|
||||
ml: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
Successfully updated
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => setUpdated(false)} // Set error state to false on click
|
||||
sx={{ marginLeft: '4px' }}
|
||||
<FormattedMessage
|
||||
id="applyChanges"
|
||||
defaultMessage="Apply Changes"
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{currentUser.hasWriteAccess && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleNewInstallationInsertion}
|
||||
sx={{
|
||||
marginLeft: '10px'
|
||||
}}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
</CardContent>
|
||||
<FormattedMessage
|
||||
id="addNewInstallation"
|
||||
defaultMessage="Add new installation"
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{currentUser.hasWriteAccess && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleNewFolderInsertion}
|
||||
sx={{
|
||||
marginLeft: '10px'
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="addNewFolder"
|
||||
defaultMessage="Add new folder"
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{currentUser.hasWriteAccess && (
|
||||
<Button
|
||||
variant="contained"
|
||||
onClick={handleDeleteFolder}
|
||||
sx={{
|
||||
marginLeft: '10px'
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="deleteFolder"
|
||||
defaultMessage="Delete Folder"
|
||||
/>
|
||||
</Button>
|
||||
)}
|
||||
|
||||
{loading && (
|
||||
<CircularProgress
|
||||
sx={{
|
||||
color: theme.palette.primary.main,
|
||||
marginLeft: '20px'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
{error && (
|
||||
<Alert
|
||||
severity="error"
|
||||
sx={{
|
||||
ml: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="errorOccured"
|
||||
defaultMessage="An error has occurred"
|
||||
/>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => setError(false)} // Set error state to false on click
|
||||
sx={{ marginLeft: '4px' }}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Alert>
|
||||
)}
|
||||
{updated && (
|
||||
<Alert
|
||||
severity="success"
|
||||
sx={{
|
||||
ml: 1,
|
||||
display: 'flex',
|
||||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
<FormattedMessage
|
||||
id="successfullyUpdated"
|
||||
defaultMessage="Successfully updated"
|
||||
/>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => setUpdated(false)} // Set error state to false on click
|
||||
sx={{ marginLeft: '4px' }}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
</IconButton>
|
||||
</Alert>
|
||||
)}
|
||||
</div>
|
||||
</Box>
|
||||
</CardContent>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</Container>
|
||||
)}
|
||||
{currentTab === 'manage' && currentUser.hasWriteAccess && (
|
||||
<AccessContextProvider>
|
||||
<Access
|
||||
currentResource={formValues}
|
||||
resourceType="folder"
|
||||
></Access>
|
||||
</AccessContextProvider>
|
||||
)}
|
||||
</Grid>
|
||||
</Card>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
</Container>
|
||||
)}
|
||||
{currentTab === 'manage' && currentUser.hasWriteAccess && (
|
||||
<AccessContextProvider>
|
||||
<Access
|
||||
currentResource={formValues}
|
||||
resourceType="folder"
|
||||
></Access>
|
||||
</AccessContextProvider>
|
||||
)}
|
||||
</Grid>
|
||||
</Card>
|
||||
</Grid>
|
||||
</>
|
||||
);
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export default Folder;
|
||||
|
|
|
@ -1,86 +1,33 @@
|
|||
import React, { useContext, useEffect, useState } from 'react';
|
||||
import { Grid, useTheme } from '@mui/material';
|
||||
import React, { useContext, useEffect } from 'react';
|
||||
import { Grid } from '@mui/material';
|
||||
import { TreeView } from '@mui/lab';
|
||||
import ExpandMoreIcon from '@mui/icons-material/ExpandMore';
|
||||
import ChevronRightIcon from '@mui/icons-material/ChevronRight';
|
||||
import CustomTreeItem from './CustomTreeItem';
|
||||
import Installation from '../Installations/Installation';
|
||||
import { I_Folder, I_Installation } from 'src/interfaces/InstallationTypes';
|
||||
import Folder from './Folder';
|
||||
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
||||
import { LogContext } from '../../../contexts/LogContextProvider';
|
||||
import LogContextProvider from 'src/contexts/LogContextProvider';
|
||||
|
||||
function InstallationTree() {
|
||||
const theme = useTheme();
|
||||
const { data, fetchAllFoldersAndInstallations } =
|
||||
const { foldersAndInstallations, fetchAllFoldersAndInstallations } =
|
||||
useContext(InstallationsContext);
|
||||
const [selectedInstallation, setSelectedInstallation] = useState<number>(-1);
|
||||
const [selectedFolder, setSelectedFolder] = useState<number>(-1);
|
||||
const [installationStatus, setInstallationStatus] = useState<
|
||||
Record<number, number[]>
|
||||
>({});
|
||||
|
||||
const selectedBulkActionsForFolders = selectedFolder === -1 ? false : true;
|
||||
|
||||
useEffect(() => {
|
||||
fetchAllFoldersAndInstallations();
|
||||
}, []);
|
||||
|
||||
const findInstallation = (id: number) => {
|
||||
return data.find(
|
||||
(installation) => installation.type != 'Folder' && installation.id === id
|
||||
) as I_Installation;
|
||||
};
|
||||
|
||||
const findFolder = (id: number) => {
|
||||
return data.find(
|
||||
(folder) => folder.type == 'Folder' && folder.id === id
|
||||
) as I_Folder;
|
||||
};
|
||||
|
||||
const logContext = useContext(LogContext);
|
||||
const { getStatus } = logContext;
|
||||
|
||||
const handleSelectOneInstallation = (
|
||||
installation: I_Installation | I_Folder
|
||||
): void => {
|
||||
if (installation.type != 'Folder') {
|
||||
if (selectedInstallation != installation.id) {
|
||||
setSelectedInstallation(installation.id);
|
||||
setSelectedFolder(-1);
|
||||
} else {
|
||||
setSelectedInstallation(-1);
|
||||
}
|
||||
} else {
|
||||
if (selectedFolder != installation.id) {
|
||||
setSelectedFolder(installation.id);
|
||||
setSelectedInstallation(-1);
|
||||
} else {
|
||||
setSelectedFolder(-1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const TreeNode = ({ node, parent_id }) => {
|
||||
if (node.type == 'Folder') {
|
||||
return (
|
||||
node.parentId == parent_id && (
|
||||
<CustomTreeItem
|
||||
node={node}
|
||||
parent_id={parent_id}
|
||||
handleSelectedInstallation={() => handleSelectOneInstallation(node)}
|
||||
status={0}
|
||||
>
|
||||
{data.map((subnode) => {
|
||||
<CustomTreeItem node={node} parent_id={parent_id}>
|
||||
{foldersAndInstallations.map((subnode) => {
|
||||
return (
|
||||
subnode != node &&
|
||||
subnode.parentId == node.id && (
|
||||
<TreeNode
|
||||
key={
|
||||
subnode.id.toString() +
|
||||
parent_id.toString() +
|
||||
subnode.type
|
||||
}
|
||||
key={subnode.id.toString() + subnode.type}
|
||||
node={subnode}
|
||||
parent_id={node.id}
|
||||
/>
|
||||
|
@ -91,60 +38,55 @@ function InstallationTree() {
|
|||
)
|
||||
);
|
||||
} else {
|
||||
const status = getStatus(node.id);
|
||||
|
||||
return (
|
||||
node.parentId == parent_id && (
|
||||
<CustomTreeItem
|
||||
node={node}
|
||||
parent_id={parent_id}
|
||||
handleSelectedInstallation={() => handleSelectOneInstallation(node)}
|
||||
status={status}
|
||||
/>
|
||||
<CustomTreeItem node={node} parent_id={parent_id} />
|
||||
)
|
||||
);
|
||||
}
|
||||
};
|
||||
|
||||
return (
|
||||
<Grid container spacing={1} sx={{ marginTop: 0.1 }}>
|
||||
<Grid item xs={12} md={3}>
|
||||
<TreeView
|
||||
defaultCollapseIcon={<ExpandMoreIcon />}
|
||||
defaultExpandIcon={<ChevronRightIcon />}
|
||||
>
|
||||
{data.map((node, index) => {
|
||||
<LogContextProvider>
|
||||
<Grid container spacing={1} sx={{ marginTop: 0.1 }}>
|
||||
<Grid item xs={12} md={3}>
|
||||
<TreeView
|
||||
defaultCollapseIcon={<ExpandMoreIcon />}
|
||||
defaultExpandIcon={<ChevronRightIcon />}
|
||||
defaultExpanded={['1Folder']}
|
||||
>
|
||||
{foldersAndInstallations.map((node, index) => {
|
||||
return (
|
||||
<TreeNode
|
||||
key={node.id.toString() + node.type}
|
||||
node={node}
|
||||
parent_id={'0'}
|
||||
/>
|
||||
);
|
||||
})}
|
||||
</TreeView>
|
||||
</Grid>
|
||||
|
||||
{foldersAndInstallations.map((installation) => {
|
||||
if (installation.type == 'Installation') {
|
||||
return (
|
||||
<TreeNode
|
||||
key={node.id.toString() + node.parentId.toString() + node.type}
|
||||
node={node}
|
||||
parent_id={'0'}
|
||||
/>
|
||||
<Installation
|
||||
key={installation.id + installation.type}
|
||||
current_installation={installation}
|
||||
type="tree"
|
||||
></Installation>
|
||||
);
|
||||
})}
|
||||
</TreeView>
|
||||
} else {
|
||||
return (
|
||||
<Folder
|
||||
key={installation.id + installation.type}
|
||||
current_folder={installation}
|
||||
></Folder>
|
||||
);
|
||||
}
|
||||
})}
|
||||
</Grid>
|
||||
|
||||
{data.map((installation) => {
|
||||
if (installation.type == 'Installation') {
|
||||
return (
|
||||
<Installation
|
||||
key={installation.id}
|
||||
current_installation={findInstallation(installation.id)}
|
||||
type="tree"
|
||||
style={{
|
||||
display:
|
||||
installation.id === selectedInstallation ? 'block' : 'none'
|
||||
}}
|
||||
></Installation>
|
||||
);
|
||||
}
|
||||
})}
|
||||
|
||||
{selectedBulkActionsForFolders && (
|
||||
<Folder current_folder={findFolder(selectedFolder)}></Folder>
|
||||
)}
|
||||
</Grid>
|
||||
</LogContextProvider>
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,7 @@ import { Close as CloseIcon } from '@mui/icons-material';
|
|||
import { I_Folder } from 'src/interfaces/InstallationTypes';
|
||||
import { TokenContext } from 'src/contexts/tokenContext';
|
||||
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
interface folderFormProps {
|
||||
cancel: () => void;
|
||||
|
@ -94,7 +95,7 @@ function folderForm(props: folderFormProps) {
|
|||
>
|
||||
<div>
|
||||
<TextField
|
||||
label="Name"
|
||||
label={<FormattedMessage id="name" defaultMessage="Name" />}
|
||||
name="name"
|
||||
value={formValues.name}
|
||||
onChange={handleChange}
|
||||
|
@ -106,7 +107,12 @@ function folderForm(props: folderFormProps) {
|
|||
|
||||
<div>
|
||||
<TextField
|
||||
label="Information"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="information"
|
||||
defaultMessage="Information"
|
||||
/>
|
||||
}
|
||||
name="information"
|
||||
value={formValues.information}
|
||||
onChange={handleChange}
|
||||
|
@ -129,7 +135,7 @@ function folderForm(props: folderFormProps) {
|
|||
}}
|
||||
disabled={!areRequiredFieldsFilled()}
|
||||
>
|
||||
Submit
|
||||
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
|
@ -139,7 +145,7 @@ function folderForm(props: folderFormProps) {
|
|||
marginLeft: '10px'
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
<FormattedMessage id="cancel" defaultMessage="Cancel" />
|
||||
</Button>
|
||||
|
||||
{loading && (
|
||||
|
@ -160,11 +166,14 @@ function folderForm(props: folderFormProps) {
|
|||
alignItems: 'center'
|
||||
}}
|
||||
>
|
||||
An error has occurred
|
||||
<FormattedMessage
|
||||
id="errorOccured"
|
||||
defaultMessage="An error has occurred"
|
||||
/>
|
||||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => setError(false)} // Set error state to false on click
|
||||
onClick={() => setError(false)}
|
||||
sx={{ marginLeft: '4px' }}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
|
|
|
@ -12,6 +12,7 @@ import { UsersContext } from '../../../contexts/UsersContextProvider';
|
|||
import Button from '@mui/material/Button';
|
||||
import UserForm from './userForm';
|
||||
import { UserContext } from '../../../contexts/userContext';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
function UsersSearch() {
|
||||
const theme = useTheme();
|
||||
|
@ -55,7 +56,7 @@ function UsersSearch() {
|
|||
<Grid item xs={12} md={3}>
|
||||
{currentUser.hasWriteAccess && (
|
||||
<Button variant="contained" onClick={handleSubmit}>
|
||||
Create user
|
||||
<FormattedMessage id="addUser" defaultMessage="Create user" />
|
||||
</Button>
|
||||
)}
|
||||
</Grid>
|
||||
|
|
|
@ -20,6 +20,7 @@ import { TokenContext } from 'src/contexts/tokenContext';
|
|||
import FormControlLabel from '@mui/material/FormControlLabel';
|
||||
import Checkbox from '@mui/material/Checkbox';
|
||||
import { I_Folder, I_Installation } from 'src/interfaces/InstallationTypes';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
interface userFormProps {
|
||||
cancel: () => void;
|
||||
|
@ -208,7 +209,7 @@ function userForm(props: userFormProps) {
|
|||
>
|
||||
<div>
|
||||
<TextField
|
||||
label="Name"
|
||||
label={<FormattedMessage id="name" defaultMessage="Name" />}
|
||||
name="name"
|
||||
value={formValues.name}
|
||||
onChange={handleChange}
|
||||
|
@ -219,7 +220,7 @@ function userForm(props: userFormProps) {
|
|||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
label="Email"
|
||||
label={<FormattedMessage id="email" defaultMessage="Email" />}
|
||||
name="email"
|
||||
value={formValues.email}
|
||||
onChange={handleChange}
|
||||
|
@ -230,7 +231,12 @@ function userForm(props: userFormProps) {
|
|||
</div>
|
||||
<div>
|
||||
<TextField
|
||||
label="Information"
|
||||
label={
|
||||
<FormattedMessage
|
||||
id="information"
|
||||
defaultMessage="Information"
|
||||
/>
|
||||
}
|
||||
name="information"
|
||||
value={formValues.information}
|
||||
onChange={handleChange}
|
||||
|
@ -249,7 +255,10 @@ function userForm(props: userFormProps) {
|
|||
backgroundColor: 'transparent'
|
||||
}}
|
||||
>
|
||||
Grant access to folders
|
||||
<FormattedMessage
|
||||
id="grantAccessToFolders"
|
||||
defaultMessage="Grant access to folders"
|
||||
/>
|
||||
</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
|
@ -284,7 +293,7 @@ function userForm(props: userFormProps) {
|
|||
}}
|
||||
onClick={handleCloseFolder}
|
||||
>
|
||||
Submit
|
||||
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||
</Button>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
@ -304,7 +313,10 @@ function userForm(props: userFormProps) {
|
|||
fontSize: 14
|
||||
}}
|
||||
>
|
||||
Grant access to installations
|
||||
<FormattedMessage
|
||||
id="grantAccessToInstallations"
|
||||
defaultMessage="Grant access to installations"
|
||||
/>
|
||||
</InputLabel>
|
||||
<Select
|
||||
multiple
|
||||
|
@ -339,7 +351,7 @@ function userForm(props: userFormProps) {
|
|||
}}
|
||||
onClick={handleCloseInstallation}
|
||||
>
|
||||
Submit
|
||||
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||
</Button>
|
||||
</Select>
|
||||
</FormControl>
|
||||
|
@ -375,7 +387,7 @@ function userForm(props: userFormProps) {
|
|||
}}
|
||||
disabled={!areRequiredFieldsFilled()}
|
||||
>
|
||||
Submit
|
||||
<FormattedMessage id="submit" defaultMessage="Submit" />
|
||||
</Button>
|
||||
|
||||
<Button
|
||||
|
@ -385,7 +397,7 @@ function userForm(props: userFormProps) {
|
|||
marginLeft: '10px'
|
||||
}}
|
||||
>
|
||||
Cancel
|
||||
<FormattedMessage id="cancel" defaultMessage="Cancel" />
|
||||
</Button>
|
||||
|
||||
{loading && (
|
||||
|
@ -410,7 +422,7 @@ function userForm(props: userFormProps) {
|
|||
<IconButton
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => setError(false)} // Set error state to false on click
|
||||
onClick={() => setError(false)}
|
||||
sx={{ marginLeft: '4px' }}
|
||||
>
|
||||
<CloseIcon fontSize="small" />
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import {
|
||||
import React, {
|
||||
createContext,
|
||||
ReactNode,
|
||||
useCallback,
|
||||
|
@ -11,6 +11,7 @@ import {
|
|||
I_UserWithInheritedAccess,
|
||||
InnovEnergyUser
|
||||
} from '../interfaces/UserTypes';
|
||||
import { FormattedMessage } from 'react-intl';
|
||||
|
||||
interface AccessContextProviderProps {
|
||||
usersWithDirectAccess: InnovEnergyUser[];
|
||||
|
@ -82,7 +83,15 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
|
|||
})
|
||||
.catch((error) => {
|
||||
setError(true);
|
||||
setErrorMessage('Unable to load data');
|
||||
|
||||
const message = (
|
||||
<FormattedMessage
|
||||
id="unableToLoadData"
|
||||
defaultMessage="Unable to load data"
|
||||
/>
|
||||
).props.defaultMessage;
|
||||
|
||||
setErrorMessage(message);
|
||||
});
|
||||
},
|
||||
[]
|
||||
|
@ -99,7 +108,13 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
|
|||
})
|
||||
.catch((error) => {
|
||||
setError(true);
|
||||
setErrorMessage('Unable to load data');
|
||||
const message = (
|
||||
<FormattedMessage
|
||||
id="unableToLoadData"
|
||||
defaultMessage="Unable to load data"
|
||||
/>
|
||||
).props.defaultMessage;
|
||||
setErrorMessage(message);
|
||||
});
|
||||
},
|
||||
[]
|
||||
|
@ -127,7 +142,19 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
|
|||
resourceType,
|
||||
current_ResourceId
|
||||
);
|
||||
setUpdatedMessage('Revoked access from user: ' + name);
|
||||
|
||||
const message =
|
||||
(
|
||||
<FormattedMessage
|
||||
id="revokedAccessFromUser"
|
||||
defaultMessage="Revoked access from user: "
|
||||
/>
|
||||
).props.defaultMessage +
|
||||
' ' +
|
||||
name;
|
||||
|
||||
setUpdatedMessage(message);
|
||||
|
||||
setUpdated(true);
|
||||
setTimeout(() => {
|
||||
setUpdated(false);
|
||||
|
@ -136,7 +163,14 @@ const AccessContextProvider = ({ children }: { children: ReactNode }) => {
|
|||
})
|
||||
.catch((error) => {
|
||||
setError(true);
|
||||
setErrorMessage('Unable to revoke access');
|
||||
const message = (
|
||||
<FormattedMessage
|
||||
id="unableToRevokeAccess"
|
||||
defaultMessage="Unable to revoke access"
|
||||
/>
|
||||
).props.defaultMessage;
|
||||
|
||||
setErrorMessage(message);
|
||||
});
|
||||
},
|
||||
[]
|
||||
|
|
|
@ -11,7 +11,8 @@ import { I_Folder, I_Installation } from 'src/interfaces/InstallationTypes';
|
|||
import { TokenContext } from './tokenContext';
|
||||
|
||||
interface I_InstallationContextProviderProps {
|
||||
data: I_Installation[];
|
||||
installations: I_Installation[];
|
||||
foldersAndInstallations: I_Installation[];
|
||||
fetchAllInstallations: () => Promise<void>;
|
||||
fetchAllFoldersAndInstallations: () => Promise<void>;
|
||||
createInstallation: (value: Partial<I_Installation>) => Promise<void>;
|
||||
|
@ -24,14 +25,14 @@ interface I_InstallationContextProviderProps {
|
|||
setUpdated: (value: boolean) => void;
|
||||
deleteInstallation: (value: I_Installation, view: string) => Promise<void>;
|
||||
createFolder: (value: Partial<I_Folder>) => Promise<void>;
|
||||
|
||||
updateFolder: (value: I_Folder) => Promise<void>;
|
||||
deleteFolder: (value: I_Folder) => Promise<void>;
|
||||
}
|
||||
|
||||
export const InstallationsContext =
|
||||
createContext<I_InstallationContextProviderProps>({
|
||||
data: [],
|
||||
installations: [],
|
||||
foldersAndInstallations: [],
|
||||
fetchAllInstallations: () => Promise.resolve(),
|
||||
fetchAllFoldersAndInstallations: () => Promise.resolve(),
|
||||
createInstallation: () => Promise.resolve(),
|
||||
|
@ -44,7 +45,6 @@ export const InstallationsContext =
|
|||
setUpdated: () => {},
|
||||
deleteInstallation: () => Promise.resolve(),
|
||||
createFolder: () => Promise.resolve(),
|
||||
|
||||
updateFolder: () => Promise.resolve(),
|
||||
deleteFolder: () => Promise.resolve()
|
||||
});
|
||||
|
@ -54,7 +54,8 @@ const InstallationsContextProvider = ({
|
|||
}: {
|
||||
children: ReactNode;
|
||||
}) => {
|
||||
const [data, setData] = useState([]);
|
||||
const [installations, setInstallations] = useState([]);
|
||||
const [foldersAndInstallations, setFoldersAndInstallations] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
const [error, setError] = useState(false);
|
||||
const [updated, setUpdated] = useState(false);
|
||||
|
@ -67,7 +68,7 @@ const InstallationsContextProvider = ({
|
|||
axiosConfig
|
||||
.get('/GetAllInstallations', {})
|
||||
.then((res) => {
|
||||
setData(res.data);
|
||||
setInstallations(res.data);
|
||||
})
|
||||
.catch((err: AxiosError) => {
|
||||
if (err.response && err.response.status == 401) {
|
||||
|
@ -80,14 +81,14 @@ const InstallationsContextProvider = ({
|
|||
return axiosConfig
|
||||
.get('/GetAllFoldersAndInstallations')
|
||||
.then((res) => {
|
||||
setData(res.data);
|
||||
setFoldersAndInstallations(res.data);
|
||||
})
|
||||
.catch((err) => {
|
||||
if (err.response && err.response.status == 401) {
|
||||
removeToken();
|
||||
}
|
||||
});
|
||||
}, [setData]);
|
||||
}, []);
|
||||
|
||||
const createInstallation = useCallback(
|
||||
async (formValues: Partial<I_Installation>) => {
|
||||
|
@ -235,7 +236,8 @@ const InstallationsContextProvider = ({
|
|||
return (
|
||||
<InstallationsContext.Provider
|
||||
value={{
|
||||
data,
|
||||
installations,
|
||||
foldersAndInstallations,
|
||||
fetchAllInstallations,
|
||||
fetchAllFoldersAndInstallations,
|
||||
createInstallation,
|
||||
|
|
|
@ -10,12 +10,13 @@ export const LogContext = createContext<LogContextProviderProps | undefined>(
|
|||
undefined
|
||||
);
|
||||
|
||||
// Create a UserContextProvider component
|
||||
export const LogContextProvider = ({ children }: { children: ReactNode }) => {
|
||||
const [installationStatus, setInstallationStatus] = useState<
|
||||
Record<number, number[]>
|
||||
>({});
|
||||
|
||||
const BUFFER_LENGTH = 5;
|
||||
|
||||
const handleLogWarningOrError = (installation_id: number, value: number) => {
|
||||
setInstallationStatus((prevStatus) => {
|
||||
const newStatus = { ...prevStatus };
|
||||
|
@ -24,16 +25,23 @@ export const LogContextProvider = ({ children }: { children: ReactNode }) => {
|
|||
newStatus[installation_id] = [];
|
||||
}
|
||||
newStatus[installation_id].unshift(value);
|
||||
newStatus[installation_id] = newStatus[installation_id].slice(0, 5);
|
||||
newStatus[installation_id] = newStatus[installation_id].slice(
|
||||
0,
|
||||
BUFFER_LENGTH
|
||||
);
|
||||
return newStatus;
|
||||
});
|
||||
};
|
||||
|
||||
const getStatus = (installationId: number) => {
|
||||
let status;
|
||||
|
||||
if (!installationStatus.hasOwnProperty(installationId)) {
|
||||
status = -2;
|
||||
} else {
|
||||
if (installationId === 2) {
|
||||
console.log(installationStatus[2]);
|
||||
}
|
||||
if (installationStatus[installationId][0] == -1) {
|
||||
let i = 0;
|
||||
for (i; i < installationStatus[installationId].length; i++) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import {RecordSeries} from './data';
|
|||
import {isUndefined, Maybe} from './utils/maybe';
|
||||
import {isNumber} from './utils/runtimeTypeChecking';
|
||||
import {I_CsvEntry} from 'src/content/dashboards/Log/graph.util';
|
||||
import {I_S3Credentials} from '../interfaces/S3Types';
|
||||
|
||||
export const FetchResult = {
|
||||
notAvailable: 'N/A',
|
||||
|
@ -33,7 +34,10 @@ function reverseBits(x: number): number {
|
|||
}
|
||||
|
||||
export default class DataCache<T extends Record<string, I_CsvEntry>> {
|
||||
readonly _fetch: (t: UnixTime) => Promise<FetchResult<T>>;
|
||||
readonly _fetch: (
|
||||
t: UnixTime,
|
||||
s3Credentials: I_S3Credentials
|
||||
) => Promise<FetchResult<T>>;
|
||||
|
||||
public readonly gotData: Observable<UnixTime>;
|
||||
|
||||
|
@ -41,16 +45,23 @@ export default class DataCache<T extends Record<string, I_CsvEntry>> {
|
|||
|
||||
private readonly resolution: TimeSpan;
|
||||
|
||||
private readonly s3Credentials: I_S3Credentials;
|
||||
|
||||
private readonly fetchQueue = createDispatchQueue(6);
|
||||
|
||||
private readonly fetching: Set<number> = new Set<number>();
|
||||
|
||||
constructor(
|
||||
fetch: (t: UnixTime) => Promise<FetchResult<T>>,
|
||||
resolution: TimeSpan
|
||||
fetch: (
|
||||
t: UnixTime,
|
||||
s3Credentials: I_S3Credentials
|
||||
) => Promise<FetchResult<T>>,
|
||||
resolution: TimeSpan,
|
||||
s3Credentials: I_S3Credentials
|
||||
) {
|
||||
this._fetch = fetch;
|
||||
this.resolution = resolution;
|
||||
this.s3Credentials = s3Credentials;
|
||||
this.gotData = new Subject<UnixTime>();
|
||||
}
|
||||
|
||||
|
@ -71,7 +82,7 @@ export default class DataCache<T extends Record<string, I_CsvEntry>> {
|
|||
const t = time.ticks;
|
||||
|
||||
const node = this.cache.find(t);
|
||||
if (node.index !== t) this.fetchData(time);
|
||||
if (node.index !== t) this.fetchData(time, this.s3Credentials);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -82,7 +93,7 @@ export default class DataCache<T extends Record<string, I_CsvEntry>> {
|
|||
const node = this.cache.find(t);
|
||||
if (node.index === t) return node.value;
|
||||
|
||||
if (fetch) this.fetchData(time);
|
||||
if (fetch) this.fetchData(time, this.s3Credentials);
|
||||
|
||||
return this.interpolate(node, t);
|
||||
}
|
||||
|
@ -130,7 +141,7 @@ export default class DataCache<T extends Record<string, I_CsvEntry>> {
|
|||
return interpolated as T;
|
||||
}
|
||||
|
||||
private fetchData(time: UnixTime) {
|
||||
private fetchData(time: UnixTime, s3Credentials: I_S3Credentials) {
|
||||
const t = time.ticks;
|
||||
|
||||
if (this.fetching.has(t))
|
||||
|
@ -160,7 +171,7 @@ export default class DataCache<T extends Record<string, I_CsvEntry>> {
|
|||
(this.gotData as Subject<UnixTime>).next(time);
|
||||
};
|
||||
|
||||
return this._fetch(time)
|
||||
return this._fetch(time, s3Credentials)
|
||||
.then(
|
||||
(d) => onSuccess(d),
|
||||
(f) => onFailure(f)
|
||||
|
|
|
@ -7,6 +7,8 @@ export interface I_S3Credentials {
|
|||
}
|
||||
|
||||
export interface Notification {
|
||||
key: string;
|
||||
value: string;
|
||||
device: string;
|
||||
description: string;
|
||||
date: string;
|
||||
time: string;
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
"german": "Deutsch",
|
||||
"groupTabs": "Gruppen",
|
||||
"groupTree": "Gruppenbaum",
|
||||
"information": "Information",
|
||||
"inheritedAccess": "Vererbter Zugriff von",
|
||||
"installation": "Installation",
|
||||
"installationTabs": "Installationen",
|
||||
|
@ -48,5 +47,24 @@
|
|||
"live": "Live Übertragung",
|
||||
"deleteInstallation": "Installation löschen",
|
||||
"errorOccured": "Ein Fehler ist aufgetreten",
|
||||
"successfullyUpdated": "Erfolgreich aktualisiert"
|
||||
"successfullyUpdated": "Erfolgreich aktualisiert",
|
||||
"grantAccess": "Zugriff gewähren",
|
||||
"UserswithDirectAccess": "Benutzer mit direktem Zugriff",
|
||||
"UserswithInheritedAccess": "Benutzer mit geerbtem Zugriff",
|
||||
"noerrors": "Es liegen keine Fehler vor",
|
||||
"nowarnings": "Es gibt keine Warnungen",
|
||||
"noUsersWithDirectAccessToThis": "Keine Benutzer mit direktem Zugriff darauf ",
|
||||
"selectUsers": "Wählen Sie Benutzer aus",
|
||||
"cancel": "Stornieren",
|
||||
"addNewFolder": "Neuen Ordner hinzufügen",
|
||||
"addNewInstallation": "Neue Installation hinzufügen",
|
||||
"deleteFolder": "Lösche Ordner",
|
||||
"grantAccessToFolders": "Gewähren Sie Zugriff auf Ordner",
|
||||
"grantAccessToInstallations": "Gewähren Sie Zugriff auf Installationen",
|
||||
"cannotloadloggingdata": "Protokollierungsdaten können nicht geladen werden",
|
||||
"grantedAccessToUsers": "Benutzern Zugriff gewährt: ",
|
||||
"unableToGrantAccess": "Der Zugriff auf kann nicht gewährt werden: ",
|
||||
"unableToLoadData": "Daten können nicht geladen werden",
|
||||
"unableToRevokeAccess": "Der Zugriff kann nicht widerrufen werden",
|
||||
"revokedAccessFromUser": "Zugriff vom Benutzer widerrufen: "
|
||||
}
|
||||
|
|
|
@ -1,8 +1,6 @@
|
|||
{
|
||||
"liveView": "Live view",
|
||||
"allInstallations": "All installations",
|
||||
"applyChanges": "Apply changes",
|
||||
"deleteInstallation": "Delete Installation",
|
||||
"country": "Country",
|
||||
"customerName": "Customer name",
|
||||
"english": "English",
|
||||
|
@ -53,5 +51,24 @@
|
|||
"live": "Live View",
|
||||
"deleteInstallation": "Delete Installation",
|
||||
"errorOccured": "An error has occurred",
|
||||
"successfullyUpdated": "Successfully updated"
|
||||
"successfullyUpdated": "Successfully updated",
|
||||
"grantAccess": "Grant Access",
|
||||
"UserswithDirectAccess": "Users with Direct Access",
|
||||
"UserswithInheritedAccess": "Users with Inherited Access",
|
||||
"noerrors": "There are no errors",
|
||||
"nowarnings": "There are no warnings",
|
||||
"noUsersWithDirectAccessToThis": "No users with direct access to this ",
|
||||
"selectUsers": "Select Users",
|
||||
"cancel": "Cancel",
|
||||
"addNewFolder": "Add new Folder",
|
||||
"addNewInstallation": "Add new Installation",
|
||||
"deleteFolder": "Delete Folder",
|
||||
"grantAccessToFolders": "Grant Access to Folders",
|
||||
"grantAccessToInstallations": "Grant Access to Installations",
|
||||
"cannotloadloggingdata": "Cannot load logging data",
|
||||
"grantedAccessToUsers": "Granted access to users: ",
|
||||
"unableToGrantAccess": "Unable to grant access to: ",
|
||||
"unableToLoadData": "Unable to load data",
|
||||
"unableToRevokeAccess": "Unable to revoke access",
|
||||
"revokedAccessFromUser": "Revoked access from user: "
|
||||
}
|
||||
|
|
|
@ -16,7 +16,6 @@
|
|||
"german": "Allemand",
|
||||
"groupTabs": "Onglets de groupe",
|
||||
"groupTree": "Arbre de groupe",
|
||||
"information": "Informations",
|
||||
"inheritedAccess": "Accès hérité de",
|
||||
"installation": "Installation",
|
||||
"installationTabs": "Onglets d'installation",
|
||||
|
@ -48,5 +47,24 @@
|
|||
"live": "Diffusion en direct",
|
||||
"deleteInstallation": "Supprimer l'installation",
|
||||
"errorOccured": "Une erreur est survenue",
|
||||
"successfullyUpdated": "Mise à jour réussie"
|
||||
"successfullyUpdated": "Mise à jour réussie",
|
||||
"grantAccess": "Accorder l'accès",
|
||||
"UserswithDirectAccess": "Utilisateurs avec accès direct",
|
||||
"UserswithInheritedAccess": "Utilisateurs avec accès hérité",
|
||||
"noerrors": "Il n'y a pas d'erreurs",
|
||||
"nowarnings": "Il n'y a aucun avertissement",
|
||||
"noUsersWithDirectAccessToThis": "Aucun utilisateur ayant un accès direct à ceci ",
|
||||
"selectUsers": "Sélectionnez les utilisateurs",
|
||||
"cancel": "Annuler",
|
||||
"addNewFolder": "Ajouter un nouveau dossier",
|
||||
"addNewInstallation": "Ajouter une nouvelle installation",
|
||||
"deleteFolder": "Supprimer le dossier",
|
||||
"grantAccessToFolders": "Accorder l'accès aux dossiers",
|
||||
"grantAccessToInstallations": "Accorder l'accès aux installations",
|
||||
"cannotloadloggingdata": "Impossible de charger les données de journalisation",
|
||||
"grantedAccessToUsers": "Accès accordé aux utilisateurs: ",
|
||||
"unableToGrantAccess": "Impossible d'accorder l'accès à: ",
|
||||
"unableToLoadData": "Impossible de charger les données",
|
||||
"unableToRevokeAccess": "Impossible de révoquer l'accès",
|
||||
"revokedAccessFromUser": "Accès révoqué de l'utilisateur: "
|
||||
}
|
||||
|
|
|
@ -4,8 +4,7 @@ import {
|
|||
ListItem,
|
||||
ListItemText,
|
||||
Menu,
|
||||
MenuItem,
|
||||
Switch
|
||||
MenuItem
|
||||
} from '@mui/material';
|
||||
import { useContext, useRef, useState } from 'react';
|
||||
import { styled } from '@mui/material/styles';
|
||||
|
@ -98,6 +97,11 @@ function HeaderMenu(props: HeaderButtonsProps) {
|
|||
setOpen(false);
|
||||
};
|
||||
|
||||
const handleLanguageSelect = (language) => {
|
||||
props.onSelectLanguage(language);
|
||||
handleClose();
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<ListWrapper
|
||||
|
@ -129,15 +133,14 @@ function HeaderMenu(props: HeaderButtonsProps) {
|
|||
</ListItem>
|
||||
</List>
|
||||
</ListWrapper>
|
||||
<Switch checked={darkState} onChange={handleThemeChange} />
|
||||
<Menu anchorEl={ref.current} onClose={handleClose} open={isOpen}>
|
||||
<MenuItem value="en" onClick={() => props.onSelectLanguage('en')}>
|
||||
<MenuItem value="en" onClick={() => handleLanguageSelect('en')}>
|
||||
English
|
||||
</MenuItem>
|
||||
<MenuItem value="de" onClick={() => props.onSelectLanguage('de')}>
|
||||
<MenuItem value="de" onClick={() => handleLanguageSelect('de')}>
|
||||
German
|
||||
</MenuItem>
|
||||
<MenuItem value="fr" onClick={() => props.onSelectLanguage('fr')}>
|
||||
<MenuItem value="fr" onClick={() => handleLanguageSelect('fr')}>
|
||||
French
|
||||
</MenuItem>
|
||||
</Menu>
|
||||
|
|
|
@ -18,7 +18,7 @@ import SidebarMenu from './SidebarMenu';
|
|||
const SidebarWrapper = styled(Box)(
|
||||
({ theme }) => `
|
||||
width: ${theme.sidebar.width};
|
||||
min-width: ${theme.sidebar.width};
|
||||
min-width: "${theme.sidebar.width}";
|
||||
color: ${theme.colors.alpha.trueWhite[70]};
|
||||
position: relative;
|
||||
z-index: 7;
|
||||
|
@ -64,8 +64,7 @@ function Sidebar() {
|
|||
alt="innovenergy logo"
|
||||
style={{
|
||||
maxWidth: '150px', // Maximum width for the image
|
||||
maxHeight: '150px', // Maximum height for the image
|
||||
marginLeft: '50px' // Adjust the value as needed
|
||||
maxHeight: '150px' // Maximum height for the image
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
import { Box, styled } from '@mui/material';
|
||||
import { Box, Card, styled } from '@mui/material';
|
||||
import Avatar from '@mui/material/Avatar';
|
||||
|
||||
export const TabsContainerWrapper = styled(Box)(
|
||||
({ theme }) => `
|
||||
|
@ -81,3 +82,61 @@ export const TabsContainerWrapper = styled(Box)(
|
|||
}
|
||||
`
|
||||
);
|
||||
|
||||
export const AvatarWrapper = styled(Avatar)(
|
||||
({ theme }) => `
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 60px;
|
||||
margin-top: -5px;
|
||||
height: ${theme.spacing(3.5)};
|
||||
width: ${theme.spacing(3.5)};
|
||||
background: ${
|
||||
theme.palette.mode === 'dark'
|
||||
? theme.colors.alpha.trueWhite[30]
|
||||
: '#ffffff'
|
||||
};
|
||||
|
||||
img {
|
||||
background: ${theme.colors.alpha.trueWhite[100]};
|
||||
display: block;
|
||||
border-radius: inherit;
|
||||
height: ${theme.spacing(4.5)};
|
||||
width: ${theme.spacing(4.5)};
|
||||
}
|
||||
`
|
||||
);
|
||||
|
||||
export const AvatarAddWrapper = styled(Avatar)(
|
||||
({ theme }) => `
|
||||
background: ${theme.colors.alpha.black[10]};
|
||||
color: ${theme.colors.primary.main};
|
||||
width: ${theme.spacing(8)};
|
||||
height: ${theme.spacing(8)};
|
||||
`
|
||||
);
|
||||
|
||||
export const CardAddAction = styled(Card)(
|
||||
({ theme }) => `
|
||||
border: ${theme.colors.primary.main} dashed 1px;
|
||||
height: 100%;
|
||||
color: ${theme.colors.primary.main};
|
||||
transition: ${theme.transitions.create(['all'])};
|
||||
|
||||
.MuiCardActionArea-root {
|
||||
height: 100%;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.MuiTouchRipple-root {
|
||||
opacity: .2;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
border-color: ${theme.colors.alpha.black[70]};
|
||||
}
|
||||
`
|
||||
);
|
||||
|
|
|
@ -240,7 +240,7 @@ export const PureLightTheme = createTheme({
|
|||
menuItemHeadingColor: colors.layout.sidebar.menuItemHeadingColor,
|
||||
boxShadow:
|
||||
'2px 0 3px rgba(159, 162, 191, .18), 1px 0 1px rgba(159, 162, 191, 0.32)',
|
||||
width: '290px'
|
||||
width: '200px'
|
||||
},
|
||||
header: {
|
||||
height: '80px',
|
||||
|
|
Loading…
Reference in New Issue