frontend ready to implement index.tsx for Sodiohome

This commit is contained in:
Noe 2025-01-21 09:24:35 +01:00
parent 3ea01e3df0
commit 25310a4250
5 changed files with 910 additions and 0 deletions

View File

@ -468,6 +468,19 @@ public class Controller : ControllerBase
.ToList();
}
[HttpGet(nameof(GetAllSodioHomeInstallations))]
public ActionResult<IEnumerable<Installation>> GetAllSodioHomeInstallations(Token authToken)
{
var user = Db.GetSession(authToken)?.User;
if (user is null)
return Unauthorized();
return user
.AccessibleInstallations(product:(int)ProductType.SodioHome)
.ToList();
}
[HttpGet(nameof(GetAllFolders))]

View File

@ -232,6 +232,17 @@ public static class SessionMethods
&& await installation.CreateBucket()
&& await installation.RenewS3Credentials();
}
if (installation.Product == (int)ProductType.SodioHome)
{
return user is not null
&& user.UserType != 0
&& user.HasAccessToParentOf(installation)
&& Db.Create(installation);
}
return false;
}

View File

@ -0,0 +1,390 @@
import { I_Installation } from 'src/interfaces/InstallationTypes';
import React from 'react';
import { Grid } from '@mui/material';
interface singleInstallationProps {
current_installation?: I_Installation;
type?: string;
}
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]);
//
return <Grid item xs={12} md={12}></Grid>;
// 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>
// </>
// );
}
export default SodioHomeInstallation;

View File

@ -0,0 +1,100 @@
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 { useLocation } from 'react-router-dom';
import routes from '../../../Resources/routes.json';
interface installationSearchProps {
installations: I_Installation[];
}
function InstallationSearch(props: installationSearchProps) {
const [searchTerm, setSearchTerm] = useState('');
const currentLocation = useLocation();
// const [filteredData, setFilteredData] = useState(props.installations);
const indexedData = useMemo(() => {
return props.installations.map((item) => ({
...item,
nameLower: item.name.toLowerCase(),
locationLower: item.location.toLowerCase(),
regionLower: item.region.toLowerCase()
}));
}, [props.installations]);
const filteredData = useMemo(() => {
return indexedData.filter(
(item) =>
item.nameLower.includes(searchTerm.toLowerCase()) ||
item.locationLower.includes(searchTerm.toLowerCase()) ||
item.regionLower.includes(searchTerm.toLowerCase())
);
}, [searchTerm, indexedData]);
return (
<>
<Grid container>
<Grid
item
xs={12}
md={6}
sx={{
display:
currentLocation.pathname ===
routes.salidomo_installations + 'list' ||
currentLocation.pathname ===
routes.salidomo_installations + routes.list
? 'block'
: 'none'
}}
>
<div
style={{
display: 'flex',
flexDirection: 'column',
alignItems: 'flex-start'
}}
>
<FormControl variant="outlined">
<TextField
placeholder="Search"
value={searchTerm}
onChange={(e) => setSearchTerm(e.target.value)}
fullWidth
InputProps={{
startAdornment: (
<InputAdornment position="start">
<SearchTwoToneIcon />
</InputAdornment>
)
}}
/>
</FormControl>
</div>
</Grid>
</Grid>
{/*<FlatInstallationView installations={filteredData} />*/}
{/*<Routes>*/}
{/* {filteredData.map((installation) => {*/}
{/* return (*/}
{/* <Route*/}
{/* key={installation.s3BucketId}*/}
{/* path={routes.installation + installation.s3BucketId + '*'}*/}
{/* element={*/}
{/* <SalidomoInstallation*/}
{/* key={installation.s3BucketId}*/}
{/* current_installation={installation}*/}
{/* type="installation"*/}
{/* ></SalidomoInstallation>*/}
{/* }*/}
{/* />*/}
{/* );*/}
{/* })}*/}
{/*</Routes>*/}
</>
);
}
export default InstallationSearch;

View File

@ -0,0 +1,396 @@
import React, { ChangeEvent, useContext, useEffect, useState } from 'react';
import { Box, Card, Container, Grid, Tab, Tabs } from '@mui/material';
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
import { Link, Navigate, Route, Routes, useLocation } from 'react-router-dom';
import routes from 'src/Resources/routes.json';
import InstallationSearch from './InstallationSearch';
import { FormattedMessage } from 'react-intl';
import { UserContext } from '../../../contexts/userContext';
import { InstallationsContext } from '../../../contexts/InstallationsContextProvider';
import ListIcon from '@mui/icons-material/List';
import AccountTreeIcon from '@mui/icons-material/AccountTree';
import TreeView from '../Tree/treeView';
import { ProductIdContext } from '../../../contexts/ProductIdContextProvider';
import { UserType } from '../../../interfaces/UserTypes';
import SodioHomeInstallation from './Installation';
function SodioHomeInstallationTabs() {
const location = useLocation();
const context = useContext(UserContext);
const { currentUser } = context;
const tabList = [
'batteryview',
'information',
'manage',
'overview',
'log',
'history'
];
const [currentTab, setCurrentTab] = useState<string>(undefined);
const [fetchedInstallations, setFetchedInstallations] =
useState<boolean>(false);
const {
sodiohomeInstallations,
fetchAllSodiohomeInstallations,
currentProduct,
socket,
openSocket,
closeSocket
} = useContext(InstallationsContext);
const { product, setProduct } = useContext(ProductIdContext);
// const webSocketsContext = useContext(WebSocketContext);
// const { socket, openSocket, closeSocket } = webSocketsContext;
useEffect(() => {
let path = location.pathname.split('/');
if (path[path.length - 2] === 'list') {
setCurrentTab('list');
} else if (path[path.length - 2] === 'tree') {
setCurrentTab('tree');
} else {
//Even if we are located at path: /batteryview/mainstats, we want the BatteryView tab to be bold
setCurrentTab(path.find((pathElement) => tabList.includes(pathElement)));
}
}, [location]);
useEffect(() => {
if (sodiohomeInstallations.length === 0 && fetchedInstallations === false) {
fetchAllSodiohomeInstallations();
setFetchedInstallations(true);
}
}, [sodiohomeInstallations]);
useEffect(() => {
if (sodiohomeInstallations && sodiohomeInstallations.length > 0) {
if (!socket) {
openSocket(1);
} else if (currentProduct == 0) {
closeSocket();
openSocket(1);
}
}
}, [sodiohomeInstallations]);
useEffect(() => {
setProduct(1);
}, []);
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
setCurrentTab(value);
};
const navigateToTabPath = (pathname: string, tab_value: string): string => {
let pathlist = pathname.split('/');
let ret_path = '';
for (let i = 1; i < pathlist.length; i++) {
if (Number.isNaN(Number(pathlist[i]))) {
ret_path += '/';
ret_path += pathlist[i];
} else {
ret_path += '/';
ret_path += pathlist[i];
ret_path += '/';
break;
}
}
ret_path += tab_value;
return ret_path;
};
const singleInstallationTabs =
currentUser.userType == UserType.admin
? [
{
value: 'batteryview',
label: (
<FormattedMessage
id="batteryview"
defaultMessage="Battery View"
/>
)
},
{
value: 'overview',
label: <FormattedMessage id="overview" defaultMessage="Overview" />
},
{
value: 'log',
label: <FormattedMessage id="log" defaultMessage="Log" />
},
{
value: 'manage',
label: (
<FormattedMessage
id="manage"
defaultMessage="Access Management"
/>
)
},
{
value: 'information',
label: (
<FormattedMessage id="information" defaultMessage="Information" />
)
},
{
value: 'history',
label: (
<FormattedMessage
id="history"
defaultMessage="History Of Actions"
/>
)
}
]
: [
{
value: 'batteryview',
label: (
<FormattedMessage
id="batteryview"
defaultMessage="Battery View"
/>
)
},
{
value: 'overview',
label: <FormattedMessage id="overview" defaultMessage="Overview" />
},
{
value: 'information',
label: (
<FormattedMessage id="information" defaultMessage="Information" />
)
}
];
const tabs =
currentTab != 'list' &&
currentTab != 'tree' &&
!location.pathname.includes('folder') &&
currentUser.userType == UserType.admin
? [
{
value: 'list',
icon: <ListIcon id="mode-toggle-button-list-icon" />
},
{
value: 'tree',
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
},
{
value: 'batteryview',
label: (
<FormattedMessage
id="batteryview"
defaultMessage="Battery View"
/>
)
},
{
value: 'overview',
label: <FormattedMessage id="overview" defaultMessage="Overview" />
},
{
value: 'log',
label: <FormattedMessage id="log" defaultMessage="Log" />
},
{
value: 'manage',
label: (
<FormattedMessage
id="manage"
defaultMessage="Access Management"
/>
)
},
{
value: 'information',
label: (
<FormattedMessage id="information" defaultMessage="Information" />
)
},
{
value: 'history',
label: (
<FormattedMessage
id="history"
defaultMessage="History Of Actions"
/>
)
}
]
: currentTab != 'list' &&
currentTab != 'tree' &&
!location.pathname.includes('folder') &&
currentUser.userType == UserType.client
? [
{
value: 'list',
icon: <ListIcon id="mode-toggle-button-list-icon" />
},
{
value: 'tree',
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
},
{
value: 'batteryview',
label: (
<FormattedMessage
id="batteryview"
defaultMessage="Battery View"
/>
)
},
{
value: 'overview',
label: <FormattedMessage id="overview" defaultMessage="Overview" />
},
{
value: 'information',
label: (
<FormattedMessage id="information" defaultMessage="Information" />
)
}
]
: [
{
value: 'list',
icon: <ListIcon id="mode-toggle-button-list-icon" />
},
{
value: 'tree',
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
}
];
return sodiohomeInstallations.length > 1 ? (
<>
<Container maxWidth="xl" sx={{ marginTop: '20px' }} className="mainframe">
<TabsContainerWrapper>
<Tabs
onChange={handleTabsChange}
value={currentTab}
variant="scrollable"
scrollButtons="auto"
textColor="primary"
indicatorColor="primary"
>
{tabs.map((tab) => (
<Tab
key={tab.value}
value={tab.value}
icon={tab.icon}
component={Link}
label={tab.label}
to={
tab.value === 'list' || tab.value === 'tree'
? routes[tab.value]
: navigateToTabPath(location.pathname, routes[tab.value])
}
/>
))}
</Tabs>
</TabsContainerWrapper>
<Card variant="outlined">
<Grid
container
direction="row"
justifyContent="center"
alignItems="stretch"
spacing={0}
>
<Routes>
<Route
path={routes.list + '*'}
element={
<Grid item xs={12}>
<Box p={4}>
<InstallationSearch
installations={sodiohomeInstallations}
/>
</Box>
</Grid>
}
/>
<Route path={routes.tree + '*'} element={<TreeView />} />
<Route
path={'*'}
element={
<Navigate
to={routes.salidomo_installations + routes.list}
></Navigate>
}
></Route>
</Routes>
</Grid>
</Card>
</Container>
</>
) : sodiohomeInstallations.length === 1 ? (
<>
{' '}
<Container maxWidth="xl" sx={{ marginTop: '20px' }} className="mainframe">
<TabsContainerWrapper>
<Tabs
onChange={handleTabsChange}
value={currentTab}
variant="scrollable"
scrollButtons="auto"
textColor="primary"
indicatorColor="primary"
>
{singleInstallationTabs.map((tab) => (
<Tab
key={tab.value}
value={tab.value}
component={Link}
label={tab.label}
to={routes[tab.value]}
/>
))}
</Tabs>
</TabsContainerWrapper>
<Card variant="outlined">
<Grid
container
direction="row"
justifyContent="center"
alignItems="stretch"
spacing={0}
>
<Routes>
<Route
path={'*'}
element={
<Grid item xs={12}>
<Box p={4}>
<SodioHomeInstallation
current_installation={sodiohomeInstallations[0]}
type="installation"
></SodioHomeInstallation>
</Box>
</Grid>
}
/>
</Routes>
</Grid>
</Card>
</Container>
</>
) : null;
}
export default SodioHomeInstallationTabs;