frontend ready to implement topology view for sodiohome

This commit is contained in:
Noe 2025-01-23 14:33:21 +01:00
parent 51a34d9920
commit ac54fc6e2e
22 changed files with 860 additions and 557 deletions

View File

@ -505,7 +505,7 @@ public class Controller : ControllerBase
var foldersAndInstallations = user
.AccessibleFoldersAndInstallations(product:productId)
.AccessibleFoldersAndInstallations()
.Do(o => o.FillOrderNumbers())
.Select(o => o.HideParentIfUserHasNoAccessToParent(user))
.OfType<Object>(); // Important! JSON serializer must see Objects otherwise

View File

@ -267,35 +267,44 @@ public static class SessionMethods
.Apply(Db.Update);
}
if (installation.Product==(int)ProductType.Salidomo)
{
return user is not null
&& installation is not null
&& original is not null
&& user.UserType !=0
&& user.HasAccessToParentOf(installation)
&& installation
.Apply(Db.Update);
}
return user is not null
&& installation is not null
&& original is not null
&& user.UserType !=0
&& user.HasAccessToParentOf(installation)
&& installation
.Apply(Db.Update);
return false;
}
public static async Task<Boolean> Delete(this Session? session, Installation? installation)
{
var user = session?.User;
if (user is not null
&& installation is not null
&& user.UserType != 0)
{
if (installation.Product is (int)ProductType.Salimax or (int)ProductType.Salidomo)
{
return
Db.Delete(installation)
&& await installation.RevokeReadKey()
&& await installation.RevokeWriteKey()
&& await installation.RemoveReadRole()
&& await installation.RemoveWriteRole()
&& await installation.DeleteBucket();
}
else
{
return Db.Delete(installation);
return user is not null
&& installation is not null
&& user.UserType != 0
&& user.HasAccessTo(installation)
&& Db.Delete(installation)
&& await installation.RevokeReadKey()
&& await installation.RevokeWriteKey()
&& await installation.RemoveReadRole()
&& await installation.RemoveWriteRole()
&& await installation.DeleteBucket();
}
}
return false;
}

View File

@ -24,6 +24,18 @@ public static class UserMethods
.Distinct();
}
public static IEnumerable<Installation> AccessibleInstallations(this User user)
{
var direct = user.DirectlyAccessibleInstallations().ToList();
var fromFolders = user
.AccessibleFolders()
.SelectMany(u => u.ChildInstallations()).ToList();
return direct
.Concat(fromFolders)
.Distinct();
}
public static IEnumerable<Folder> AccessibleFolders(this User user)
{
@ -42,6 +54,16 @@ public static class UserMethods
return folders.Concat(installations);
}
public static IEnumerable<TreeNode> AccessibleFoldersAndInstallations(this User user)
{
var folders = user.AccessibleFolders() as IEnumerable<TreeNode>;
user.AccessibleInstallations().ForEach(i => i.FillOrderNumbers());
var installations = user.AccessibleInstallations();
return folders.Concat(installations);
}
public static IEnumerable<Installation> DirectlyAccessibleInstallations(this User user)
{

View File

@ -50,13 +50,20 @@ public static class WebsocketManager
Console.WriteLine("TRY TO LOCK FOR MONITOR SALIDOMO INSTALLATIONS\n");
lock (InstallationConnections){
Console.WriteLine("MONITOR SALIDOMO INSTALLATIONS\n");
foreach (var installationConnection in InstallationConnections){
Console.WriteLine("Installation ID is "+installationConnection.Key);
foreach (var installationConnection in InstallationConnections)
{
//Console.WriteLine("Installation ID is "+installationConnection.Key);
if (installationConnection.Value.Product == (int)ProductType.Salidomo && (DateTime.Now - installationConnection.Value.Timestamp) < TimeSpan.FromMinutes(60)){
Console.WriteLine("Installation ID is "+installationConnection.Key + " diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
}
if (installationConnection.Value.Product==(int)ProductType.Salidomo && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(60))
{
// Console.WriteLine("Installation ID is "+installationConnection.Key);
// Console.WriteLine("installationConnection.Value.Timestamp is "+installationConnection.Value.Timestamp);
// Console.WriteLine("diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
//Console.WriteLine("Installation ID is "+installationConnection.Key + " diff is "+(DateTime.Now-installationConnection.Value.Timestamp));
//Console.WriteLine("installationConnection.Value.Timestamp is "+installationConnection.Value.Timestamp);
//Console.WriteLine("timestamp now is is "+(DateTime.Now));
Installation installation = Db.Installations.FirstOrDefault(f => f.Product == (int)ProductType.Salidomo && f.Id == installationConnection.Key);
installation.Status = (int)StatusType.Offline;

View File

@ -54,6 +54,6 @@ INNOVENERGY_PROTOCOL_VERSION = '48TL200V3'
# S3 Credentials
S3BUCKET = "158-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e"
S3KEY = "EXOf4d6d68a9ce062f25541fe4a"
S3SECRET = "4zTQBvwIWnFYajRhoZW0F7k_6rdhnPiSqdvw9cMAZw8"
S3BUCKET = "436-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e"
S3KEY = "EXO6bb2b06f3cebfdbbc8a9b240"
S3SECRET = "m6bEzM8z9t2lCQ13OptMcZcNf80p_TSjaMDtZTNdEjo"

View File

@ -0,0 +1,393 @@
import {
Alert,
Box,
CardContent,
CircularProgress,
Container,
Grid,
IconButton,
Modal,
TextField,
Typography,
useTheme
} from '@mui/material';
import { FormattedMessage } from 'react-intl';
import Button from '@mui/material/Button';
import { Close as CloseIcon } from '@mui/icons-material';
import React, { useContext, useState } from 'react';
import { I_Installation } from '../../../interfaces/InstallationTypes';
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
import { UserContext } from '../../../contexts/userContext';
import routes from '../../../Resources/routes.json';
import { useNavigate } from 'react-router-dom';
import { UserType } from '../../../interfaces/UserTypes';
interface InformationSodioHomeProps {
values: I_Installation;
type?: string;
}
function InformationSodioHome(props: InformationSodioHomeProps) {
if (props.values === null) {
return null;
}
const context = useContext(UserContext);
const { currentUser } = context;
const theme = useTheme();
const [formValues, setFormValues] = useState(props.values);
const requiredFields = ['name', 'region', 'location', 'country'];
const [openModalDeleteInstallation, setOpenModalDeleteInstallation] =
useState(false);
const navigate = useNavigate();
const DeviceTypes = ['Cerbo', 'Venus'];
const installationContext = useContext(InstallationsContext);
const {
updateInstallation,
deleteInstallation,
loading,
setLoading,
error,
setError,
updated,
setUpdated
} = installationContext;
const handleChange = (e) => {
const { name, value } = e.target;
setFormValues({
...formValues,
[name]: value
});
};
const handleSubmit = () => {
setLoading(true);
setError(false);
updateInstallation(formValues, props.type);
};
const handleDelete = () => {
setLoading(true);
setError(false);
setOpenModalDeleteInstallation(true);
};
const deleteInstallationModalHandle = () => {
setOpenModalDeleteInstallation(false);
deleteInstallation(formValues, props.type);
setLoading(false);
navigate(routes.salidomo_installations + routes.list, {
replace: true
});
};
const deleteInstallationModalHandleCancel = () => {
setOpenModalDeleteInstallation(false);
setLoading(false);
};
const areRequiredFieldsFilled = () => {
for (const field of requiredFields) {
if (!formValues[field]) {
return false;
}
}
return true;
};
return (
<>
{openModalDeleteInstallation && (
<Modal
open={openModalDeleteInstallation}
onClose={deleteInstallationModalHandleCancel}
aria-labelledby="error-modal"
aria-describedby="error-modal-description"
>
<Box
sx={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
width: 350,
bgcolor: 'background.paper',
borderRadius: 4,
boxShadow: 24,
p: 4,
display: 'flex',
flexDirection: 'column',
alignItems: 'center'
}}
>
<Typography
variant="body1"
gutterBottom
sx={{ fontWeight: 'bold' }}
>
Do you want to delete this installation?
</Typography>
<div
style={{
display: 'flex',
alignItems: 'center',
marginTop: 10
}}
>
<Button
sx={{
marginTop: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={deleteInstallationModalHandle}
>
Delete
</Button>
<Button
sx={{
marginTop: 2,
marginLeft: 2,
textTransform: 'none',
bgcolor: '#ffc04d',
color: '#111111',
'&:hover': { bgcolor: '#f7b34d' }
}}
onClick={deleteInstallationModalHandleCancel}
>
Cancel
</Button>
</div>
</Box>
</Modal>
)}
<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={
<FormattedMessage
id="installation_name"
defaultMessage="Installation Name"
/>
}
name="name"
value={formValues.name}
onChange={handleChange}
variant="outlined"
fullWidth
/>
</div>
<div>
<TextField
label={
<FormattedMessage id="region" defaultMessage="Region" />
}
name="region"
value={formValues.region}
onChange={handleChange}
variant="outlined"
fullWidth
required
error={formValues.region === ''}
/>
</div>
<div>
<TextField
label={
<FormattedMessage
id="location"
defaultMessage="Location"
/>
}
name="location"
value={formValues.location}
onChange={handleChange}
variant="outlined"
fullWidth
required
error={formValues.location === ''}
/>
</div>
<div>
<TextField
label={
<FormattedMessage id="country" defaultMessage="Country" />
}
name="country"
value={formValues.country}
onChange={handleChange}
variant="outlined"
fullWidth
required
error={formValues.country === ''}
/>
</div>
<div>
<TextField
label={
<FormattedMessage
id="information"
defaultMessage="Information"
/>
}
name="information"
value={formValues.information}
onChange={handleChange}
variant="outlined"
fullWidth
/>
</div>
{currentUser.userType == UserType.admin && (
<>
<div>
<TextField
label="BitWatt Cloud Access Key"
name="s3WriteKey"
value={formValues.s3WriteKey}
onChange={handleChange}
variant="outlined"
fullWidth
/>
</div>
<div>
<TextField
label="BitWatt Cloud Secret Key"
name="s3WriteSecret"
onChange={handleChange}
value={formValues.s3WriteSecret}
variant="outlined"
fullWidth
/>
</div>
</>
)}
<div
style={{
display: 'flex',
alignItems: 'center',
marginTop: 10
}}
>
<Button
variant="contained"
onClick={handleSubmit}
sx={{
marginLeft: '10px'
}}
disabled={!areRequiredFieldsFilled()}
>
<FormattedMessage
id="applyChanges"
defaultMessage="Apply Changes"
/>
</Button>
{currentUser.userType == UserType.admin && (
<Button
variant="contained"
onClick={handleDelete}
sx={{
marginLeft: '10px'
}}
>
<FormattedMessage
id="deleteInstallation"
defaultMessage="Delete Installation"
/>
</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)}
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>
</Container>
</>
);
}
export default InformationSodioHome;

View File

@ -115,11 +115,11 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
<TableBody>
{sortedInstallations.map((installation) => {
const isInstallationSelected =
installation.s3BucketId === selectedInstallation;
installation.id === selectedInstallation;
const status = installation.status;
const rowStyles =
isRowHovered === installation.s3BucketId
isRowHovered === installation.id
? {
cursor: 'pointer',
backgroundColor: theme.colors.primary.lighter
@ -129,15 +129,13 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
return (
<TableRow
hover
key={installation.s3BucketId}
key={installation.id}
selected={isInstallationSelected}
style={rowStyles}
onClick={() =>
handleSelectOneInstallation(installation.s3BucketId)
}
onMouseEnter={() =>
handleRowMouseEnter(installation.s3BucketId)
handleSelectOneInstallation(installation.id)
}
onMouseEnter={() => handleRowMouseEnter(installation.id)}
onMouseLeave={() => handleRowMouseLeave()}
>
<TableCell>

View File

@ -63,11 +63,11 @@ function InstallationSearch(props: installationSearchProps) {
{filteredData.map((installation) => {
return (
<Route
key={installation.s3BucketId}
path={routes.installation + installation.s3BucketId + '*'}
key={installation.id}
path={routes.installation + installation.id + '*'}
element={
<Installation
key={installation.s3BucketId}
key={installation.id}
current_installation={installation}
type="installation"
></Installation>

View File

@ -137,15 +137,15 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
// )
.map((installation) => {
const isInstallationSelected =
installation.s3BucketId === selectedInstallation;
installation.id === selectedInstallation;
const status = installation.status;
return (
<HoverableTableRow
key={installation.s3BucketId}
key={installation.id}
onClick={() =>
handleSelectOneInstallation(installation.s3BucketId)
handleSelectOneInstallation(installation.id)
}
>
<TableCell>

View File

@ -82,11 +82,11 @@ function InstallationSearch(props: installationSearchProps) {
{filteredData.map((installation) => {
return (
<Route
key={installation.s3BucketId}
path={routes.installation + installation.s3BucketId + '*'}
key={installation.id}
path={routes.installation + installation.id + '*'}
element={
<SalidomoInstallation
key={installation.s3BucketId}
key={installation.id}
current_installation={installation}
type="installation"
></SalidomoInstallation>

View File

@ -57,7 +57,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
routes.installation +
`${installationID}` +
'/' +
routes.batteryview,
routes.information,
{
replace: true
}
@ -107,12 +107,6 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
<TableCell>
<FormattedMessage id="country" defaultMessage="Country" />
</TableCell>
<TableCell>
<FormattedMessage id="VRM Link" defaultMessage="VRM Link" />
</TableCell>
<TableCell>
<FormattedMessage id="Device" defaultMessage="Device" />
</TableCell>
<TableCell>
<FormattedMessage id="status" defaultMessage="Status" />
</TableCell>
@ -127,15 +121,15 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
// )
.map((installation) => {
const isInstallationSelected =
installation.s3BucketId === selectedInstallation;
installation.id === selectedInstallation;
const status = installation.status;
return (
<HoverableTableRow
key={installation.s3BucketId}
key={installation.id}
onClick={() =>
handleSelectOneInstallation(installation.s3BucketId)
handleSelectOneInstallation(installation.id)
}
>
<TableCell>
@ -190,79 +184,6 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
</Typography>
</TableCell>
<TableCell>
<Typography
variant="body2"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '10px', fontSize: 'small' }}
>
<a
href={
'https://vrm.victronenergy.com/installation/' +
installation.vrmLink +
'/dashboard'
}
target="_blank"
rel="noopener noreferrer"
style={{
display: 'inline-block',
padding: '0'
}} // Style the link
onClick={(e) => e.stopPropagation()} // Prevent the click event from bubbling up to the table row
>
VRM link
</a>
</Typography>
</TableCell>
<TableCell>
<div
style={{
display: 'flex',
alignItems: 'center',
marginLeft: '5px'
}}
>
{installation.device === 1 ? (
<Typography
variant="body2"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '10px', fontSize: 'small' }}
>
Cerbo
</Typography>
) : installation.device === 2 ? (
<Typography
variant="body2"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '10px', fontSize: 'small' }}
>
Venus
</Typography>
) : (
<Typography
variant="body2"
fontWeight="bold"
color="text.primary"
gutterBottom
noWrap
sx={{ marginTop: '10px', fontSize: 'small' }}
>
Device not specified
</Typography>
)}
</div>
</TableCell>
<TableCell>
<div
style={{

View File

@ -1,6 +1,25 @@
import React, { useContext, useEffect, useState } from 'react';
import {
Card,
CircularProgress,
Container,
Grid,
Typography
} from '@mui/material';
import { I_Installation } from 'src/interfaces/InstallationTypes';
import React from 'react';
import { Grid } from '@mui/material';
import { UserContext } from 'src/contexts/userContext';
import { TopologyValues } from 'src/content/dashboards/Log/graph.util';
import { FormattedMessage } from 'react-intl';
import { Navigate, Route, Routes, useLocation } from 'react-router-dom';
import routes from '../../../Resources/routes.json';
import Log from '../Log/Log';
import CancelIcon from '@mui/icons-material/Cancel';
import { UserType } from '../../../interfaces/UserTypes';
import HistoryOfActions from '../History/History';
import BuildIcon from '@mui/icons-material/Build';
import AccessContextProvider from '../../../contexts/AccessContextProvider';
import Access from '../ManageAccess/Access';
import InformationSodioHome from '../Information/InformationSodioHome';
interface singleInstallationProps {
current_installation?: I_Installation;
@ -8,383 +27,266 @@ interface singleInstallationProps {
}
function SodioHomeInstallation(props: singleInstallationProps) {
// const context = useContext(UserContext);
// const { currentUser } = context;
// const location = useLocation().pathname;
// const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false);
// const [currentTab, setCurrentTab] = useState<string>(undefined);
// const [values, setValues] = useState<TopologyValues | null>(null);
// const status = props.current_installation.status;
// const [
// failedToCommunicateWithInstallation,
// setFailedToCommunicateWithInstallation
// ] = useState(0);
// const [connected, setConnected] = useState(true);
// const [loading, setLoading] = useState(true);
//
// if (props.current_installation == undefined) {
// return null;
// }
//
// const S3data = {
// s3Region: props.current_installation.s3Region,
// s3Provider: props.current_installation.s3Provider,
// s3Key: props.current_installation.s3Key,
// s3Secret: props.current_installation.s3Secret,
// s3BucketId: props.current_installation.s3BucketId
// };
//
// const s3Bucket =
// props.current_installation.s3BucketId.toString() +
// '-' +
// 'c0436b6a-d276-4cd8-9c44-1eae86cf5d0e';
//
// const [fetchFunctionCalled, setFetchFunctionCalled] = useState(false);
// const s3Credentials = { s3Bucket, ...S3data };
//
// function timeout(delay: number) {
// return new Promise((res) => setTimeout(res, delay));
// }
//
// const continueFetching = useRef(false);
//
// const fetchDataPeriodically = async () => {
// var timeperiodToSearch = 30;
// let res;
// let timestampToFetch;
//
// for (var i = 0; i < timeperiodToSearch; i += 1) {
// if (!continueFetching.current) {
// return false;
// }
// timestampToFetch = UnixTime.now().earlier(TimeSpan.fromMinutes(i));
// console.log('timestamp to fetch is ' + timestampToFetch);
//
// try {
// res = await fetchData(timestampToFetch, s3Credentials, true);
// if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) {
// break;
// }
// } catch (err) {
// console.error('Error fetching data:', err);
// return false;
// }
// }
//
// if (i >= timeperiodToSearch) {
// setConnected(false);
// setLoading(false);
// return false;
// }
// setConnected(true);
// setLoading(false);
// console.log('NUMBER OF FILES=' + Object.keys(res).length);
//
// while (continueFetching.current) {
// for (const timestamp of Object.keys(res)) {
// if (!continueFetching.current) {
// setFetchFunctionCalled(false);
// return false;
// }
// console.log(`Timestamp: ${timestamp}`);
// console.log(res[timestamp]);
//
// // Set values asynchronously with delay
// setValues(
// extractValues({
// time: UnixTime.fromTicks(parseInt(timestamp, 10)),
// value: res[timestamp]
// })
// );
// // Wait for 2 seconds before processing next timestamp
// await timeout(2000);
// }
//
// timestampToFetch = timestampToFetch.later(TimeSpan.fromMinutes(20));
// console.log('NEW TIMESTAMP TO FETCH IS ' + timestampToFetch);
//
// for (i = 0; i < 10; i++) {
// if (!continueFetching.current) {
// return false;
// }
//
// try {
// console.log('Trying to fetch timestamp ' + timestampToFetch);
// res = await fetchData(timestampToFetch, s3Credentials, true);
// if (
// res !== FetchResult.notAvailable &&
// res !== FetchResult.tryLater
// ) {
// break;
// }
// } catch (err) {
// console.error('Error fetching data:', err);
// return false;
// }
// timestampToFetch = timestampToFetch.later(TimeSpan.fromMinutes(1));
// }
// }
// };
// useEffect(() => {
// let path = location.split('/');
// setCurrentTab(path[path.length - 1]);
// }, [location]);
//
// useEffect(() => {
// if (location.includes('batteryview')) {
// if (location.includes('batteryview') && !location.includes('mainstats')) {
// if (!continueFetching.current) {
// continueFetching.current = true;
// if (!fetchFunctionCalled) {
// setFetchFunctionCalled(true);
// fetchDataPeriodically();
// }
// }
// }
//
// return () => {
// continueFetching.current = false;
// };
// } else {
// continueFetching.current = false;
// }
// }, [currentTab, location]);
//
// useEffect(() => {
// if (status === null) {
// setConnected(false);
// }
// }, [status]);
//
const context = useContext(UserContext);
const { currentUser } = context;
const location = useLocation().pathname;
const [errorLoadingS3Data, setErrorLoadingS3Data] = useState(false);
const [currentTab, setCurrentTab] = useState<string>(undefined);
const [values, setValues] = useState<TopologyValues | null>(null);
const status = props.current_installation.status;
const [
failedToCommunicateWithInstallation,
setFailedToCommunicateWithInstallation
] = useState(0);
const [connected, setConnected] = useState(true);
const [loading, setLoading] = useState(true);
return <Grid item xs={12} md={12}></Grid>;
if (props.current_installation == undefined) {
return null;
}
// return (
// <>
// <Grid item xs={12} md={12}>
// <div style={{ display: 'flex', alignItems: 'center' }}>
// <Typography
// fontWeight="bold"
// color="text.primary"
// noWrap
// sx={{
// marginTop: '-20px',
// marginBottom: '10px',
// fontSize: '14px'
// }}
// >
// <FormattedMessage
// id="installation_name_simple"
// defaultMessage="Installation Name:"
// />
// </Typography>
// <Typography
// fontWeight="bold"
// color="orange"
// noWrap
// sx={{
// marginTop: '-20px',
// marginBottom: '10px',
// marginLeft: '5px',
// fontSize: '14px'
// }}
// >
// {props.current_installation.name}
// </Typography>
// </div>
// <div style={{ display: 'flex', alignItems: 'center' }}>
// <Typography
// fontWeight="bold"
// color="text.primary"
// noWrap
// sx={{
// marginTop: '0px',
// marginBottom: '10px',
// fontSize: '14px'
// }}
// >
// Status:
// </Typography>
// <div
// style={{
// display: 'flex',
// alignItems: 'center',
// marginLeft: '75px',
// marginTop: '-10px'
// }}
// >
// {status === -1 ? (
// <CancelIcon
// style={{
// width: '23px',
// height: '23px',
// color: 'red',
// borderRadius: '50%'
// }}
// />
// ) : (
// ''
// )}
//
// {status === -2 ? (
// <CircularProgress
// size={20}
// sx={{
// color: '#f7b34d'
// }}
// />
// ) : (
// ''
// )}
//
// <div
// style={{
// width: '20px',
// height: '20px',
// marginLeft: '2px',
// borderRadius: '50%',
// backgroundColor:
// status === 2
// ? 'red'
// : status === 1
// ? 'orange'
// : status === -1 || status === -2
// ? 'transparent'
// : 'green'
// }}
// />
//
// {props.current_installation.testingMode && (
// <BuildIcon
// style={{
// width: '23px',
// height: '23px',
// color: 'purple',
// borderRadius: '50%',
// position: 'relative',
// zIndex: 1,
// marginLeft: '15px'
// }}
// />
// )}
// </div>
// </div>
// {loading &&
// currentTab != 'information' &&
// currentTab != 'overview' &&
// currentTab != 'manage' &&
// currentTab != 'history' &&
// currentTab != 'log' && (
// <Container
// maxWidth="xl"
// sx={{
// display: 'flex',
// flexDirection: 'column',
// justifyContent: 'center',
// alignItems: 'center',
// height: '70vh'
// }}
// >
// <CircularProgress size={60} style={{ color: '#ffc04d' }} />
// <Typography
// variant="body2"
// style={{ color: 'black', fontWeight: 'bold' }}
// mt={2}
// >
// Connecting to the device...
// </Typography>
// </Container>
// )}
//
// <Card variant="outlined">
// <Grid
// container
// direction="row"
// justifyContent="center"
// alignItems="stretch"
// spacing={0}
// >
// <Routes>
// <Route
// path={routes.information}
// element={
// <InformationSalidomo
// values={props.current_installation}
// s3Credentials={s3Credentials}
// type={props.type}
// ></InformationSalidomo>
// }
// />
//
// <Route
// path={routes.log}
// element={
// <Log
// errorLoadingS3Data={errorLoadingS3Data}
// id={props.current_installation.id}
// ></Log>
// }
// />
//
// <Route
// path={routes.overview}
// element={
// <SalidomoOverview
// s3Credentials={s3Credentials}
// id={props.current_installation.id}
// ></SalidomoOverview>
// }
// />
//
// <Route
// path={routes.batteryview + '*'}
// element={
// <BatteryView
// values={values}
// s3Credentials={s3Credentials}
// installationId={props.current_installation.id}
// productNum={props.current_installation.product}
// connected={connected}
// ></BatteryView>
// }
// ></Route>
//
// {currentUser.userType == UserType.admin && (
// <Route
// path={routes.history}
// element={
// <HistoryOfActions
// errorLoadingS3Data={errorLoadingS3Data}
// id={props.current_installation.id}
// ></HistoryOfActions>
// }
// />
// )}
//
// {currentUser.userType == UserType.admin && (
// <Route
// path={routes.manage}
// element={
// <AccessContextProvider>
// <Access
// currentResource={props.current_installation}
// resourceType={props.type}
// ></Access>
// </AccessContextProvider>
// }
// />
// )}
//
// <Route
// path={'*'}
// element={<Navigate to={routes.batteryview}></Navigate>}
// />
// </Routes>
// </Grid>
// </Card>
// </Grid>
// </>
// );
const [fetchFunctionCalled, setFetchFunctionCalled] = useState(false);
function timeout(delay: number) {
return new Promise((res) => setTimeout(res, delay));
}
useEffect(() => {
let path = location.split('/');
setCurrentTab(path[path.length - 1]);
}, [location]);
useEffect(() => {
if (status === null) {
setConnected(false);
}
}, [status]);
return (
<>
<Grid item xs={12} md={12}>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Typography
fontWeight="bold"
color="text.primary"
noWrap
sx={{
marginTop: '-20px',
marginBottom: '10px',
fontSize: '14px'
}}
>
<FormattedMessage
id="installation_name_simple"
defaultMessage="Installation Name:"
/>
</Typography>
<Typography
fontWeight="bold"
color="orange"
noWrap
sx={{
marginTop: '-20px',
marginBottom: '10px',
marginLeft: '5px',
fontSize: '14px'
}}
>
{props.current_installation.name}
</Typography>
</div>
<div style={{ display: 'flex', alignItems: 'center' }}>
<Typography
fontWeight="bold"
color="text.primary"
noWrap
sx={{
marginTop: '0px',
marginBottom: '10px',
fontSize: '14px'
}}
>
Status:
</Typography>
<div
style={{
display: 'flex',
alignItems: 'center',
marginLeft: '75px',
marginTop: '-10px'
}}
>
{status === -1 ? (
<CancelIcon
style={{
width: '23px',
height: '23px',
color: 'red',
borderRadius: '50%'
}}
/>
) : (
''
)}
{status === -2 ? (
<CircularProgress
size={20}
sx={{
color: '#f7b34d'
}}
/>
) : (
''
)}
<div
style={{
width: '20px',
height: '20px',
marginLeft: '2px',
borderRadius: '50%',
backgroundColor:
status === 2
? 'red'
: status === 1
? 'orange'
: status === -1 || status === -2
? 'transparent'
: 'green'
}}
/>
{props.current_installation.testingMode && (
<BuildIcon
style={{
width: '23px',
height: '23px',
color: 'purple',
borderRadius: '50%',
position: 'relative',
zIndex: 1,
marginLeft: '15px'
}}
/>
)}
</div>
</div>
{loading &&
currentTab != 'information' &&
currentTab != 'overview' &&
currentTab != 'manage' &&
currentTab != 'history' &&
currentTab != 'log' && (
<Container
maxWidth="xl"
sx={{
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
height: '70vh'
}}
>
<CircularProgress size={60} style={{ color: '#ffc04d' }} />
<Typography
variant="body2"
style={{ color: 'black', fontWeight: 'bold' }}
mt={2}
>
Connecting to the device...
</Typography>
</Container>
)}
<Card variant="outlined">
<Grid
container
direction="row"
justifyContent="center"
alignItems="stretch"
spacing={0}
>
<Routes>
<Route
path={routes.information}
element={
<InformationSodioHome
values={props.current_installation}
type={props.type}
></InformationSodioHome>
}
/>
<Route
path={routes.log}
element={
<Log
errorLoadingS3Data={errorLoadingS3Data}
id={props.current_installation.id}
></Log>
}
/>
{/*<Route*/}
{/* path={routes.overview}*/}
{/* element={*/}
{/* <SalidomoOverview*/}
{/* s3Credentials={s3Credentials}*/}
{/* id={props.current_installation.id}*/}
{/* ></SalidomoOverview>*/}
{/* }*/}
{/*/>*/}
{/*<Route*/}
{/* path={routes.batteryview + '*'}*/}
{/* element={*/}
{/* <BatteryView*/}
{/* values={values}*/}
{/* s3Credentials={s3Credentials}*/}
{/* installationId={props.current_installation.id}*/}
{/* productNum={props.current_installation.product}*/}
{/* connected={connected}*/}
{/* ></BatteryView>*/}
{/* }*/}
{/*></Route>*/}
{currentUser.userType == UserType.admin && (
<Route
path={routes.history}
element={
<HistoryOfActions
errorLoadingS3Data={errorLoadingS3Data}
id={props.current_installation.id}
></HistoryOfActions>
}
/>
)}
{currentUser.userType == UserType.admin && (
<Route
path={routes.manage}
element={
<AccessContextProvider>
<Access
currentResource={props.current_installation}
resourceType={props.type}
></Access>
</AccessContextProvider>
}
/>
)}
<Route
path={'*'}
element={<Navigate to={routes.information}></Navigate>}
/>
</Routes>
</Grid>
</Card>
</Grid>
</>
);
}
export default SodioHomeInstallation;

View File

@ -2,7 +2,7 @@ import React, { useMemo, useState } from 'react';
import { FormControl, Grid, InputAdornment, TextField } from '@mui/material';
import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone';
import { I_Installation } from '../../../interfaces/InstallationTypes';
import { Route, Routes,useLocation } from 'react-router-dom';
import { Route, Routes, useLocation } from 'react-router-dom';
import routes from '../../../Resources/routes.json';
import FlatInstallationView from './FlatInstallationView';
import SodioHomeInstallation from './Installation';
@ -82,11 +82,11 @@ function InstallationSearch(props: installationSearchProps) {
{filteredData.map((installation) => {
return (
<Route
key={installation.s3BucketId}
path={routes.installation + installation.s3BucketId + '*'}
key={installation.id}
path={routes.installation + installation.id + '*'}
element={
<SodioHomeInstallation
key={installation.s3BucketId}
key={installation.id}
current_installation={installation}
type="installation"
></SodioHomeInstallation>

View File

@ -3,12 +3,8 @@ import {
Alert,
Box,
CircularProgress,
FormControl,
IconButton,
InputLabel,
MenuItem,
Modal,
Select,
TextField,
useTheme
} from '@mui/material';
@ -32,7 +28,9 @@ function SodiohomeInstallationForm(props: SodiohomeInstallationFormProps) {
region: '',
location: '',
country: '',
serialNumber: ''
serialNumber: '',
s3WriteSecret: '',
s3WriteKey: ''
});
const requiredFields = ['name', 'location', 'country', 'serialNumber'];
@ -159,7 +157,10 @@ function SodiohomeInstallationForm(props: SodiohomeInstallationFormProps) {
<div>
<TextField
label={
<FormattedMessage id="serialNumber" defaultMessage="Serial Number" />
<FormattedMessage
id="serialNumber"
defaultMessage="Serial Number"
/>
}
name="serialNumber"
value={formValues.serialNumber}
@ -169,6 +170,38 @@ function SodiohomeInstallationForm(props: SodiohomeInstallationFormProps) {
/>
</div>
<div>
<TextField
label={
<FormattedMessage
id="s3WriteKey"
defaultMessage="BitWatt Cloud Access Key"
/>
}
name="s3WriteKey"
value={formValues.s3WriteKey}
onChange={handleChange}
required
error={formValues.s3WriteKey === ''}
/>
</div>
<div>
<TextField
label={
<FormattedMessage
id="s3WriteSecret"
defaultMessage="BitWatt Cloud Secret Key"
/>
}
name="s3WriteSecret"
value={formValues.s3WriteSecret}
onChange={handleChange}
required
error={formValues.s3WriteSecret === ''}
/>
</div>
<div>
<TextField
label={

View File

@ -66,16 +66,16 @@ function SodioHomeInstallationTabs() {
useEffect(() => {
if (sodiohomeInstallations && sodiohomeInstallations.length > 0) {
if (!socket) {
openSocket(1);
} else if (currentProduct == 0) {
openSocket(2);
} else if (currentProduct != 2) {
closeSocket();
openSocket(1);
openSocket(2);
}
}
}, [sodiohomeInstallations]);
useEffect(() => {
setProduct(1);
setProduct(2);
}, []);
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {

View File

@ -42,18 +42,30 @@ function CustomTreeItem(props: CustomTreeItemProps) {
const navigate = useNavigate();
const [selected, setSelected] = useState(false);
const currentLocation = useLocation();
const { product, setProduct } = useContext(ProductIdContext);
const { product } = useContext(ProductIdContext);
const handleSelectOneInstallation = (): void => {
let installation = props.node;
let path =
product == 0 ? routes.installations : routes.salidomo_installations;
let installation_path =
installation.product == 0
? routes.installations
: installation.product == 1
? routes.salidomo_installations
: routes.sodiohome_installations;
let folder_path =
product == 0
? routes.installations
: product == 1
? routes.salidomo_installations
: routes.sodiohome_installations;
if (installation.type != 'Folder') {
navigate(
path +
installation_path +
routes.tree +
routes.installation +
installation.s3BucketId +
installation.id +
'/' +
routes.live,
{
@ -63,7 +75,7 @@ function CustomTreeItem(props: CustomTreeItemProps) {
setSelected(!selected);
} else {
navigate(
path +
folder_path +
routes.tree +
routes.folder +
installation.id +
@ -164,6 +176,8 @@ function CustomTreeItem(props: CustomTreeItemProps) {
currentLocation.pathname ===
routes.salidomo_installations + routes.tree ||
currentLocation.pathname === routes.installations + routes.tree ||
currentLocation.pathname ===
routes.sodiohome_installations + routes.tree ||
currentLocation.pathname.includes('folder')
? 'block'
: 'none',

View File

@ -58,7 +58,6 @@ function TreeInformation(props: TreeInformationProps) {
deleteFolder
} = installationContext;
//const { product, setProduct } = useContext(ProductIdContext);
const [product, setProduct] = useState('Salimax');
const handleChangeInstallationChoice = (e) => {

View File

@ -9,13 +9,13 @@ import { InstallationsContext } from 'src/contexts/InstallationsContextProvider'
import { Route, Routes } from 'react-router-dom';
import routes from '../../../Resources/routes.json';
import SalidomoInstallation from '../SalidomoInstallations/Installation';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
import Folder from './Folder';
import SodioHomeInstallation from '../SodiohomeInstallations/Installation';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
function InstallationTree() {
const { foldersAndInstallations, fetchAllFoldersAndInstallations } =
useContext(InstallationsContext);
const { product, setProduct } = useContext(ProductIdContext);
const sortedInstallations = [...foldersAndInstallations].sort((a, b) => {
// Compare the status field of each installation and sort them based on the status.
@ -36,8 +36,10 @@ function InstallationTree() {
return 0;
});
const { product } = useContext(ProductIdContext);
useEffect(() => {
fetchAllFoldersAndInstallations(product);
fetchAllFoldersAndInstallations();
}, []);
const TreeNode = ({ node, parent_id }) => {
@ -50,11 +52,7 @@ function InstallationTree() {
subnode != node &&
subnode.parentId == node.id && (
<TreeNode
key={
subnode.type == 'Installation'
? subnode.s3BucketId.toString() + subnode.type
: subnode.id.toString() + subnode.type
}
key={subnode.id.toString() + subnode.type}
node={subnode}
parent_id={node.id}
/>
@ -82,21 +80,27 @@ function InstallationTree() {
if (installation.type == 'Installation') {
return (
<Route
key={installation.s3BucketId}
path={routes.installation + installation.s3BucketId + '*'}
key={installation.id}
path={routes.installation + installation.id + '*'}
element={
product == 0 ? (
installation.product == 0 ? (
<Installation
key={installation.s3BucketId}
key={installation.id}
current_installation={installation}
type="installation"
></Installation>
) : (
) : installation.product == 1 ? (
<SalidomoInstallation
key={installation.s3BucketId}
key={installation.id}
current_installation={installation}
type="installation"
></SalidomoInstallation>
) : (
<SodioHomeInstallation
key={installation.id}
current_installation={installation}
type="installation"
></SodioHomeInstallation>
)
}
/>
@ -126,11 +130,7 @@ function InstallationTree() {
{foldersAndInstallations.map((node, index) => {
return (
<TreeNode
key={
node.type == 'Installation'
? node.s3BucketId.toString() + node.type
: node.id.toString() + node.type
}
key={node.id.toString() + node.type}
node={node}
parent_id={'0'}
/>

View File

@ -253,6 +253,7 @@ const InstallationsContextProvider = ({
.put('/UpdateInstallation', formValues)
.then(() => {
setUpdated(true);
setLoading(false);
if (formValues.product === 0 && view === 'installation')
fetchAllInstallations();
else if (formValues.product === 1 && view === 'installation')
@ -286,6 +287,7 @@ const InstallationsContextProvider = ({
.delete(`/DeleteInstallation?installationId=${formValues.id}`)
.then(() => {
setUpdated(true);
setLoading(false);
if (formValues.product === 0 && view === 'installation')
fetchAllInstallations();
else if (formValues.product === 1 && view === 'installation')
@ -317,7 +319,10 @@ const InstallationsContextProvider = ({
async (formValues: Partial<I_Folder>, product: number) => {
axiosConfig
.post('/CreateFolder', formValues)
.then(() => fetchAllFoldersAndInstallations(product))
.then(() => {
setLoading(false);
fetchAllFoldersAndInstallations(product);
})
.catch((error) => {
setError(true);
if (error.response?.status === 401) {
@ -334,6 +339,7 @@ const InstallationsContextProvider = ({
axiosConfig
.put('/UpdateFolder', formValues)
.then(() => {
setLoading(false);
setUpdated(true);
fetchAllFoldersAndInstallations(product);
@ -357,6 +363,7 @@ const InstallationsContextProvider = ({
.delete(`/DeleteFolder?folderId=${formValues.id}`)
.then(() => {
setUpdated(true);
setLoading(false);
fetchAllFoldersAndInstallations(product);
setTimeout(() => setUpdated(false), 3000);
})

View File

@ -18,14 +18,6 @@ export const ProductIdContext = createContext<ProductIdContextType | undefined>(
undefined
);
// Define the product mapping for dynamic assignment
// const productMapping: { [key: string]: number } = {
// salimax: 0,
// salidomo: 1,
// sodiohome: 2,
// // Additional mappings can be added here
// };
// Create a UserContextProvider component
export const ProductIdContextProvider = ({
children
@ -60,6 +52,7 @@ export const ProductIdContextProvider = ({
});
const changeProductId = (new_product: number) => {
// console.log('from provider' + new_product);
setProduct(new_product);
};

View File

@ -28,6 +28,7 @@ export interface I_Folder {
name: string;
information: string;
parentId: number;
product: number;
type: string;
s3BucketId: number;
status?: number;

View File

@ -164,7 +164,8 @@ function SidebarMenu() {
const { closeSidebar } = useContext(SidebarContext);
const context = useContext(UserContext);
const { currentUser, setUser } = context;
const { accessToSalimax, accessToSalidomo,accessToSodiohome } = useContext(ProductIdContext);
const { accessToSalimax, accessToSalidomo, accessToSodiohome } =
useContext(ProductIdContext);
return (
<>
@ -228,7 +229,10 @@ function SidebarMenu() {
startIcon={<BrightnessLowTwoToneIcon />}
>
<Box sx={{ marginTop: '3px' }}>
<FormattedMessage id="sodiohome" defaultMessage="Sodiohome" />
<FormattedMessage
id="sodiohome"
defaultMessage="Sodiohome"
/>
</Box>
</Button>
</ListItem>