diff --git a/csharp/App/Backend/Backend.csproj b/csharp/App/Backend/Backend.csproj
index bf230bd4e..4b498515e 100644
--- a/csharp/App/Backend/Backend.csproj
+++ b/csharp/App/Backend/Backend.csproj
@@ -13,6 +13,7 @@
+
@@ -39,6 +40,200 @@
PreserveNewest
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/csharp/App/Backend/Controller.cs b/csharp/App/Backend/Controller.cs
index ada5ed86a..7adf7d751 100644
--- a/csharp/App/Backend/Controller.cs
+++ b/csharp/App/Backend/Controller.cs
@@ -197,7 +197,10 @@ public class Controller : ControllerBase
if (user == null)
return Unauthorized();
- return user.DescendantUsers().Select(u => u.HidePassword()).ToList();
+ return user
+ .DescendantUsers()
+ .Select(u => u.HidePassword())
+ .ToList();
}
@@ -252,7 +255,7 @@ public class Controller : ControllerBase
[HttpPost(nameof(CreateUser))]
- public ActionResult CreateUser(User newUser, Token authToken)
+ public ActionResult CreateUser([FromBody] User newUser, Token authToken)
{
return Db.GetSession(authToken).Create(newUser)
? newUser.HidePassword()
@@ -260,7 +263,7 @@ public class Controller : ControllerBase
}
[HttpPost(nameof(CreateInstallation))]
- public async Task> CreateInstallation([FromBody]Installation installation, Token authToken)
+ public async Task> CreateInstallation([FromBody] Installation installation, Token authToken)
{
var session = Db.GetSession(authToken);
@@ -271,7 +274,7 @@ public class Controller : ControllerBase
}
[HttpPost(nameof(CreateFolder))]
- public ActionResult CreateFolder(Folder folder, Token authToken)
+ public ActionResult CreateFolder([FromBody] Folder folder, Token authToken)
{
var session = Db.GetSession(authToken);
@@ -331,7 +334,7 @@ public class Controller : ControllerBase
var session = Db.GetSession(authToken);
// TODO: automatic BadRequest when properties are null during deserialization
- var installation = Db.GetFolderById(installationAccess.InstallationId);
+ var installation = Db.GetInstallationById(installationAccess.InstallationId);
var user = Db.GetUserById(installationAccess.UserId);
return session.RevokeUserAccessTo(user, installation)
@@ -342,7 +345,7 @@ public class Controller : ControllerBase
[HttpPut(nameof(UpdateUser))]
- public ActionResult UpdateUser(User updatedUser, Token authToken)
+ public ActionResult UpdateUser([FromBody] User updatedUser, Token authToken)
{
var session = Db.GetSession(authToken);
@@ -366,7 +369,7 @@ public class Controller : ControllerBase
[HttpPut(nameof(UpdateInstallation))]
- public ActionResult UpdateInstallation(Installation installation, Token authToken)
+ public ActionResult UpdateInstallation([FromBody] Installation installation, Token authToken)
{
var session = Db.GetSession(authToken);
@@ -378,7 +381,7 @@ public class Controller : ControllerBase
[HttpPut(nameof(UpdateFolder))]
- public ActionResult UpdateFolder(Folder folder, Token authToken)
+ public ActionResult UpdateFolder([FromBody] Folder folder, Token authToken)
{
var session = Db.GetSession(authToken);
diff --git a/csharp/App/Backend/DataTypes/Installation.cs b/csharp/App/Backend/DataTypes/Installation.cs
index 71fb0d640..492558c54 100644
--- a/csharp/App/Backend/DataTypes/Installation.cs
+++ b/csharp/App/Backend/DataTypes/Installation.cs
@@ -9,8 +9,9 @@ public class Installation : TreeNode
public String Country { get; set; } = "";
// TODO: make relation
- [Ignore] public IReadOnlyList OrderNumbers { get; set; } = Array.Empty();
-
+ //public IReadOnlyList OrderNumbers { get; set; } = Array.Empty();
+ public String OrderNumbers { get; set; } = "";
+
public Double Lat { get; set; }
public Double Long { get; set; }
diff --git a/csharp/App/Backend/DataTypes/Methods/Folder.cs b/csharp/App/Backend/DataTypes/Methods/Folder.cs
index a71960f52..8098b53c0 100644
--- a/csharp/App/Backend/DataTypes/Methods/Folder.cs
+++ b/csharp/App/Backend/DataTypes/Methods/Folder.cs
@@ -52,6 +52,11 @@ public static class FolderMethods
.Skip(1); // skip self
}
+ public static IEnumerable DescendantFoldersAndSelf(this Folder parent)
+ {
+ return parent
+ .TraverseDepthFirstPreOrder(ChildFolders);
+ }
public static Boolean IsDescendantOf(this Folder folder, Folder ancestor)
{
return folder
diff --git a/csharp/App/Backend/DataTypes/Methods/Installation.cs b/csharp/App/Backend/DataTypes/Methods/Installation.cs
index fbae86041..cf6bdb014 100644
--- a/csharp/App/Backend/DataTypes/Methods/Installation.cs
+++ b/csharp/App/Backend/DataTypes/Methods/Installation.cs
@@ -1,4 +1,5 @@
using InnovEnergy.App.Backend.Database;
+using InnovEnergy.App.Backend.Relations;
using InnovEnergy.App.Backend.S3;
using InnovEnergy.Lib.Utils;
@@ -119,12 +120,28 @@ public static class InstallationMethods
return Db.Installations.Any(i => i.Id == installation.Id);
}
- public static IReadOnlyList GetOrderNumbers(this Installation installation)
+ public static Boolean SetOrderNumbers(this Installation installation)
{
- return Db.OrderNumber2Installation
+ foreach (var orderNumber in installation.OrderNumbers.Split(','))
+ {
+
+ var o2I = new OrderNumber2Installation
+ {
+ OrderNumber = orderNumber,
+ InstallationId = installation.Id
+ };
+ Db.Create(o2I);
+ }
+
+ return true;
+ }
+
+ public static String GetOrderNumbers(this Installation installation)
+ {
+ return string.Join(", ", Db.OrderNumber2Installation
.Where(i => i.InstallationId == installation.Id)
.Select(i => i.OrderNumber)
- .ToReadOnlyList();
+ .ToReadOnlyList());
}
public static Installation FillOrderNumbers(this Installation installation)
diff --git a/csharp/App/Backend/DataTypes/Methods/Session.cs b/csharp/App/Backend/DataTypes/Methods/Session.cs
index 8f2ee1191..f2a1540c3 100644
--- a/csharp/App/Backend/DataTypes/Methods/Session.cs
+++ b/csharp/App/Backend/DataTypes/Methods/Session.cs
@@ -83,18 +83,19 @@ public static class SessionMethods
var user = session?.User;
return user is not null
- && installation is not null
- && user.HasWriteAccess
- && user.HasAccessToParentOf(installation)
- && Db.Create(installation) // TODO: these two in a transaction
- && Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id })
- && await installation.CreateBucket()
- && await installation.RenewS3Credentials(); // generation of access _after_ generation of
- // bucket to prevent "zombie" access-rights.
- // This might fuck us over if the creation of access rights fails,
- // as bucket-names are unique and bound to the installation id... -K
+ && installation is not null
+ && user.HasWriteAccess
+ && user.HasAccessToParentOf(installation)
+ && Db.Create(installation) // TODO: these two in a transaction
+ && installation.SetOrderNumbers()
+ && Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id });
+ //&& await installation.CreateBucket()
+ //&& await installation.RenewS3Credentials(); // generation of access _after_ generation of
+ // bucket to prevent "zombie" access-rights.
+ // This might fuck us over if the creation of access rights fails,
+ // as bucket-names are unique and bound to the installation id... -K
}
-
+
public static Boolean Update(this Session? session, Installation? installation)
{
var user = session?.User;
@@ -104,7 +105,7 @@ public static class SessionMethods
if (!Equals(originalOrderNumbers, installation?.OrderNumbers))
{
- foreach (var orderNumber in installation!.OrderNumbers)
+ foreach (var orderNumber in installation!.OrderNumbers.Split(','))
{
if (originalOrderNumbers.Contains(orderNumber)) continue;
var o2I = new OrderNumber2Installation
@@ -115,7 +116,7 @@ public static class SessionMethods
Db.Create(o2I);
}
- foreach (var orderNumberOld in originalOrderNumbers)
+ foreach (var orderNumberOld in originalOrderNumbers.Split(','))
{
if (!installation!.OrderNumbers.Contains(orderNumberOld))
{
@@ -138,13 +139,13 @@ public static class SessionMethods
public static async Task Delete(this Session? session, Installation? installation)
{
var user = session?.User;
-
+
return user is not null
- && installation is not null
- && user.HasWriteAccess
- && user.HasAccessTo(installation)
- && Db.Delete(installation)
- && await installation.DeleteBucket();
+ && installation is not null
+ && user.HasWriteAccess
+ && user.HasAccessTo(installation)
+ && Db.Delete(installation);
+ //&& await installation.DeleteBucket();
}
public static Boolean Create(this Session? session, User newUser)
@@ -158,7 +159,7 @@ public static class SessionMethods
&& newUser
.WithParent(sessionUser)
.Do(() => newUser.MustResetPassword = true)
- .Do(() => newUser.Password = newUser.SaltAndHashPassword(newUser.Password))
+ .Do(() => newUser.Password = null)
.Apply(Db.Create);
// && Mailer.Mailer.SendVerificationMessage(newUser);
diff --git a/csharp/App/Backend/DataTypes/TreeNode.cs b/csharp/App/Backend/DataTypes/TreeNode.cs
index b1a4a72fa..5613fbbdf 100644
--- a/csharp/App/Backend/DataTypes/TreeNode.cs
+++ b/csharp/App/Backend/DataTypes/TreeNode.cs
@@ -5,7 +5,7 @@ namespace InnovEnergy.App.Backend.DataTypes;
public abstract partial class TreeNode
{
[PrimaryKey, AutoIncrement]
- public virtual Int64 Id { get; set; }
+ public virtual Int64 Id { get; set; }
public virtual String Name { get; set; } = ""; // overridden by User (unique)
public String Information { get; set; } = ""; // unstructured random info
diff --git a/csharp/App/Backend/DataTypes/User.cs b/csharp/App/Backend/DataTypes/User.cs
index a9c6b4bdd..7397eb6e2 100644
--- a/csharp/App/Backend/DataTypes/User.cs
+++ b/csharp/App/Backend/DataTypes/User.cs
@@ -8,7 +8,7 @@ public class User : TreeNode
public Boolean HasWriteAccess { get; set; } = false;
public Boolean MustResetPassword { get; set; } = false;
public String Language { get; set; } = null!;
- public String Password { get; set; } = null!;
+ public String? Password { get; set; } = null!;
[Unique]
public override String Name { get; set; } = null!;
diff --git a/csharp/App/Backend/Database/Db.cs b/csharp/App/Backend/Database/Db.cs
index 0a630bc50..ed95f6340 100644
--- a/csharp/App/Backend/Database/Db.cs
+++ b/csharp/App/Backend/Database/Db.cs
@@ -27,7 +27,8 @@ public static partial class Db
.Last().Name;
var fileConnection = new SQLiteConnection("DbBackups/"+latestDb);
-
+
+ Console.Out.Write(latestDb);
var memoryConnection = new SQLiteConnection(":memory:");
// fileConnection.Backup(memoryConnection.DatabasePath);
@@ -74,7 +75,7 @@ public static partial class Db
public static void BackupDatabase()
{
var filename = "db-" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + ".sqlite";
- Connection.Backup("DbBackups/"+filename);
+ Connection.Backup("DbBackups/" + filename);
}
public static TableQuery Sessions => Connection.Table();
diff --git a/csharp/App/Backend/Database/Delete.cs b/csharp/App/Backend/Database/Delete.cs
index 55ff12f84..cb3895037 100644
--- a/csharp/App/Backend/Database/Delete.cs
+++ b/csharp/App/Backend/Database/Delete.cs
@@ -10,12 +10,18 @@ public static partial class Db
{
public static Boolean Delete(Folder folder)
{
- return RunTransaction(DeleteFolderAndAllItsDependencies);
+ var deleteSuccess= RunTransaction(DeleteFolderAndAllItsDependencies);
+ if (deleteSuccess)
+ {
+ BackupDatabase();
+ }
+
+ return deleteSuccess;
Boolean DeleteFolderAndAllItsDependencies()
{
return folder
- .DescendantFolders()
+ .DescendantFoldersAndSelf()
.All(DeleteDescendantFolderAndItsDependencies);
}
@@ -24,10 +30,8 @@ public static partial class Db
FolderAccess .Delete(r => r.FolderId == f.Id);
Installations.Delete(r => r.ParentId == f.Id);
var delete = Folders.Delete(r => r.Id == f.Id);
- var deleteSuccess = delete > 0;
- if (deleteSuccess)
- BackupDatabase();
- return deleteSuccess;
+
+ return delete>0;
}
}
@@ -42,6 +46,7 @@ public static partial class Db
Boolean DeleteInstallationAndItsDependencies()
{
InstallationAccess.Delete(i => i.InstallationId == installation.Id);
+ OrderNumber2Installation.Delete(i => i.InstallationId == installation.Id);
return Installations.Delete(i => i.Id == installation.Id) > 0;
}
}
diff --git a/csharp/App/Backend/Program.cs b/csharp/App/Backend/Program.cs
index 3e49baeb2..f39e5e463 100644
--- a/csharp/App/Backend/Program.cs
+++ b/csharp/App/Backend/Program.cs
@@ -3,6 +3,8 @@ using InnovEnergy.App.Backend.Database;
using Microsoft.AspNetCore.HttpOverrides;
using Microsoft.AspNetCore.Mvc;
using Microsoft.OpenApi.Models;
+using System.Net;
+using InnovEnergy.Lib.Utils;
namespace InnovEnergy.App.Backend;
@@ -12,8 +14,8 @@ public static class Program
{
//Db.CreateFakeRelations();
Db.Init();
-
var builder = WebApplication.CreateBuilder(args);
+
builder.Services.AddControllers();
builder.Services.AddProblemDetails(setup =>
{
@@ -21,7 +23,7 @@ public static class Program
setup.IncludeExceptionDetails = (ctx, env) => builder.Environment.IsDevelopment() || builder.Environment.IsStaging();
//This handles our Exceptions
- setup.Map(exception => new ProblemDetails()
+ setup.Map(exception => new ProblemDetails
{
Detail = exception.Detail,
Status = exception.Status,
@@ -38,6 +40,16 @@ public static class Program
});
var app = builder.Build();
+
+ app.Use(async (context, next) =>
+ {
+ var x = 2;
+
+ context.Request.WriteLine();
+
+ await next(context);
+ });
+
app.UseForwardedHeaders(new ForwardedHeadersOptions
{
@@ -51,12 +63,11 @@ public static class Program
}
app.UseCors(p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()) ;
- app.UseHttpsRedirection();
+ //app.UseHttpsRedirection();
app.MapControllers();
app.UseProblemDetails();
-
- app.Run();
+ app.Run();
}
private static OpenApiInfo OpenApiInfo { get; } = new OpenApiInfo
diff --git a/csharp/App/Backend/exoscale.toml b/csharp/App/Backend/S3/exoscale.toml
similarity index 100%
rename from csharp/App/Backend/exoscale.toml
rename to csharp/App/Backend/S3/exoscale.toml
diff --git a/csharp/App/VrmGrabber/server.py b/csharp/App/VrmGrabber/server.py
index 27abdbd52..5d5c350ec 100644
--- a/csharp/App/VrmGrabber/server.py
+++ b/csharp/App/VrmGrabber/server.py
@@ -3,7 +3,7 @@ from flask import Flask
from json2html import json2html
app = Flask(__name__)
-serverUrl = "https://127.0.0.1:7087/api" #todo change me
+serverUrl = "https://127.0.0.1:8000/api" #todo change me
@app.route('/')
def hello():
diff --git a/typescript/Frontend/package.json b/typescript/Frontend/package.json
index 97d6cfe9c..6e32e3d2f 100644
--- a/typescript/Frontend/package.json
+++ b/typescript/Frontend/package.json
@@ -35,6 +35,7 @@
"yup": "^1.1.0"
},
"scripts": {
+
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
diff --git a/typescript/Frontend/src/App.tsx b/typescript/Frontend/src/App.tsx
index ea3ca8592..e7216a7c5 100644
--- a/typescript/Frontend/src/App.tsx
+++ b/typescript/Frontend/src/App.tsx
@@ -1,11 +1,11 @@
import useToken from "./hooks/useToken";
import Login from "./Login";
-import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
+import {BrowserRouter, Navigate, Route, Routes} from "react-router-dom";
-import { Container, Grid, Box } from "@mui/material";
+import {Box, Container, Grid} from "@mui/material";
import routes from "./routes.json";
-import { IntlProvider } from "react-intl";
-import { useContext, useState } from "react";
+import {IntlProvider} from "react-intl";
+import {useContext, useState} from "react";
import en from "./lang/en.json";
import de from "./lang/de.json";
import fr from "./lang/fr.json";
@@ -14,10 +14,10 @@ import LogoutButton from "./components/Layout/LogoutButton";
import Users from "./components/Users/Users";
import NavigationTabs from "./components/Layout/NavigationTabs";
import InstallationPage from "./components/Installations/InstallationPage";
-import { UserContext } from "./components/Context/UserContextProvider";
+import {UserContext} from "./components/Context/UserContextProvider";
import ResetPassword from "./ResetPassword";
import innovenergyLogo from "./resources/innoveng_logo_on_orange.png";
-import { colors } from "./index";
+import {colors} from "./index";
const App = () => {
const { token, setToken, removeToken } = useToken();
@@ -51,8 +51,10 @@ const App = () => {
>
-
-
+
+
+
+
void;
fetchData: (timestamp: UnixTime) => Promise>;
}
-
+
export const S3CredentialsContext =
createContext({
s3Credentials: {} as I_S3Credentials,
@@ -27,7 +27,7 @@ const S3CredentialsContextProvider = ({
const [s3Credentials, setS3Credentials] = useState();
const saveS3Credentials = (credentials: I_S3Credentials, id: string) => {
- const s3Bucket = id + "-3e5b3069-214a-43ee-8d85-57d72000c10d";
+ const s3Bucket = id + "-3e5b3069-214a-43ee-8d85-57d72000c19d";
setS3Credentials({ s3Bucket, ...credentials });
};
diff --git a/typescript/Frontend/src/components/Context/UserContextProvider.tsx b/typescript/Frontend/src/components/Context/UserContextProvider.tsx
index 2a1ea29b5..f7a83464c 100644
--- a/typescript/Frontend/src/components/Context/UserContextProvider.tsx
+++ b/typescript/Frontend/src/components/Context/UserContextProvider.tsx
@@ -1,6 +1,7 @@
import { createContext, ReactNode, useState } from "react";
import { I_User } from "../../util/user.util";
+
interface I_InstallationContextProviderProps {
currentUser?: I_User;
setCurrentUser: (value: I_User) => void;
diff --git a/typescript/Frontend/src/components/Installations/Detail/InstallationForm.tsx b/typescript/Frontend/src/components/Installations/Detail/InstallationForm.tsx
index e780ab854..307ab6118 100644
--- a/typescript/Frontend/src/components/Installations/Detail/InstallationForm.tsx
+++ b/typescript/Frontend/src/components/Installations/Detail/InstallationForm.tsx
@@ -33,7 +33,7 @@ const InstallationForm = (props: I_InstallationFormProps) => {
const readOnly = !getCurrentUser().hasWriteAccess;
const intl = useIntl();
-
+
const validationSchema = Yup.object().shape({
name: Yup.string().required(
intl.formatMessage({
@@ -53,6 +53,19 @@ const InstallationForm = (props: I_InstallationFormProps) => {
defaultMessage: "Location is required",
})
),
+ country: Yup.string().required(
+ intl.formatMessage({
+ id: "requiredCountry",
+ defaultMessage: "Country is required",
+ })
+ ),
+ orderNumbers: Yup.string().required(
+ intl.formatMessage({
+ id: "requiredOrderNumber",
+ defaultMessage: "Order Number is required",
+ })
+ ),
+
});
const formik = useFormik({
@@ -64,6 +77,12 @@ const InstallationForm = (props: I_InstallationFormProps) => {
orderNumbers: values.orderNumbers,
},
onSubmit: (formikValues) => {
+ /*const updatedValues = {
+ ...formikValues,
+
+ orderNumbers: formikValues.orderNumbers.split(','),
+ };*/
+
handleSubmit(values, formikValues)
.then(() => {
setOpen(true);
@@ -164,12 +183,22 @@ const InstallationForm = (props: I_InstallationFormProps) => {
additionalButtons.map((button) => button)}
{!readOnly && (
+
+
)}
+ {!readOnly && (
+
+
+
+ )}
{
);
};
-export default Installations;
+export default Installations;
\ No newline at end of file
diff --git a/typescript/Frontend/src/components/Layout/NavigationTabs.tsx b/typescript/Frontend/src/components/Layout/NavigationTabs.tsx
index b74f31a0d..bf8de966f 100644
--- a/typescript/Frontend/src/components/Layout/NavigationTabs.tsx
+++ b/typescript/Frontend/src/components/Layout/NavigationTabs.tsx
@@ -16,12 +16,7 @@ const NavigationTabs = () => {
return (
<>
diff --git a/typescript/Frontend/src/lang/en.json b/typescript/Frontend/src/lang/en.json
index 3cbbc6634..2751313ca 100644
--- a/typescript/Frontend/src/lang/en.json
+++ b/typescript/Frontend/src/lang/en.json
@@ -2,6 +2,7 @@
"liveView": "Live view",
"allInstallations": "All installations",
"applyChanges": "Apply changes",
+ "deleteInstallation": "Delete Installation",
"country": "Country",
"customerName": "Customer name",
"english": "English",
@@ -44,6 +45,7 @@
"requiredLocation": "Location is required",
"requiredName": "Name is required",
"requiredRegion": "Region is required",
+ "requiredOrderNumber": "Required Order Number",
"submit": "Submit",
"user": "User",
"userTabs": "user tabs"
diff --git a/typescript/frontend-marios2/src/Resources/axiosConfig.tsx b/typescript/frontend-marios2/src/Resources/axiosConfig.tsx
new file mode 100644
index 000000000..7406652c7
--- /dev/null
+++ b/typescript/frontend-marios2/src/Resources/axiosConfig.tsx
@@ -0,0 +1,26 @@
+import axios from 'axios';
+
+export const axiosConfigWithoutToken = axios.create({
+ baseURL: 'https://localhost:7087/api'
+});
+
+const axiosConfig = axios.create({
+ baseURL: 'https://localhost:7087/api'
+});
+
+axiosConfig.defaults.params = {};
+axiosConfig.interceptors.request.use(
+ (config) => {
+ const tokenString = localStorage.getItem('token');
+ const token = tokenString !== null ? tokenString : '';
+ if (token) {
+ config.params['authToken'] = token;
+ }
+ return config;
+ },
+ (error) => {
+ return Promise.reject(error);
+ }
+);
+
+export default axiosConfig;
diff --git a/typescript/frontend-marios2/src/Resources/innoveng_logo_on_orange.png b/typescript/frontend-marios2/src/Resources/innoveng_logo_on_orange.png
new file mode 100644
index 000000000..4f1714ecd
Binary files /dev/null and b/typescript/frontend-marios2/src/Resources/innoveng_logo_on_orange.png differ
diff --git a/typescript/frontend-marios2/src/content/dashboards/Users/FlatUsersView.tsx b/typescript/frontend-marios2/src/content/dashboards/Users/FlatUsersView.tsx
new file mode 100644
index 000000000..452f81c9c
--- /dev/null
+++ b/typescript/frontend-marios2/src/content/dashboards/Users/FlatUsersView.tsx
@@ -0,0 +1,126 @@
+import React, { useState } from 'react';
+import {
+ Card,
+ Divider,
+ Grid,
+ Table,
+ TableBody,
+ TableCell,
+ TableContainer,
+ TableHead,
+ TableRow,
+ Typography,
+ useTheme
+} from '@mui/material';
+import { InnovEnergyUser } from 'src/interfaces/UserTypes';
+import User from './User';
+
+interface FlatUsersViewProps {
+ users: InnovEnergyUser[];
+ fetchDataAgain: () => void;
+}
+
+const FlatUsersView = (props: FlatUsersViewProps) => {
+ const [selectedUser, setSelectedUser] = useState(-1);
+ const selectedBulkActions = selectedUser !== -1;
+
+ const handleSelectOneUser = (installationID: number): void => {
+ if (selectedUser != installationID) {
+ setSelectedUser(installationID);
+ } else {
+ setSelectedUser(-1);
+ }
+ };
+
+ const theme = useTheme();
+ const [isRowHovered, setHoveredRow] = useState(-1);
+
+ const handleRowMouseEnter = (id: number) => {
+ setHoveredRow(id);
+ };
+
+ const handleRowMouseLeave = () => {
+ setHoveredRow(-1);
+ };
+ const findUser = (id: number) => {
+ return props.users.find((user) => user.id === id);
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+ Username
+ Email
+
+
+
+ {props.users.map((user) => {
+ const isInstallationSelected = user.id === selectedUser;
+ const rowStyles =
+ isRowHovered === user.id
+ ? {
+ cursor: 'pointer',
+ backgroundColor: theme.colors.primary.lighter // Set your desired hover background color here
+ }
+ : {};
+
+ return (
+ handleSelectOneUser(user.id)}
+ style={rowStyles}
+ onMouseEnter={() => handleRowMouseEnter(user.id)}
+ onMouseLeave={() => handleRowMouseLeave()}
+ >
+
+
+
+ {user.name}
+
+
+
+
+ {user.email}
+
+
+
+ );
+ })}
+
+
+
+
+
+
+ {selectedBulkActions && (
+
+ )}
+
+ );
+};
+
+export default FlatUsersView;
diff --git a/typescript/frontend-marios2/src/content/dashboards/Users/UsersSearch.tsx b/typescript/frontend-marios2/src/content/dashboards/Users/UsersSearch.tsx
new file mode 100644
index 000000000..3ae60674a
--- /dev/null
+++ b/typescript/frontend-marios2/src/content/dashboards/Users/UsersSearch.tsx
@@ -0,0 +1,90 @@
+import React, { useContext, useEffect, useState } from 'react';
+import {
+ FormControl,
+ Grid,
+ InputAdornment,
+ TextField,
+ useTheme
+} from '@mui/material';
+import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone';
+import FlatUsersView from './FlatUsersView';
+import { UsersContext } from '../../../contexts/UsersContextProvider';
+import Button from '@mui/material/Button';
+import UserForm from './userForm';
+import { UserContext } from '../../../contexts/userContext';
+
+function UsersSearch() {
+ const theme = useTheme();
+ const [searchTerm, setSearchTerm] = useState('');
+ const { availableUsers, fetchAvailableUsers } = useContext(UsersContext);
+ const [filteredData, setFilteredData] = useState(availableUsers);
+ const [openModal, setOpenModal] = useState(false);
+ const context = useContext(UserContext);
+ const { currentUser, setUser } = context;
+
+ useEffect(() => {
+ fetchAvailableUsers();
+ }, []);
+
+ const fetchDataAgain = () => {
+ fetchAvailableUsers();
+ };
+
+ useEffect(() => {
+ const filtered = availableUsers.filter((item) =>
+ item.name.toLowerCase().includes(searchTerm.toLowerCase())
+ );
+ setFilteredData(filtered);
+ }, [searchTerm, availableUsers]);
+
+ const handleSubmit = () => {
+ setOpenModal(true);
+ };
+ const handleUserFormSubmit = () => {
+ setOpenModal(false);
+ fetchAvailableUsers();
+ };
+
+ const handleUserFormCancel = () => {
+ setOpenModal(false);
+ };
+
+ return (
+ <>
+
+
+ {currentUser.hasWriteAccess && (
+
+ )}
+
+
+ {openModal && (
+
+ )}
+
+
+
+ setSearchTerm(e.target.value)}
+ fullWidth
+ InputProps={{
+ startAdornment: (
+
+
+
+ )
+ }}
+ />
+
+
+
+
+ >
+ );
+}
+
+export default UsersSearch;
diff --git a/typescript/frontend-marios2/src/content/dashboards/Users/index.tsx b/typescript/frontend-marios2/src/content/dashboards/Users/index.tsx
new file mode 100644
index 000000000..5980287f0
--- /dev/null
+++ b/typescript/frontend-marios2/src/content/dashboards/Users/index.tsx
@@ -0,0 +1,25 @@
+import Footer from 'src/components/Footer';
+import { Box, Container, Grid, useTheme } from '@mui/material';
+import UsersSearch from './UsersSearch';
+import UsersContextProvider from 'src/contexts/UsersContextProvider';
+
+function Users() {
+ const theme = useTheme();
+
+ return (
+ <>
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
+
+export default Users;
diff --git a/typescript/frontend-marios2/src/lang/de.json b/typescript/frontend-marios2/src/lang/de.json
new file mode 100644
index 000000000..56edefed1
--- /dev/null
+++ b/typescript/frontend-marios2/src/lang/de.json
@@ -0,0 +1,47 @@
+{
+ "information": "Information",
+ "addNewChild": "Neues Kind hinzufügen",
+ "addNewDialogButton": "Neue Dialogschaltfläche hinzufügen",
+ "addUser": "Nutzer erstellen",
+ "alarms": "Alarme",
+ "applyChanges": "Änderungen speichern",
+ "country": "Land",
+ "createNewFolder": "Neuen Ordner erstellen",
+ "createNewUser": "Neuen Nutzer erstellen",
+ "customerName": "Kundenname",
+ "email": "Email",
+ "english": "Englisch",
+ "error": "Fehler",
+ "folder": "Ordner",
+ "german": "Deutsch",
+ "groupTabs": "Gruppen",
+ "groupTree": "Gruppenbaum",
+ "information": "Information",
+ "inheritedAccess": "Vererbter Zugriff von",
+ "installation": "Installation",
+ "installationTabs": "Installationen",
+ "installations": "Installationen",
+ "lastWeek": "Letzte Woche",
+ "location": "Standort",
+ "log": "Logbuch",
+ "logout": "Abmelden",
+ "makeASelection": "Bitte wählen Sie links eine Auswahl",
+ "manageAccess": "Zugriff verwalten",
+ "move": "Verschieben",
+ "moveTo": "Verschieben zu",
+ "moveTree": "Baum verschieben",
+ "name": "Name",
+ "navigationTabs": "Navigation",
+ "orderNumbers": "Bestellnummer",
+ "region": "Region",
+ "requiredLocation": "Standort ist erforderlich",
+ "requiredName": "Name ist erforderlich",
+ "requiredRegion": "Region ist erforderlich",
+ "search": "Suche",
+ "submit": "Senden",
+ "updateFolderErrorMessage": "Fehler, Ordner kann nicht aktualisiert werden",
+ "updatedSuccessfully": "Erfolgreich aktualisiert",
+ "user": "Nutzer",
+ "userTabs": "Nutzer",
+ "users": "Nutzer"
+}
diff --git a/typescript/frontend-marios2/src/lang/en.json b/typescript/frontend-marios2/src/lang/en.json
new file mode 100644
index 000000000..2751313ca
--- /dev/null
+++ b/typescript/frontend-marios2/src/lang/en.json
@@ -0,0 +1,52 @@
+{
+ "liveView": "Live view",
+ "allInstallations": "All installations",
+ "applyChanges": "Apply changes",
+ "deleteInstallation": "Delete Installation",
+ "country": "Country",
+ "customerName": "Customer name",
+ "english": "English",
+ "german": "German",
+ "installation": "Installation",
+ "location": "Location",
+ "log": "Log",
+ "orderNumbers": "Order numbers",
+ "region": "Region",
+ "search": "Search",
+ "users": "Users",
+ "logout": "Logout",
+ "updatedSuccessfully": "Updated successfully",
+ "groups": "Groups",
+ "group": "Group",
+ "folder": "Folder",
+ "updateFolderErrorMessage": "Couldn't update folder, an error occured",
+ "Information": "Information",
+ "addNewChild": "Add new child",
+ "addNewDialogButton": "Add new dialog button",
+ "addUser": "Create user",
+ "createNewFolder": "Create new folder",
+ "createNewUser": "Create new user",
+ "email": "Email",
+ "error": "",
+ "groupTabs": "Group tabs",
+ "groupTree": "Group tree",
+ "information": "Information",
+ "inheritedAccess": "Inherited access from",
+ "installationTabs": "Installation tabs",
+ "installations": "Installations",
+ "lastWeek": "Last week",
+ "makeASelection": "Please make a selection on the left",
+ "manageAccess": "Manage access",
+ "move": "Move",
+ "moveTo": "Move to",
+ "moveTree": "Move tree",
+ "name": "Name",
+ "navigationTabs": "Navigation tabs",
+ "requiredLocation": "Location is required",
+ "requiredName": "Name is required",
+ "requiredRegion": "Region is required",
+ "requiredOrderNumber": "Required Order Number",
+ "submit": "Submit",
+ "user": "User",
+ "userTabs": "user tabs"
+}
diff --git a/typescript/frontend-marios2/src/lang/fr.json b/typescript/frontend-marios2/src/lang/fr.json
new file mode 100644
index 000000000..f7834a5fe
--- /dev/null
+++ b/typescript/frontend-marios2/src/lang/fr.json
@@ -0,0 +1,47 @@
+{
+ "information": "Informations",
+ "addNewChild": "Ajouter un nouvel enfant",
+ "addNewDialogButton": "Ajouter un nouveau bouton de dialogue",
+ "addUser": "Créer un utilisateur",
+ "alarms": "Alarmes",
+ "applyChanges": "Appliquer les modifications",
+ "country": "Pays",
+ "createNewFolder": "Créer un nouveau dossier",
+ "createNewUser": "Créer un nouvel utilisateur",
+ "customerName": "Nom du client",
+ "email": "E-mail",
+ "english": "Anglais",
+ "error": "Erreur",
+ "folder": "Dossier",
+ "german": "Allemand",
+ "groupTabs": "Onglets de groupe",
+ "groupTree": "Arbre de groupe",
+ "information": "Informations",
+ "inheritedAccess": "Accès hérité de",
+ "installation": "Installation",
+ "installationTabs": "Onglets d'installation",
+ "installations": "Installations",
+ "lastWeek": "La semaine dernière",
+ "location": "Localisation",
+ "log": "Journal",
+ "logout": "Déconnexion",
+ "makeASelection": "Veuillez faire une sélection à gauche",
+ "manageAccess": "Gérer l'accès",
+ "move": "Déplacer",
+ "moveTo": "Déplacer à",
+ "moveTree": "Déplacer l'arbre",
+ "name": "Nom",
+ "navigationTabs": "Onglets de navigation",
+ "orderNumbers": "Numéro de commande",
+ "region": "Région",
+ "requiredLocation": "L'emplacement est requis",
+ "requiredName": "Le nom est obligatoire",
+ "requiredRegion": "La région est obligatoire",
+ "search": "Recherche",
+ "submit": "Soumettre",
+ "updateFolderErrorMessage": "Une erreur s'est produite, impossible de mettre à jour le dossier.",
+ "updatedSuccessfully": "Mise à jour réussie",
+ "user": "Utilisateur",
+ "userTabs": "Onglets utilisateurs",
+ "users": "Utilisateurs"
+}