diff --git a/csharp/App/Backend/Controller.cs b/csharp/App/Backend/Controller.cs index c4be8820c..abe4126b4 100644 --- a/csharp/App/Backend/Controller.cs +++ b/csharp/App/Backend/Controller.cs @@ -40,10 +40,14 @@ public class Controller : ControllerBase //return Unauthorized("No Password set"); throw new Exceptions(401, "Wrong Password Exception", "Please try again.", Request.Path.Value!); } + + var session = new Session(user.HidePassword().HideParentIfUserHasNoAccessToParent(user)); //TODO The Frontend should check for the MustResetPassword Flag + + return Db.Create(session) ? session @@ -472,15 +476,16 @@ public class Controller : ControllerBase [HttpGet(nameof(GetAllFoldersAndInstallations))] - public ActionResult> GetAllFoldersAndInstallations(Token authToken) + public ActionResult> GetAllFoldersAndInstallations(int productId, Token authToken) { var user = Db.GetSession(authToken)?.User; if (user is null) return Unauthorized(); + var foldersAndInstallations = user - .AccessibleFoldersAndInstallations(product:0) + .AccessibleFoldersAndInstallations(product:productId) .Do(o => o.FillOrderNumbers()) .Select(o => o.HideParentIfUserHasNoAccessToParent(user)) .OfType(); // Important! JSON serializer must see Objects otherwise diff --git a/csharp/App/Backend/DataTypes/Folder.cs b/csharp/App/Backend/DataTypes/Folder.cs index f383ef483..e953821bc 100644 --- a/csharp/App/Backend/DataTypes/Folder.cs +++ b/csharp/App/Backend/DataTypes/Folder.cs @@ -1,3 +1,3 @@ namespace InnovEnergy.App.Backend.DataTypes; -public class Folder : TreeNode {} \ No newline at end of file +public class Folder : TreeNode { } \ No newline at end of file diff --git a/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs b/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs index 2231f0413..fde540ac2 100644 --- a/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs +++ b/csharp/App/Backend/DataTypes/Methods/ExoCmd.cs @@ -143,7 +143,7 @@ public static class ExoCmd { const String url = "https://api-ch-dk-2.exoscale.com/v2/iam-role"; const String method = "iam-role"; - String rolename = installation.Product==0?Db.Installations.Count(f => f.Product == 0) + installation.InstallationName:Db.Installations.Count(f => f.Product == 1) + installation.InstallationName; + String rolename = installation.Product==0?Db.Installations.Count(f => f.Product == 0) + installation.Name:Db.Installations.Count(f => f.Product == 1) + installation.Name; var contentString = $$""" @@ -245,7 +245,7 @@ public static class ExoCmd { const String url = "https://api-ch-dk-2.exoscale.com/v2/iam-role"; const String method = "iam-role"; - String rolename = installation.Product==0?Db.Installations.Count(f => f.Product == 0) + installation.InstallationName:Db.Installations.Count(f => f.Product == 1) + installation.InstallationName; + String rolename = installation.Product==0?Db.Installations.Count(f => f.Product == 0) + installation.Name:Db.Installations.Count(f => f.Product == 1) + installation.Name; var contentString = $$""" { diff --git a/csharp/App/Backend/DataTypes/Methods/Folder.cs b/csharp/App/Backend/DataTypes/Methods/Folder.cs index 778bbab89..e06fd2c91 100644 --- a/csharp/App/Backend/DataTypes/Methods/Folder.cs +++ b/csharp/App/Backend/DataTypes/Methods/Folder.cs @@ -41,9 +41,9 @@ public static class FolderMethods public static IEnumerable UniqueChildFolders(this Folder parent) { - var set = new HashSet(Db.Folders, EqualityComparer.Default); + //var set = new HashSet(Db.Folders, EqualityComparer.Default); - return ChildFolders(parent).Where(set.Add); + return ChildFolders(parent); } public static IEnumerable ChildInstallations(this Folder parent) @@ -56,6 +56,8 @@ public static class FolderMethods public static IEnumerable DescendantFolders(this Folder parent) { + + Console.WriteLine("Parent is "+parent.Id+" looking for descendant folders"); return parent .TraverseDepthFirstPreOrder(UniqueChildFolders) .Skip(1); // skip self diff --git a/csharp/App/Backend/DataTypes/Methods/User.cs b/csharp/App/Backend/DataTypes/Methods/User.cs index 38a2b4eba..888cb11be 100644 --- a/csharp/App/Backend/DataTypes/Methods/User.cs +++ b/csharp/App/Backend/DataTypes/Methods/User.cs @@ -18,7 +18,7 @@ public static class UserMethods var fromFolders = user .AccessibleFolders() .SelectMany(u => u.ChildInstallations()).ToList().Where(f=>f.Product==product); - + return direct .Concat(fromFolders) .Distinct(); @@ -56,6 +56,7 @@ public static class UserMethods public static IEnumerable DirectlyAccessibleFolders(this User user) { + return Db .FolderAccess .Where(r => r.UserId == user.Id) diff --git a/csharp/App/Backend/DataTypes/User.cs b/csharp/App/Backend/DataTypes/User.cs index 05a87f630..78fedf21f 100644 --- a/csharp/App/Backend/DataTypes/User.cs +++ b/csharp/App/Backend/DataTypes/User.cs @@ -6,11 +6,11 @@ public class User : TreeNode { [Unique] - public String Email { get; set; } = null!; - public int UserType { get; set; } = 0; - public Boolean MustResetPassword { get; set; } = false; - public String Language { get; set; } = null!; - public String? Password { get; set; } = null!; + public String Email { get; set; } = null!; + public int UserType { get; set; } = 0; + public Boolean MustResetPassword { get; set; } = false; + public String Language { get; set; } = null!; + public String? Password { get; set; } = null!; [Unique] public override String Name { get; set; } = null!; diff --git a/csharp/App/Backend/DataTypes/WebsocketMessage.cs b/csharp/App/Backend/DataTypes/WebsocketMessage.cs new file mode 100644 index 000000000..fa8c93694 --- /dev/null +++ b/csharp/App/Backend/DataTypes/WebsocketMessage.cs @@ -0,0 +1,10 @@ +namespace InnovEnergy.App.Backend.DataTypes; + +public class WebsocketMessage +{ + + public int id { get; set; } + public int status { get; set; } + public Boolean testingMode { get; set; } + +} \ No newline at end of file diff --git a/csharp/App/Backend/Program.cs b/csharp/App/Backend/Program.cs index 4b500550f..ec927a158 100644 --- a/csharp/App/Backend/Program.cs +++ b/csharp/App/Backend/Program.cs @@ -21,7 +21,8 @@ public static class Program RabbitMqManager.InitializeEnvironment(); RabbitMqManager.StartRabbitMqConsumer(); - WebsocketManager.MonitorInstallationTable(); + WebsocketManager.MonitorSalimaxInstallationTable(); + WebsocketManager.MonitorSalidomoInstallationTable(); builder.Services.AddControllers(); diff --git a/csharp/App/Backend/Relations/Session.cs b/csharp/App/Backend/Relations/Session.cs index c86cb3c38..bb0555396 100644 --- a/csharp/App/Backend/Relations/Session.cs +++ b/csharp/App/Backend/Relations/Session.cs @@ -1,5 +1,6 @@ using InnovEnergy.App.Backend.Database; using InnovEnergy.App.Backend.DataTypes; +using InnovEnergy.App.Backend.DataTypes.Methods; using SQLite; namespace InnovEnergy.App.Backend.Relations; @@ -11,7 +12,8 @@ public class Session : Relation [Unique ] public String Token { get => Left ; init => Left = value;} [Indexed] public Int64 UserId { get => Right; init => Right = value;} [Indexed] public DateTime LastSeen { get; set; } - + public Boolean AccessToSalimax { get; set; } = false; + public Boolean AccessToSalidomo { get; set; } = false; [Ignore] public Boolean Valid => DateTime.Now - LastSeen < MaxAge && (User) is not null; @@ -30,6 +32,9 @@ public class Session : Relation Token = CreateToken(); UserId = user.Id; LastSeen = DateTime.Now; + AccessToSalimax = user.AccessibleInstallations(product: 0).ToList().Count > 0; + AccessToSalidomo = user.AccessibleInstallations(product: 1).ToList().Count > 0; + } private static String CreateToken() diff --git a/csharp/App/Backend/Websockets/InstallationInfo.cs b/csharp/App/Backend/Websockets/InstallationInfo.cs index 0367dd8ae..df816607b 100644 --- a/csharp/App/Backend/Websockets/InstallationInfo.cs +++ b/csharp/App/Backend/Websockets/InstallationInfo.cs @@ -3,7 +3,8 @@ namespace InnovEnergy.App.Backend.Websockets; public class InstallationInfo { - public int Status { get; set; } - public DateTime Timestamp { get; set; } + public int Status { get; set; } + public DateTime Timestamp { get; set; } + public int Product { get; set; } public List Connections { get; } = new List(); } \ No newline at end of file diff --git a/csharp/App/Backend/Websockets/RabbitMQManager.cs b/csharp/App/Backend/Websockets/RabbitMQManager.cs index e25b5e7fe..274cc0b64 100644 --- a/csharp/App/Backend/Websockets/RabbitMQManager.cs +++ b/csharp/App/Backend/Websockets/RabbitMQManager.cs @@ -158,7 +158,8 @@ public static class RabbitMqManager WebsocketManager.InstallationConnections[installationId] = new InstallationInfo { Status = receivedStatusMessage.Status, - Timestamp = DateTime.Now + Timestamp = DateTime.Now, + Product = installation.Product }; } else diff --git a/csharp/App/Backend/Websockets/WebsockerManager.cs b/csharp/App/Backend/Websockets/WebsockerManager.cs index 89f6b8991..da63c6463 100644 --- a/csharp/App/Backend/Websockets/WebsockerManager.cs +++ b/csharp/App/Backend/Websockets/WebsockerManager.cs @@ -4,6 +4,7 @@ using System.Net.WebSockets; using System.Text; using System.Text.Json; using InnovEnergy.App.Backend.Database; +using InnovEnergy.App.Backend.DataTypes; namespace InnovEnergy.App.Backend.Websockets; @@ -13,12 +14,12 @@ public static class WebsocketManager //Every 2 minutes, check the timestamp of the latest received message for every installation. //If the difference between the two timestamps is more than two minutes, we consider this installation unavailable. - public static async Task MonitorInstallationTable() + public static async Task MonitorSalimaxInstallationTable() { while (true){ lock (InstallationConnections){ foreach (var installationConnection in InstallationConnections){ - if ((DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(1)){ + if (installationConnection.Value.Product==0 && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(1)){ installationConnection.Value.Status = -1; if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);} } @@ -27,6 +28,21 @@ public static class WebsocketManager await Task.Delay(TimeSpan.FromMinutes(2)); } } + + public static async Task MonitorSalidomoInstallationTable() + { + while (true){ + lock (InstallationConnections){ + foreach (var installationConnection in InstallationConnections){ + if (installationConnection.Value.Product==1 && (DateTime.Now - installationConnection.Value.Timestamp) > TimeSpan.FromMinutes(20)){ + installationConnection.Value.Status = -1; + if (installationConnection.Value.Connections.Count > 0){InformWebsocketsForInstallation(installationConnection.Key);} + } + } + } + await Task.Delay(TimeSpan.FromMinutes(30)); + } + } //Inform all the connected websockets regarding installation "installationId" public static void InformWebsocketsForInstallation(Int64 installationId) @@ -35,7 +51,6 @@ public static class WebsocketManager var installationConnection = InstallationConnections[installationId]; Console.WriteLine("Update all the connected websockets for installation " + installationId); - var jsonObject = new { id = installationId, @@ -96,6 +111,9 @@ public static class WebsocketManager //Console.WriteLine("Received a new message from websocket"); lock (InstallationConnections) { + + List dataToSend = new List(); + //Each front-end will send the list of the installations it wants to access //If this is a new key (installation id), initialize the list for this key and then add the websocket object for this client //Then, report the status of each requested installation to the front-end that created the websocket connection @@ -108,29 +126,42 @@ public static class WebsocketManager //Console.WriteLine("Create new empty list for installation id " + installationId); InstallationConnections[installationId] = new InstallationInfo { - Status = -1 + Status = -1, + Product = installation.Product }; } InstallationConnections[installationId].Connections.Add(currentWebSocket); - var jsonObject = new + var jsonObject = new WebsocketMessage { id = installationId, status = InstallationConnections[installationId].Status, testingMode = installation.TestingMode }; + + dataToSend.Add(jsonObject); - var jsonString = JsonSerializer.Serialize(jsonObject); - var dataToSend = Encoding.UTF8.GetBytes(jsonString); + //var jsonString = JsonSerializer.Serialize(jsonObject); + //var dataToSend = Encoding.UTF8.GetBytes(jsonString); - currentWebSocket.SendAsync(dataToSend, - WebSocketMessageType.Text, - true, // Indicates that this is the end of the message - CancellationToken.None - ); + // currentWebSocket.SendAsync(dataToSend, + // WebSocketMessageType.Text, + // true, // Indicates that this is the end of the message + // CancellationToken.None + // ); } + var jsonString = JsonSerializer.Serialize(dataToSend); + var encodedDataToSend = Encoding.UTF8.GetBytes(jsonString); + + + currentWebSocket.SendAsync(encodedDataToSend, + WebSocketMessageType.Text, + true, // Indicates that this is the end of the message + CancellationToken.None + ); + Console.WriteLine("Printing installation connection list"); Console.WriteLine("----------------------------------------------"); diff --git a/csharp/App/SaliMax/HostList.txt b/csharp/App/SaliMax/HostList.txt index 3d0e08138..9a03162c9 100755 --- a/csharp/App/SaliMax/HostList.txt +++ b/csharp/App/SaliMax/HostList.txt @@ -10,4 +10,5 @@ Salimax0006 ie-entwicklung@10.2.4.35 Steakhouse Mettmenstetten Salimax0007 ie-entwicklung@10.2.4.154 LerchenhofHerr Twannberg Salimax0008 ie-entwicklung@10.2.4.113 Wittmann Kottingbrunn Salimax0010 ie-entwicklung@10.2.4.211 Mohatech 1 (Beat Moser) +Salimax0011 ie-entwicklung@10.2.4.239 Thomas Tschirren (Enggistein) SalidomoServer ig@134.209.238.170 \ No newline at end of file diff --git a/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/config.py b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/config.py index 9f488eccd..8f593e498 100755 --- a/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/config.py +++ b/firmware/Venus_Release/VenusReleaseFiles/dbus-fzsonick-48tl/config.py @@ -54,6 +54,6 @@ INNOVENERGY_PROTOCOL_VERSION = '48TL200V3' # S3 Credentials -S3BUCKET = "114-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e" -S3KEY = "EXO5b9198dc11544f42b44e1180" -S3SECRET = "ga-mD3SYZMJUfjksmXPKAHQhVkxPZYv57jC1oD_mkC0" +S3BUCKET = "91-c0436b6a-d276-4cd8-9c44-1eae86cf5d0e" +S3KEY = "EXOe6dce12288f11a676c2025a1" +S3SECRET = "xpqM4Eh0Gg1HaYVkzlR9X6PwYa-QNb-mVk0XUkwW3cc" diff --git a/typescript/frontend-marios2/src/components/login.tsx b/typescript/frontend-marios2/src/components/login.tsx index c0148d42a..4e58eafdb 100644 --- a/typescript/frontend-marios2/src/components/login.tsx +++ b/typescript/frontend-marios2/src/components/login.tsx @@ -24,6 +24,7 @@ import { useNavigate } from 'react-router-dom'; import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank'; import CheckBoxIcon from '@mui/icons-material/CheckBox'; import routes from 'src/Resources/routes.json'; +import { ProductIdContext } from '../contexts/ProductIdContextProvider'; function Login() { const [username, setUsername] = useState(''); @@ -35,6 +36,8 @@ function Login() { const theme = useTheme(); const context = useContext(UserContext); + const { setAccessToSalimax, setAccessToSalidomo } = + useContext(ProductIdContext); const navigate = useNavigate(); if (!context) { @@ -73,11 +76,18 @@ function Login() { setNewToken(response.data.token); setUser(response.data.user); + setAccessToSalimax(response.data.accessToSalimax); + setAccessToSalidomo(response.data.accessToSalidomo); + if (rememberMe) { cookies.set('rememberedUsername', username, { path: '/' }); cookies.set('rememberedPassword', password, { path: '/' }); } - navigate(routes.installations); + if (response.data.accessToSalimax) { + navigate(routes.installations); + } else { + navigate(routes.salidomo_installations); + } } }) .catch((error) => { diff --git a/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryView.tsx b/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryView.tsx index 65e09e4bf..f176b1638 100644 --- a/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryView.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/BatteryView/DetailedBatteryView.tsx @@ -582,6 +582,7 @@ function DetailedBatteryView(props: DetailedBatteryViewProps) { Select Firmware Version + AF09 AF11 AF0A diff --git a/typescript/frontend-marios2/src/content/dashboards/Information/InformationSalidomo.tsx b/typescript/frontend-marios2/src/content/dashboards/Information/InformationSalidomo.tsx index f169e5947..3aa6c093e 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Information/InformationSalidomo.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Information/InformationSalidomo.tsx @@ -200,7 +200,7 @@ function InformationSalidomo(props: InformationSalidomoProps) { /> } name="installationName" - value={formValues.installationName} + value={formValues.name} onChange={handleChange} variant="outlined" fullWidth diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx index 69434daf7..0ad3e1b3b 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/Installation.tsx @@ -379,6 +379,7 @@ function Installation(props: singleInstallationProps) { {loading && currentTab != 'information' && currentTab != 'history' && + currentTab != 'manage' && currentTab != 'overview' && currentTab != 'log' && ( >> => { - const s3Path = `${timestamp.ticks}.csv`; + const s3Path = cutdigits + ? `${timestamp.ticks.toString().slice(0, -2)}.csv` + : `${timestamp.ticks}.csv`; if (s3Credentials && s3Credentials.s3Bucket) { const s3Access = new S3Access( s3Credentials.s3Bucket, @@ -71,10 +74,14 @@ export const fetchData = ( if (r.status === 404) { return Promise.resolve(FetchResult.notAvailable); } else if (r.status === 200) { + console.log('FOUND ITTTTTTTTTTTT'); const csvtext = await r.text(); // Assuming the server returns the Base64 encoded ZIP file as text const contentEncoding = r.headers.get('content-type'); + console.log(contentEncoding); + if (contentEncoding != 'application/base64; charset=utf-8') { + console.log('uncompressed'); return parseChunk(csvtext); } @@ -87,6 +94,8 @@ export const fetchData = ( // Assuming the CSV file is named "data.csv" inside the ZIP archive const csvContent = await zip.file('data.csv').async('text'); + console.log(csvContent); + return parseChunk(csvContent); } else { return Promise.resolve(FetchResult.notAvailable); diff --git a/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx b/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx index bd10ab9d4..34b776af4 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Installations/index.tsx @@ -14,6 +14,7 @@ import { InstallationsContext } from '../../../contexts/InstallationsContextProv import Installation from './Installation'; import { WebSocketContext } from '../../../contexts/WebSocketContextProvider'; import { UserType } from '../../../interfaces/UserTypes'; +import { ProductIdContext } from '../../../contexts/ProductIdContextProvider'; function InstallationTabs() { const location = useLocation(); @@ -35,6 +36,8 @@ function InstallationTabs() { const { salimaxInstallations, fetchAllInstallations } = useContext(InstallationsContext); + const { product, setProduct } = useContext(ProductIdContext); + const webSocketsContext = useContext(WebSocketContext); const { socket, openSocket, closeSocket } = webSocketsContext; @@ -67,6 +70,10 @@ function InstallationTabs() { } }, [salimaxInstallations]); + useEffect(() => { + setProduct(0); + }, []); + const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => { setCurrentTab(value); }; diff --git a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx index 56ef8a740..8857be8d2 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Overview/overview.tsx @@ -1291,6 +1291,140 @@ function Overview(props: OverviewProps) { + + + + + + + + + + + + + + + + {dailyData && ( + { + startZoom(); + handleBeforeZoom(chartContext, options); + } + } + } + }} + series={[ + { + ...dailyDataArray[chartState].chartData.ACLoad, + color: '#ff9900' + } + ]} + type="line" + height={400} + /> + )} + + + + + + + + + + + + + + + {dailyData && ( + { + startZoom(); + handleBeforeZoom(chartContext, options); + } + } + } + }} + series={[ + { + ...dailyDataArray[chartState].chartData.DCLoad, + color: '#ff3333' + } + ]} + type="line" + height={400} + /> + )} + + + )} diff --git a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/FlatInstallationView.tsx b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/FlatInstallationView.tsx index 7f522a466..1f3bc5a20 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/FlatInstallationView.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/FlatInstallationView.tsx @@ -157,7 +157,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => { noWrap sx={{ marginTop: '10px', fontSize: 'small' }} > - {installation.installationName} + {installation.name} diff --git a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx index 86f13aa35..1be19d424 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/Installation.tsx @@ -27,13 +27,15 @@ import SalidomoOverview from '../Overview/salidomoOverview'; 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'; interface singleInstallationProps { current_installation?: I_Installation; type?: string; } -function Installation(props: singleInstallationProps) { +function SalidomoInstallation(props: singleInstallationProps) { const context = useContext(UserContext); const { currentUser } = context; const location = useLocation().pathname; @@ -77,18 +79,19 @@ function Installation(props: singleInstallationProps) { const continueFetching = useRef(false); const fetchDataPeriodically = async () => { - var timeperiodToSearch = 90; + var timeperiodToSearch = 30; let res; let timestampToFetch; - for (var i = 0; i < timeperiodToSearch; i += 2) { + for (var i = 0; i < timeperiodToSearch; i += 1) { if (!continueFetching.current) { return false; } - timestampToFetch = UnixTime.now().earlier(TimeSpan.fromSeconds(i)); + timestampToFetch = UnixTime.now().earlier(TimeSpan.fromMinutes(i)); + console.log('timestamp to fetch is ' + timestampToFetch); try { - res = await fetchData(timestampToFetch, s3Credentials); + res = await fetchData(timestampToFetch, s3Credentials, true); if (res !== FetchResult.notAvailable && res !== FetchResult.tryLater) { break; } @@ -127,7 +130,7 @@ function Installation(props: singleInstallationProps) { await timeout(2000); } - timestampToFetch = timestampToFetch.later(TimeSpan.fromSeconds(60)); + timestampToFetch = timestampToFetch.later(TimeSpan.fromMinutes(20)); console.log('NEW TIMESTAMP TO FETCH IS ' + timestampToFetch); for (i = 0; i < 10; i++) { @@ -137,7 +140,7 @@ function Installation(props: singleInstallationProps) { try { console.log('Trying to fetch timestamp ' + timestampToFetch); - res = await fetchData(timestampToFetch, s3Credentials); + res = await fetchData(timestampToFetch, s3Credentials, true); if ( res !== FetchResult.notAvailable && res !== FetchResult.tryLater @@ -148,7 +151,7 @@ function Installation(props: singleInstallationProps) { console.error('Error fetching data:', err); return false; } - timestampToFetch = timestampToFetch.later(TimeSpan.fromSeconds(1)); + timestampToFetch = timestampToFetch.later(TimeSpan.fromMinutes(1)); } } }; @@ -213,7 +216,7 @@ function Installation(props: singleInstallationProps) { fontSize: '14px' }} > - {props.current_installation.installationName} + {props.current_installation.name}
@@ -296,6 +299,7 @@ function Installation(props: singleInstallationProps) { {loading && currentTab != 'information' && currentTab != 'overview' && + currentTab != 'manage' && currentTab != 'history' && currentTab != 'log' && ( )} + {currentUser.userType == UserType.admin && ( + + + + } + /> + )} + } + element={} /> @@ -396,4 +414,4 @@ function Installation(props: singleInstallationProps) { ); } -export default Installation; +export default SalidomoInstallation; diff --git a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/InstallationSearch.tsx b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/InstallationSearch.tsx index a8fddc0d3..53d29140c 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/InstallationSearch.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/InstallationSearch.tsx @@ -5,10 +5,7 @@ import FlatInstallationView from './FlatInstallationView'; import { I_Installation } from '../../../interfaces/InstallationTypes'; import { Route, Routes, useLocation } from 'react-router-dom'; import routes from '../../../Resources/routes.json'; -import Installation from './Installation'; -import { FormattedMessage } from 'react-intl'; -import Button from '@mui/material/Button'; -import SalidomonstallationForm from './SalidomoInstallationForm'; +import SalidomoInstallation from './Installation'; interface installationSearchProps { installations: I_Installation[]; @@ -18,26 +15,11 @@ function InstallationSearch(props: installationSearchProps) { const [searchTerm, setSearchTerm] = useState(''); const currentLocation = useLocation(); const [filteredData, setFilteredData] = useState(props.installations); - const [openModalInstallation, setOpenModalInstallation] = useState(false); - - const handleNewInstallationInsertion = () => { - setOpenModalInstallation(true); - }; - - const handleInstallationFormSubmit = () => { - setOpenModalInstallation(false); - }; - - const handleFormCancel = () => { - setOpenModalInstallation(false); - }; useEffect(() => { const filtered = props.installations.filter( (item) => - item.installationName - .toLowerCase() - .includes(searchTerm.toLowerCase()) || + item.name.toLowerCase().includes(searchTerm.toLowerCase()) || item.location.toLowerCase().includes(searchTerm.toLowerCase()) || item.region.toLowerCase().includes(searchTerm.toLowerCase()) ); @@ -46,12 +28,6 @@ function InstallationSearch(props: installationSearchProps) { return ( <> - {openModalInstallation && ( - - )} - - + > } /> ); diff --git a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/SalidomoInstallationForm.tsx b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/SalidomoInstallationForm.tsx index 66025c284..4a253135f 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/SalidomoInstallationForm.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/SalidomoInstallationForm.tsx @@ -21,26 +21,21 @@ import { FormattedMessage } from 'react-intl'; interface SalidomoInstallationFormProps { cancel: () => void; submit: () => void; + parentid: number; } function SalidomonstallationForm(props: SalidomoInstallationFormProps) { const theme = useTheme(); const [open, setOpen] = useState(true); const [formValues, setFormValues] = useState>({ - installationName: '', + name: '', region: '', location: '', country: '', vpnIp: '', vrmLink: '' }); - const requiredFields = [ - 'installationName', - 'location', - 'country', - 'vpnIp', - 'vrmLink' - ]; + const requiredFields = ['name', 'location', 'country', 'vpnIp', 'vrmLink']; const DeviceTypes = ['Cerbo', 'Venus']; const installationContext = useContext(InstallationsContext); @@ -57,7 +52,7 @@ function SalidomonstallationForm(props: SalidomoInstallationFormProps) { }; const handleSubmit = async (e) => { setLoading(true); - formValues.parentId = 1; + formValues.parentId = props.parentid; formValues.product = 1; const responseData = await createInstallation(formValues); props.submit(); @@ -120,11 +115,11 @@ function SalidomonstallationForm(props: SalidomoInstallationFormProps) { defaultMessage="Installation Name" /> } - name="installationName" - value={formValues.installationName} + name="name" + value={formValues.name} onChange={handleChange} required - error={formValues.installationName === ''} + error={formValues.name === ''} />
diff --git a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx index 2e56a6ff6..46d337b38 100644 --- a/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/SalidomoInstallations/index.tsx @@ -9,12 +9,24 @@ import { UserContext } from '../../../contexts/userContext'; import { InstallationsContext } from '../../../contexts/InstallationsContextProvider'; import { WebSocketContext } from '../../../contexts/WebSocketContextProvider'; 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 SalidomoInstallation from './Installation'; function SalidomoInstallationTabs() { const location = useLocation(); const context = useContext(UserContext); const { currentUser } = context; - const tabList = ['batteryview', 'information', 'overview', 'log', 'history']; + const tabList = [ + 'batteryview', + 'information', + 'manage', + 'overview', + 'log', + 'history' + ]; const [currentTab, setCurrentTab] = useState(undefined); const [fetchedInstallations, setFetchedInstallations] = @@ -22,6 +34,8 @@ function SalidomoInstallationTabs() { const { salidomoInstallations, fetchAllSalidomoInstallations } = useContext(InstallationsContext); + const { product, setProduct } = useContext(ProductIdContext); + const webSocketsContext = useContext(WebSocketContext); const { socket, openSocket, closeSocket } = webSocketsContext; @@ -30,6 +44,8 @@ function SalidomoInstallationTabs() { 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))); @@ -52,6 +68,10 @@ function SalidomoInstallationTabs() { } }, [salidomoInstallations]); + useEffect(() => { + setProduct(1); + }, []); + const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => { setCurrentTab(value); }; @@ -75,14 +95,9 @@ function SalidomoInstallationTabs() { return ret_path; }; - const tabs = - currentTab != 'list' && !location.pathname.includes('folder') + const singleInstallationTabs = + currentUser.userType == UserType.admin ? [ - { - value: 'list', - icon: - }, - { value: 'batteryview', label: ( @@ -101,6 +116,16 @@ function SalidomoInstallationTabs() { label: }, + { + value: 'manage', + label: ( + + ) + }, + { value: 'information', label: ( @@ -119,71 +144,247 @@ function SalidomoInstallationTabs() { ] : [ { - value: 'list', - icon: + value: 'batteryview', + label: ( + + ) + }, + { + value: 'overview', + label: + }, + + { + value: 'information', + label: ( + + ) } ]; - return ( - - - - {tabs.map((tab) => ( - - ))} - - - - - - - - - - - } - /> + const tabs = + currentTab != 'list' && + currentTab != 'tree' && + !location.pathname.includes('folder') && + currentUser.userType == UserType.admin + ? [ + { + value: 'list', + icon: + }, + { + value: 'tree', + icon: + }, + { + value: 'batteryview', + label: ( + + ) + }, + { + value: 'overview', + label: + }, + { + value: 'log', + label: + }, - - } - > - - - - - ); + { + value: 'manage', + label: ( + + ) + }, + + { + value: 'information', + label: ( + + ) + }, + { + value: 'history', + label: ( + + ) + } + ] + : currentTab != 'list' && + currentTab != 'tree' && + !location.pathname.includes('folder') && + currentUser.userType == UserType.client + ? [ + { + value: 'list', + icon: + }, + { + value: 'tree', + icon: + }, + { + value: 'batteryview', + label: ( + + ) + }, + { + value: 'overview', + label: + }, + + { + value: 'information', + label: ( + + ) + } + ] + : [ + { + value: 'list', + icon: + }, + { + value: 'tree', + icon: + } + ]; + + return salidomoInstallations.length > 1 ? ( + <> + + + + {tabs.map((tab) => ( + + ))} + + + + + + + + + + + } + /> + + } /> + + + } + > + + + + + + ) : salidomoInstallations.length === 1 ? ( + <> + {' '} + + + + {singleInstallationTabs.map((tab) => ( + + ))} + + + + + + + + + + + } + /> + + + + + + ) : null; } export default SalidomoInstallationTabs; diff --git a/typescript/frontend-marios2/src/content/dashboards/Tree/CustomTreeItem.tsx b/typescript/frontend-marios2/src/content/dashboards/Tree/CustomTreeItem.tsx index 7183e8f4d..9e2bef622 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Tree/CustomTreeItem.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Tree/CustomTreeItem.tsx @@ -10,6 +10,7 @@ import CancelIcon from '@mui/icons-material/Cancel'; import { WebSocketContext } from 'src/contexts/WebSocketContextProvider'; import routes from 'src/Resources/routes.json'; import { useLocation, useNavigate } from 'react-router-dom'; +import { ProductIdContext } from '../../../contexts/ProductIdContextProvider'; interface CustomTreeItemProps { node: I_Installation | I_Folder; @@ -44,12 +45,15 @@ function CustomTreeItem(props: CustomTreeItemProps) { const navigate = useNavigate(); const [selected, setSelected] = useState(false); const currentLocation = useLocation(); + const { product, setProduct } = useContext(ProductIdContext); const handleSelectOneInstallation = (): void => { let installation = props.node; + let path = + product == 0 ? routes.installations : routes.salidomo_installations; if (installation.type != 'Folder') { navigate( - routes.installations + + path + routes.tree + routes.installation + installation.s3BucketId + @@ -62,7 +66,7 @@ function CustomTreeItem(props: CustomTreeItemProps) { setSelected(!selected); } else { navigate( - routes.installations + + path + routes.tree + routes.folder + installation.id + @@ -156,7 +160,8 @@ function CustomTreeItem(props: CustomTreeItemProps) { } sx={{ display: - currentLocation.pathname === routes.installations + 'tree' || + currentLocation.pathname === + routes.salidomo_installations + routes.tree || currentLocation.pathname === routes.installations + routes.tree || currentLocation.pathname.includes('folder') ? 'block' diff --git a/typescript/frontend-marios2/src/content/dashboards/Tree/Information.tsx b/typescript/frontend-marios2/src/content/dashboards/Tree/Information.tsx index 3293d102c..4855a049f 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Tree/Information.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Tree/Information.tsx @@ -21,6 +21,8 @@ import FolderForm from './folderForm'; import InstallationForm from '../Installations/installationForm'; import { InstallationsContext } from '../../../contexts/InstallationsContextProvider'; import { UserType } from '../../../interfaces/UserTypes'; +import SalidomoInstallationForm from '../SalidomoInstallations/SalidomoInstallationForm'; +import { ProductIdContext } from '../../../contexts/ProductIdContextProvider'; interface TreeInformationProps { folder: I_Folder; @@ -50,6 +52,8 @@ function TreeInformation(props: TreeInformationProps) { deleteFolder } = installationContext; + const { product, setProduct } = useContext(ProductIdContext); + const handleChange = (e) => { const { name, value } = e.target; setFormValues({ @@ -61,7 +65,7 @@ function TreeInformation(props: TreeInformationProps) { const handleFolderInformationUpdate = () => { setLoading(true); setError(false); - updateFolder(formValues); + updateFolder(formValues, product); }; const handleNewInstallationInsertion = () => { @@ -80,7 +84,7 @@ function TreeInformation(props: TreeInformationProps) { const deleteFolderModalHandle = () => { setOpenModalDeleteFolder(false); - deleteFolder(formValues); + deleteFolder(formValues, product); setLoading(false); }; @@ -195,13 +199,20 @@ function TreeInformation(props: TreeInformationProps) { parentid={props.folder.id} /> )} - {openModalInstallation && ( + {openModalInstallation && product == 0 && ( )} + {openModalInstallation && product == 1 && ( + + )} { // Compare the status field of each installation and sort them based on the status. //Installations with alarms go first + + if (a.type == 'Folder') { + return -1; + } let a_status = getStatus(a.id); let b_status = getStatus(b.id); @@ -34,7 +42,7 @@ function InstallationTree() { }); useEffect(() => { - fetchAllFoldersAndInstallations(); + fetchAllFoldersAndInstallations(product); }, []); const TreeNode = ({ node, parent_id }) => { @@ -61,17 +69,59 @@ function InstallationTree() { ) ); - } else { + } else if (node.type == 'Installation') { return ( node.parentId == parent_id && ( ) ); + } else { + return null; } }; return ( + + {foldersAndInstallations.map((installation) => { + if (installation.type == 'Installation') { + return ( + + ) : ( + + ) + } + /> + ); + } else { + return ( + + } + /> + ); + } + })} + } @@ -93,39 +143,6 @@ function InstallationTree() { })} - - - {foldersAndInstallations.map((installation) => { - if (installation.type == 'Installation') { - return ( - - } - /> - ); - } else { - return ( - - } - /> - ); - } - })} - ); } diff --git a/typescript/frontend-marios2/src/content/dashboards/Tree/folderForm.tsx b/typescript/frontend-marios2/src/content/dashboards/Tree/folderForm.tsx index 88f9aaa2f..716aaab5d 100644 --- a/typescript/frontend-marios2/src/content/dashboards/Tree/folderForm.tsx +++ b/typescript/frontend-marios2/src/content/dashboards/Tree/folderForm.tsx @@ -14,6 +14,7 @@ import { I_Folder } from 'src/interfaces/InstallationTypes'; import { TokenContext } from 'src/contexts/tokenContext'; import { InstallationsContext } from 'src/contexts/InstallationsContextProvider'; import { FormattedMessage } from 'react-intl'; +import { ProductIdContext } from '../../../contexts/ProductIdContextProvider'; interface folderFormProps { cancel: () => void; @@ -29,7 +30,7 @@ function folderForm(props: folderFormProps) { information: '' }); const requiredFields = ['name']; - + const { product, setProduct } = useContext(ProductIdContext); const tokencontext = useContext(TokenContext); const { removeToken } = tokencontext; @@ -47,7 +48,7 @@ function folderForm(props: folderFormProps) { const handleSubmit = async (e) => { formValues.parentId = props.parentid; setLoading(true); - const responseData = await createFolder(formValues); + const responseData = await createFolder(formValues, product); props.submit(); }; diff --git a/typescript/frontend-marios2/src/contexts/InstallationsContextProvider.tsx b/typescript/frontend-marios2/src/contexts/InstallationsContextProvider.tsx index 615ed650b..3d56b54c8 100644 --- a/typescript/frontend-marios2/src/contexts/InstallationsContextProvider.tsx +++ b/typescript/frontend-marios2/src/contexts/InstallationsContextProvider.tsx @@ -18,7 +18,7 @@ interface I_InstallationContextProviderProps { foldersAndInstallations: I_Installation[]; fetchAllInstallations: () => Promise; fetchAllSalidomoInstallations: () => Promise; - fetchAllFoldersAndInstallations: () => Promise; + fetchAllFoldersAndInstallations: (product: number) => Promise; createInstallation: (value: Partial) => Promise; updateInstallation: (value: I_Installation, view: string) => Promise; loading: boolean; @@ -28,9 +28,9 @@ interface I_InstallationContextProviderProps { updated: boolean; setUpdated: (value: boolean) => void; deleteInstallation: (value: I_Installation, view: string) => Promise; - createFolder: (value: Partial) => Promise; - updateFolder: (value: I_Folder) => Promise; - deleteFolder: (value: I_Folder) => Promise; + createFolder: (value: Partial, product: number) => Promise; + updateFolder: (value: I_Folder, product: number) => Promise; + deleteFolder: (value: I_Folder, product: number) => Promise; } export const InstallationsContext = @@ -40,7 +40,7 @@ export const InstallationsContext = foldersAndInstallations: [], fetchAllInstallations: () => Promise.resolve(), fetchAllSalidomoInstallations: () => Promise.resolve(), - fetchAllFoldersAndInstallations: () => Promise.resolve(), + fetchAllFoldersAndInstallations: (product: number) => Promise.resolve(), createInstallation: () => Promise.resolve(), updateInstallation: () => Promise.resolve(), loading: false, @@ -104,19 +104,22 @@ const InstallationsContextProvider = ({ }); }, []); - const fetchAllFoldersAndInstallations = useCallback(async () => { - return axiosConfig - .get('/GetAllFoldersAndInstallations') - .then((res) => { - setFoldersAndInstallations(res.data); - }) - .catch((err) => { - if (err.response && err.response.status == 401) { - removeToken(); - navigate(routes.login); - } - }); - }, []); + const fetchAllFoldersAndInstallations = useCallback( + async (product: number) => { + return axiosConfig + .get(`/GetAllFoldersAndInstallations?productId=${product}`) + .then((res) => { + setFoldersAndInstallations(res.data); + }) + .catch((err) => { + if (err.response && err.response.status == 401) { + removeToken(); + navigate(routes.login); + } + }); + }, + [] + ); const createInstallation = useCallback( async (formValues: Partial) => { @@ -124,11 +127,12 @@ const InstallationsContextProvider = ({ .post('/CreateInstallation', formValues) .then((res) => { setLoading(false); - if (formValues.product == 0) { - fetchAllFoldersAndInstallations(); - } else { - fetchAllSalidomoInstallations(); - } + fetchAllFoldersAndInstallations(formValues.product); + // if (formValues.product == 0) { + // fetchAllFoldersAndInstallations(); + // } else { + // fetchAllSalidomoInstallations(); + // } }) .catch((error) => { @@ -151,14 +155,12 @@ const InstallationsContextProvider = ({ if (response) { setLoading(false); setUpdated(true); - if (formValues.product == 0) { - if (view == 'installation') { - fetchAllInstallations(); - } else { - fetchAllFoldersAndInstallations(); - } - } else { + if (formValues.product == 0 && view == 'installation') { + fetchAllInstallations(); + } else if (formValues.product == 1 && view == 'installation') { fetchAllSalidomoInstallations(); + } else { + fetchAllFoldersAndInstallations(formValues.product); } setTimeout(() => { @@ -186,14 +188,12 @@ const InstallationsContextProvider = ({ if (response) { setLoading(false); setUpdated(true); - if (formValues.product == 0) { - if (view == 'installation') { - fetchAllInstallations(); - } else { - fetchAllFoldersAndInstallations(); - } - } else { + if (formValues.product == 0 && view == 'installation') { + fetchAllInstallations(); + } else if (formValues.product == 1 && view == 'installation') { fetchAllSalidomoInstallations(); + } else { + fetchAllFoldersAndInstallations(formValues.product); } setTimeout(() => { @@ -213,71 +213,80 @@ const InstallationsContextProvider = ({ [] ); - const createFolder = useCallback(async (formValues: Partial) => { - axiosConfig - .post('/CreateFolder', formValues) - .then((res) => { - setLoading(false); - fetchAllFoldersAndInstallations(); - }) - - .catch((error) => { - setLoading(false); - setError(true); - if (error.response && error.response.status == 401) { - removeToken(); - navigate(routes.login); - } - }); - }, []); - - const updateFolder = useCallback(async (formValues: I_Folder) => { - axiosConfig - .put('/UpdateFolder', formValues) - .then((response) => { - if (response) { + const createFolder = useCallback( + async (formValues: Partial, product: number) => { + axiosConfig + .post('/CreateFolder', formValues) + .then((res) => { setLoading(false); - setUpdated(true); - fetchAllFoldersAndInstallations(); + fetchAllFoldersAndInstallations(product); + }) - setTimeout(() => { - setUpdated(false); - }, 3000); - } - }) - .catch((error) => { - setLoading(false); - setError(true); - if (error.response && error.response.status == 401) { - removeToken(); - navigate(routes.login); - } - }); - }, []); - - const deleteFolder = useCallback(async (formValues: I_Folder) => { - axiosConfig - .delete(`/DeleteFolder?folderId=${formValues.id}`) - .then((response) => { - if (response) { + .catch((error) => { setLoading(false); - setUpdated(true); - fetchAllFoldersAndInstallations(); + setError(true); + if (error.response && error.response.status == 401) { + removeToken(); + navigate(routes.login); + } + }); + }, + [] + ); - setTimeout(() => { - setUpdated(false); - }, 3000); - } - }) - .catch((error) => { - setLoading(false); - setError(true); - if (error.response && error.response.status == 401) { - removeToken(); - navigate(routes.login); - } - }); - }, []); + const updateFolder = useCallback( + async (formValues: I_Folder, product: number) => { + axiosConfig + .put('/UpdateFolder', formValues) + .then((response) => { + if (response) { + setLoading(false); + setUpdated(true); + fetchAllFoldersAndInstallations(product); + + setTimeout(() => { + setUpdated(false); + }, 3000); + } + }) + .catch((error) => { + setLoading(false); + setError(true); + if (error.response && error.response.status == 401) { + removeToken(); + navigate(routes.login); + } + }); + }, + [] + ); + + const deleteFolder = useCallback( + async (formValues: I_Folder, product: number) => { + axiosConfig + .delete(`/DeleteFolder?folderId=${formValues.id}`) + .then((response) => { + if (response) { + setLoading(false); + setUpdated(true); + fetchAllFoldersAndInstallations(product); + + setTimeout(() => { + setUpdated(false); + }, 3000); + } + }) + .catch((error) => { + setLoading(false); + setError(true); + if (error.response && error.response.status == 401) { + removeToken(); + navigate(routes.login); + } + }); + }, + [] + ); return ( void; + accessToSalimax: boolean; + accessToSalidomo: boolean; + setAccessToSalimax: (access: boolean) => void; + setAccessToSalidomo: (access: boolean) => void; +} + +// Create the context. +export const ProductIdContext = createContext( + undefined +); + +// Create a UserContextProvider component +export const ProductIdContextProvider = ({ + children +}: { + children: ReactNode; +}) => { + const location = useLocation().pathname; + const [accessToSalimax, setAccessToSalimax] = useState(() => { + const storedValue = localStorage.getItem('accessToSalimax'); + return storedValue === 'true'; + }); + const [accessToSalidomo, setAccessToSalidomo] = useState(() => { + const storedValue = localStorage.getItem('accessToSalidomo'); + return storedValue === 'true'; + }); + const [product, setProduct] = useState(location.includes('salidomo') ? 1 : 0); + + const changeProductId = (new_product: number) => { + setProduct(new_product); + }; + + const changeAccessSalimax = (access: boolean) => { + setAccessToSalimax(access); + localStorage.setItem('accessToSalimax', JSON.stringify(access)); + }; + + const changeAccessSalidomo = (access: boolean) => { + setAccessToSalidomo(access); + localStorage.setItem('accessToSalidomo', JSON.stringify(access)); + }; + + return ( + + {children} + + ); +}; + +export default ProductIdContextProvider; diff --git a/typescript/frontend-marios2/src/contexts/WebSocketContextProvider.tsx b/typescript/frontend-marios2/src/contexts/WebSocketContextProvider.tsx index 8a339e80f..77f34f5e9 100644 --- a/typescript/frontend-marios2/src/contexts/WebSocketContextProvider.tsx +++ b/typescript/frontend-marios2/src/contexts/WebSocketContextProvider.tsx @@ -18,12 +18,8 @@ export const WebSocketContext = createContext< const WebSocketContextProvider = ({ children }: { children: ReactNode }) => { const [socket, setSocket] = useState(null); const [installations, setInstallations] = useState(null); - const [installationStatus, setInstallationStatus] = useState< - Record - >({}); - const [installationMode, setInstallatioMode] = useState< - Record - >({}); + const [installationStatus, setInstallationStatus] = useState(new Map()); + const [installationMode, setInstallationMode] = useState(new Map()); const BUFFER_LENGTH = 5; useEffect(() => { @@ -53,22 +49,33 @@ const WebSocketContextProvider = ({ children }: { children: ReactNode }) => { // Listen for messages socket.addEventListener('message', (event) => { const message = JSON.parse(event.data); // Parse the JSON data - - if (message.id != -1) { - const installation_id = message.id; - - //console.log('Message from server ', installation_id, status); - setInstallatioMode((prevMode) => { - // Create a new object by spreading the previous state - const newMode = { ...prevMode }; - newMode[installation_id] = message.testingMode; + if (Array.isArray(message)) { + setInstallationMode((prevMode) => { + const newMode = new Map(prevMode); + message.forEach((item) => { + newMode.set(item.id, item.testingMode); + }); return newMode; }); setInstallationStatus((prevStatus) => { - // Create a new object by spreading the previous state - const newStatus = { ...prevStatus }; - newStatus[installation_id] = message.status; + const newStatus = new Map(prevStatus); + message.forEach((item) => { + newStatus.set(item.id, item.status); + }); + return newStatus; + }); + } else if (message.id != -1) { + const installation_id = message.id; + + setInstallationMode((prevMode) => { + const newMode = new Map(prevMode); + newMode.set(message.id, message.testingMode); + return newMode; + }); + setInstallationStatus((prevStatus) => { + const newStatus = new Map(prevStatus); + newStatus.set(message.id, message.status); return newStatus; }); } @@ -86,17 +93,16 @@ const WebSocketContextProvider = ({ children }: { children: ReactNode }) => { }; const getStatus = (installationId: number) => { - let status; - if (!installationStatus.hasOwnProperty(installationId)) { - status = -2; - } else { - status = installationStatus[installationId]; - } - return status; + return installationStatus.get(installationId); + // if (installationStatus.has(installationId)) { + // installationStatus.get(installationId); + // } else { + // return -2; + // } }; const getTestingMode = (installationId: number) => { - return installationMode[installationId]; + return installationMode.get(installationId); }; return ( diff --git a/typescript/frontend-marios2/src/index.tsx b/typescript/frontend-marios2/src/index.tsx index 49008b962..14a7859ed 100644 --- a/typescript/frontend-marios2/src/index.tsx +++ b/typescript/frontend-marios2/src/index.tsx @@ -8,6 +8,7 @@ import { SidebarProvider } from 'src/contexts/SidebarContext'; import * as serviceWorker from 'src/serviceWorker'; import UserContextProvider from './contexts/userContext'; import TokenContextProvider from './contexts/tokenContext'; +import ProductIdContextProvider from './contexts/ProductIdContextProvider'; ReactDOM.render( @@ -15,7 +16,9 @@ ReactDOM.render( - + + + diff --git a/typescript/frontend-marios2/src/interfaces/Chart.tsx b/typescript/frontend-marios2/src/interfaces/Chart.tsx index b7c5ed5dc..2e9539de4 100644 --- a/typescript/frontend-marios2/src/interfaces/Chart.tsx +++ b/typescript/frontend-marios2/src/interfaces/Chart.tsx @@ -26,6 +26,8 @@ export interface overviewInterface { pvProduction: chartInfoInterface; dcBusVoltage: chartInfoInterface; overview: chartInfoInterface; + ACLoad: chartInfoInterface; + DCLoad: chartInfoInterface; } export interface chartAggregatedDataInterface { @@ -46,6 +48,8 @@ export interface chartDataInterface { gridPower: { name: string; data: number[] }; pvProduction: { name: string; data: number[] }; dcBusVoltage: { name: string; data: number[] }; + ACLoad: { name: string; data: number[] }; + DCLoad: { name: string; data: number[] }; } export interface BatteryDataInterface { @@ -339,7 +343,9 @@ export const transformInputToDailyData = async ( '/Battery/Dc/Power', '/GridMeter/Ac/Power/Active', '/PvOnDc/Dc/Power', - '/DcDc/Dc/Link/Voltage' + '/DcDc/Dc/Link/Voltage', + '/LoadOnAcGrid/Power/Active', + '/LoadOnDc/Power' ]; const categories = [ 'soc', @@ -347,7 +353,9 @@ export const transformInputToDailyData = async ( 'dcPower', 'gridPower', 'pvProduction', - 'dcBusVoltage' + 'dcBusVoltage', + 'ACLoad', + 'DCLoad' ]; const chartData: chartDataInterface = { @@ -356,7 +364,9 @@ export const transformInputToDailyData = async ( dcPower: { name: 'Battery Power', data: [] }, gridPower: { name: 'Grid Power', data: [] }, pvProduction: { name: 'Pv Production', data: [] }, - dcBusVoltage: { name: 'DC Bus Voltage', data: [] } + dcBusVoltage: { name: 'DC Bus Voltage', data: [] }, + ACLoad: { name: 'AC Load', data: [] }, + DCLoad: { name: 'DC Load', data: [] } }; const chartOverview: overviewInterface = { @@ -367,7 +377,9 @@ export const transformInputToDailyData = async ( gridPower: { magnitude: 0, unit: '', min: 0, max: 0 }, pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 }, dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 }, - overview: { magnitude: 0, unit: '', min: 0, max: 0 } + overview: { magnitude: 0, unit: '', min: 0, max: 0 }, + ACLoad: { magnitude: 0, unit: '', min: 0, max: 0 }, + DCLoad: { magnitude: 0, unit: '', min: 0, max: 0 } }; categories.forEach((category) => { @@ -560,7 +572,9 @@ export const transformInputToAggregatedData = async ( gridPower: { magnitude: 0, unit: '', min: 0, max: 0 }, pvProduction: { magnitude: 0, unit: '', min: 0, max: 0 }, dcBusVoltage: { magnitude: 0, unit: '', min: 0, max: 0 }, - overview: { magnitude: 0, unit: '', min: 0, max: 0 } + overview: { magnitude: 0, unit: '', min: 0, max: 0 }, + ACLoad: { magnitude: 0, unit: '', min: 0, max: 0 }, + DCLoad: { magnitude: 0, unit: '', min: 0, max: 0 } }; pathsToSearch.forEach((path) => { diff --git a/typescript/frontend-marios2/src/layouts/SidebarLayout/Sidebar/SidebarMenu/index.tsx b/typescript/frontend-marios2/src/layouts/SidebarLayout/Sidebar/SidebarMenu/index.tsx index 0a439921c..9b9ce4207 100644 --- a/typescript/frontend-marios2/src/layouts/SidebarLayout/Sidebar/SidebarMenu/index.tsx +++ b/typescript/frontend-marios2/src/layouts/SidebarLayout/Sidebar/SidebarMenu/index.tsx @@ -16,6 +16,7 @@ import TableChartTwoToneIcon from '@mui/icons-material/TableChartTwoTone'; import { FormattedMessage } from 'react-intl'; import { UserContext } from '../../../../contexts/userContext'; import { UserType } from '../../../../interfaces/UserTypes'; +import { ProductIdContext } from '../../../../contexts/ProductIdContextProvider'; const MenuWrapper = styled(Box)( ({ theme }) => ` @@ -163,6 +164,7 @@ function SidebarMenu() { const { closeSidebar } = useContext(SidebarContext); const context = useContext(UserContext); const { currentUser, setUser } = context; + const { accessToSalimax, accessToSalidomo } = useContext(ProductIdContext); return ( <> @@ -176,23 +178,25 @@ function SidebarMenu() { } > - - - - - + {accessToSalimax && ( + + + + + + )} - {currentUser.userType == UserType.admin && ( + {accessToSalidomo && (