Merge remote-tracking branch 'origin/main'
This commit is contained in:
commit
12326c2a6d
|
@ -7,6 +7,7 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
<PackageReference Include="AWSSDK.S3" Version="3.7.205.17" />
|
||||||
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
<PackageReference Include="Flurl.Http" Version="3.2.4" />
|
||||||
<PackageReference Include="Hellang.Middleware.ProblemDetails" Version="6.5.1" />
|
<PackageReference Include="Hellang.Middleware.ProblemDetails" Version="6.5.1" />
|
||||||
<PackageReference Include="Microsoft.AspNet.Identity.Core" Version="2.2.3" />
|
<PackageReference Include="Microsoft.AspNet.Identity.Core" Version="2.2.3" />
|
||||||
|
@ -34,213 +35,20 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../../Lib/Utils/Utils.csproj" />
|
<ProjectReference Include="../../Lib/Utils/Utils.csproj" />
|
||||||
<ProjectReference Include="../../Lib/Mailer/Mailer.csproj" />
|
<ProjectReference Include="../../Lib/Mailer/Mailer.csproj" />
|
||||||
<ProjectReference Include="..\..\Lib\S3Utils\S3Utils.csproj" />
|
<ProjectReference Include="../../Lib/S3Utils/S3Utils.csproj" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<None Update="Resources/s3cmd.py">
|
<None Update="Resources/s3cmd.py">
|
||||||
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
</None>
|
</None>
|
||||||
<None Remove="DbBackups\db-1692621279.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692620662.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692620973.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692620165.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692620475.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692619622.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692620266.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692618414.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692618326.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692612258.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692619963.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692620296.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692618125.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692621869.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692621699.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692611631.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692627958.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692715302.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692715647.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692715652.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692884061.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692884224.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692884244.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692884524.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692884642.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692885117.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692885781.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692885908.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692889590.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692889640.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692890673.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692890981.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692891081.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692891207.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692891239.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692891540.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692891640.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692947517.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692951226.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692956795.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692957809.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692958545.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692965093.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692965087.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692965105.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692965660.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692965676.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693813301.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693815606.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693813151.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693388471.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693385306.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693236515.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693230148.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693301572.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693388748.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693388189.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693297768.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693299939.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693229044.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693225598.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693300771.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693212155.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693225325.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693212119.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693211833.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693392099.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693392147.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693391476.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693395914.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693394644.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693389535.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693389069.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693812839.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693389121.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693390948.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693401522.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693299943.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693230582.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693388417.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693238297.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693228621.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693388588.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693823647.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693390865.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693395143.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693823298.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693389080.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693390583.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693391825.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693391007.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693390368.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693391131.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693392465.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693820540.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693389837.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693394858.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693395207.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693815792.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693389713.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693391988.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693389959.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693395856.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693488424.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693474277.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693482868.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693472646.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693470245.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693470249.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693469782.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693497578.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693474069.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693499184.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693395013.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693398191.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693394891.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693390733.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693389451.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693390182.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693811965.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692966481.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692969227.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692966381.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692967853.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693820304.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692967068.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693822619.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692969899.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693820664.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692966501.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693820595.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692966495.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692967782.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693820327.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692966486.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692965827.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693581684.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692966088.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693207198.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693214346.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692979283.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692979039.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692979326.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693210467.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692970330.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692979087.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692971615.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692970282.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692966463.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692971631.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692967061.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692968130.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692969863.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693574723.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1692969234.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693574756.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693572655.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693572839.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693563487.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693571800.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693554456.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693571859.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693571699.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693571694.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693555209.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693571639.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693465491.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693571738.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693465474.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693498493.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693575085.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693574598.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693575136.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693574755.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693574962.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693822770.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693822650.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693822757.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693822641.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693823785.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693823723.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693836773.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693837575.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693837906.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693838013.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693837691.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693837666.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693838039.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693838563.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693838248.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693838578.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693839416.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1693839492.sqlite" />
|
|
||||||
<None Remove="DbBackups\db-1694156276.sqlite" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<Content Update="Resources\urlAndKey.json">
|
<Content Update="Resources/urlAndKey.json">
|
||||||
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
<CopyToPublishDirectory>Never</CopyToPublishDirectory>
|
||||||
</Content>
|
</Content>
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,5 +1,3 @@
|
||||||
using System.Runtime.InteropServices.ComTypes;
|
|
||||||
using System.Text.Json.Nodes;
|
|
||||||
using InnovEnergy.App.Backend.Database;
|
using InnovEnergy.App.Backend.Database;
|
||||||
using InnovEnergy.App.Backend.DataTypes;
|
using InnovEnergy.App.Backend.DataTypes;
|
||||||
using InnovEnergy.App.Backend.DataTypes.Methods;
|
using InnovEnergy.App.Backend.DataTypes.Methods;
|
||||||
|
@ -21,17 +19,12 @@ public class Controller : ControllerBase
|
||||||
var user = Db.GetUserByEmail(username);
|
var user = Db.GetUserByEmail(username);
|
||||||
|
|
||||||
if (user is null)
|
if (user is null)
|
||||||
{
|
throw new Exceptions(400, "Null User Exception", "Must provide a user to log in as.", Request.Path.Value!);
|
||||||
throw new Exceptions(400,"Null User Exception", "Must provide a user to log in as.", Request.Path.Value!);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(user.Password.IsNullOrEmpty() && user.MustResetPassword))
|
if (!(user.Password.IsNullOrEmpty() && user.MustResetPassword) && !user.VerifyPassword(password))
|
||||||
{
|
{
|
||||||
if (!user.VerifyPassword(password))
|
//return Unauthorized("No Password set");
|
||||||
{
|
throw new Exceptions(401, "Wrong Password Exception", "Please try again.", Request.Path.Value!);
|
||||||
//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));
|
var session = new Session(user.HidePassword().HideParentIfUserHasNoAccessToParent(user));
|
||||||
|
@ -67,7 +60,9 @@ public class Controller : ControllerBase
|
||||||
if (user is null || !session.HasAccessTo(user))
|
if (user is null || !session.HasAccessTo(user))
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
|
|
||||||
return user.HidePassword().HideParentIfUserHasNoAccessToParent(session);
|
return user
|
||||||
|
.HidePassword()
|
||||||
|
.HideParentIfUserHasNoAccessToParent(session);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -102,10 +97,10 @@ public class Controller : ControllerBase
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
|
|
||||||
return installation
|
return installation
|
||||||
.UsersWithDirectAccess()
|
.UsersWithDirectAccess()
|
||||||
.Where(u => u.IsDescendantOf(user))
|
.Where(u => u.IsDescendantOf(user))
|
||||||
.Select(u => u.HidePassword())
|
.Select(u => u.HidePassword())
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet(nameof(GetUsersWithInheritedAccessToInstallation))]
|
[HttpGet(nameof(GetUsersWithInheritedAccessToInstallation))]
|
||||||
|
@ -121,11 +116,11 @@ public class Controller : ControllerBase
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
|
|
||||||
return installation
|
return installation
|
||||||
.Ancestors()
|
.Ancestors()
|
||||||
.SelectMany(f => f.UsersWithDirectAccess()
|
.SelectMany(f => f.UsersWithDirectAccess()
|
||||||
.Where(u => u.IsDescendantOf(user))
|
.Where(u => u.IsDescendantOf(user))
|
||||||
.Select(u => new { folderId = f.Id, folderName = f.Name, user = u.HidePassword() }))
|
.Select(u => new { folderId = f.Id, folderName = f.Name, user = u.HidePassword() }))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpGet(nameof(GetUsersWithDirectAccessToFolder))]
|
[HttpGet(nameof(GetUsersWithDirectAccessToFolder))]
|
||||||
|
@ -255,11 +250,11 @@ public class Controller : ControllerBase
|
||||||
|
|
||||||
|
|
||||||
[HttpPost(nameof(CreateUser))]
|
[HttpPost(nameof(CreateUser))]
|
||||||
public ActionResult<User> CreateUser([FromBody] User newUser, Token authToken)
|
public async Task<ActionResult<User>> CreateUser([FromBody] User newUser, Token authToken)
|
||||||
{
|
{
|
||||||
var create = Db.GetSession(authToken).Create(newUser);
|
var create = Db.GetSession(authToken).Create(newUser);
|
||||||
|
|
||||||
return create && Db.SendNewUserEmail(newUser)
|
return create && await Db.SendNewUserEmail(newUser)
|
||||||
? newUser.HidePassword()
|
? newUser.HidePassword()
|
||||||
: Unauthorized() ;
|
: Unauthorized() ;
|
||||||
}
|
}
|
||||||
|
@ -326,8 +321,8 @@ public class Controller : ControllerBase
|
||||||
var user = Db.GetUserById(installationAccess.UserId);
|
var user = Db.GetUserById(installationAccess.UserId);
|
||||||
|
|
||||||
return session.GrantUserAccessTo(user, installation)
|
return session.GrantUserAccessTo(user, installation)
|
||||||
? Ok()
|
? Ok()
|
||||||
: Unauthorized();
|
: Unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost(nameof(RevokeUserAccessToInstallation))]
|
[HttpPost(nameof(RevokeUserAccessToInstallation))]
|
||||||
|
@ -461,7 +456,7 @@ public class Controller : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost(nameof(ResetPasswordRequest))]
|
[HttpPost(nameof(ResetPasswordRequest))]
|
||||||
public ActionResult<IEnumerable<Object>> ResetPasswordRequest(String username)
|
public async Task<ActionResult<IEnumerable<Object>>> ResetPasswordRequest(String username)
|
||||||
{
|
{
|
||||||
var user = Db.GetUserByEmail(username);
|
var user = Db.GetUserByEmail(username);
|
||||||
|
|
||||||
|
@ -469,10 +464,11 @@ public class Controller : ControllerBase
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
|
|
||||||
var session = new Session(user.HidePassword().HideParentIfUserHasNoAccessToParent(user));
|
var session = new Session(user.HidePassword().HideParentIfUserHasNoAccessToParent(user));
|
||||||
var res = Db.Create(session);
|
var success = Db.Create(session);
|
||||||
return res && Db.SendPasswordResetEmail(user, session.Token)
|
|
||||||
? Ok()
|
return success && await Db.SendPasswordResetEmail(user, session.Token)
|
||||||
: Unauthorized();
|
? Ok()
|
||||||
|
: Unauthorized();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -486,7 +482,7 @@ public class Controller : ControllerBase
|
||||||
|
|
||||||
Db.DeleteUserPassword(user);
|
Db.DeleteUserPassword(user);
|
||||||
|
|
||||||
return Redirect($"https://monitor.innov.energy/?username={user.Email}&reset=true");
|
return Redirect($"https://monitor.innov.energy/?username={user.Email}&reset=true"); // TODO: move to settings file
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
using System.Net.Mail;
|
|
||||||
using System.Security.Cryptography;
|
using System.Security.Cryptography;
|
||||||
|
using System.Web;
|
||||||
using InnovEnergy.App.Backend.Database;
|
using InnovEnergy.App.Backend.Database;
|
||||||
|
using InnovEnergy.Lib.Mailer;
|
||||||
using InnovEnergy.Lib.Utils;
|
using InnovEnergy.Lib.Utils;
|
||||||
using Convert = System.Convert;
|
using Convert = System.Convert;
|
||||||
using static System.Text.Encoding;
|
using static System.Text.Encoding;
|
||||||
|
@ -91,13 +92,14 @@ public static class UserMethods
|
||||||
.Skip(1); // skip self
|
.Skip(1); // skip self
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Boolean VerifyPassword(this User user, String password)
|
public static Boolean VerifyPassword(this User user, String? password)
|
||||||
{
|
{
|
||||||
return Db.GetUserByEmail(user.Email)?.Password == user.SaltAndHashPassword(password);
|
return password is not null
|
||||||
|
&& Db.GetUserByEmail(user.Email)?.Password == user.SaltAndHashPassword(password);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String? SaltAndHashPassword(this User user, String password)
|
public static String SaltAndHashPassword(this User user, String password)
|
||||||
{
|
{
|
||||||
var dataToHash = $"{password}{user.Salt()}";
|
var dataToHash = $"{password}{user.Salt()}";
|
||||||
|
|
||||||
|
@ -150,8 +152,10 @@ public static class UserMethods
|
||||||
if (installation is null)
|
if (installation is null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
return user.HasDirectAccessTo(installation) ||
|
return user.HasDirectAccessTo(installation)
|
||||||
installation.Ancestors().Any(user.HasDirectAccessTo);
|
|| installation
|
||||||
|
.Ancestors()
|
||||||
|
.Any(user.HasDirectAccessTo);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Boolean HasAccessTo(this User user, User? other)
|
public static Boolean HasAccessTo(this User user, User? other)
|
||||||
|
@ -159,10 +163,8 @@ public static class UserMethods
|
||||||
if (other is null)
|
if (other is null)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (other.Id == user.Id)
|
return other.Id == user.Id
|
||||||
return true;
|
|| other
|
||||||
|
|
||||||
return other
|
|
||||||
.Ancestors()
|
.Ancestors()
|
||||||
.Contains(user);
|
.Contains(user);
|
||||||
}
|
}
|
||||||
|
@ -172,9 +174,9 @@ public static class UserMethods
|
||||||
return other?.Type switch
|
return other?.Type switch
|
||||||
{
|
{
|
||||||
"installation" => user.HasAccessTo((Installation)other),
|
"installation" => user.HasAccessTo((Installation)other),
|
||||||
"user" => user.HasAccessTo((User)other),
|
"user" => user.HasAccessTo((User)other),
|
||||||
"folder" => user.HasAccessTo((Folder)other),
|
"folder" => user.HasAccessTo((Folder)other),
|
||||||
_ => false
|
_ => false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -183,14 +185,12 @@ public static class UserMethods
|
||||||
return other?.Type switch
|
return other?.Type switch
|
||||||
{
|
{
|
||||||
"Installation" => user.HasAccessTo(Db.GetFolderById(other.ParentId)),
|
"Installation" => user.HasAccessTo(Db.GetFolderById(other.ParentId)),
|
||||||
"User" => user.HasAccessTo(Db.GetUserById(other.ParentId)),
|
"User" => user.HasAccessTo(Db.GetUserById(other.ParentId)),
|
||||||
"Folder" => user.HasAccessTo(Db.GetFolderById(other.ParentId)),
|
"Folder" => user.HasAccessTo(Db.GetFolderById(other.ParentId)),
|
||||||
_ => false
|
_ => false
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
private static String Salt(this User user)
|
private static String Salt(this User user)
|
||||||
{
|
{
|
||||||
// + id => salt unique per user
|
// + id => salt unique per user
|
||||||
|
@ -211,4 +211,35 @@ public static class UserMethods
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Task SendEmail(this User user, String subject, String body)
|
||||||
|
{
|
||||||
|
return Mailer.Send(user.Name, user.Email, subject, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task SendPasswordResetEmail(this User user, String token)
|
||||||
|
{
|
||||||
|
const String subject = "Reset the password of your InnovEnergy-Account";
|
||||||
|
const String resetLink = "https://monitor.innov.energy/api/ResetPassword"; // TODO: move to settings file
|
||||||
|
var encodedToken = HttpUtility.UrlEncode(token);
|
||||||
|
|
||||||
|
var body = $"Dear {user.Name}\n" +
|
||||||
|
$"To reset your password " +
|
||||||
|
$"please open this link:{resetLink}?token={encodedToken}";
|
||||||
|
|
||||||
|
return user.SendEmail(subject, body);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Task SendNewUserWelcomeMessage(this User user)
|
||||||
|
{
|
||||||
|
const String subject = "Your new InnovEnergy-Account";
|
||||||
|
|
||||||
|
var resetLink = $"https://monitor.innov.energy/?username={user.Email}"; // TODO: move to settings file
|
||||||
|
|
||||||
|
var body = $"Dear {user.Name}\n" +
|
||||||
|
$"To set your password and log in to your " +
|
||||||
|
$"Innovenergy-Account open this link:{resetLink}";
|
||||||
|
|
||||||
|
return user.SendEmail(subject, body);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -4,6 +4,8 @@ namespace InnovEnergy.App.Backend.DataTypes;
|
||||||
|
|
||||||
public class User : TreeNode
|
public class User : TreeNode
|
||||||
{
|
{
|
||||||
|
|
||||||
|
[Unique]
|
||||||
public String Email { get; set; } = null!;
|
public String Email { get; set; } = null!;
|
||||||
public Boolean HasWriteAccess { get; set; } = false;
|
public Boolean HasWriteAccess { get; set; } = false;
|
||||||
public Boolean MustResetPassword { get; set; } = false;
|
public Boolean MustResetPassword { get; set; } = false;
|
||||||
|
|
|
@ -1,12 +1,11 @@
|
||||||
using System.Reactive.Concurrency;
|
using System.Reactive.Concurrency;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using CliWrap;
|
|
||||||
using CliWrap.Buffered;
|
|
||||||
using InnovEnergy.App.Backend.DataTypes;
|
using InnovEnergy.App.Backend.DataTypes;
|
||||||
using InnovEnergy.App.Backend.DataTypes.Methods;
|
using InnovEnergy.App.Backend.DataTypes.Methods;
|
||||||
using InnovEnergy.App.Backend.Relations;
|
using InnovEnergy.App.Backend.Relations;
|
||||||
using InnovEnergy.Lib.S3Utils;
|
using InnovEnergy.Lib.S3Utils;
|
||||||
using InnovEnergy.Lib.S3Utils.DataTypes;
|
using InnovEnergy.Lib.S3Utils.DataTypes;
|
||||||
|
using InnovEnergy.Lib.Utils;
|
||||||
using SQLite;
|
using SQLite;
|
||||||
using SQLiteConnection = SQLite.SQLiteConnection;
|
using SQLiteConnection = SQLite.SQLiteConnection;
|
||||||
|
|
||||||
|
@ -39,34 +38,14 @@ public static partial class Db
|
||||||
memoryConnection.CreateTable<Session>();
|
memoryConnection.CreateTable<Session>();
|
||||||
memoryConnection.CreateTable<OrderNumber2Installation>();
|
memoryConnection.CreateTable<OrderNumber2Installation>();
|
||||||
|
|
||||||
foreach (var obj in fileConnection.Table<Session>())
|
fileConnection.Table<Session> ().ForEach(memoryConnection.Insert);
|
||||||
{
|
fileConnection.Table<Folder> ().ForEach(memoryConnection.Insert);
|
||||||
memoryConnection.Insert(obj);
|
fileConnection.Table<Installation> ().ForEach(memoryConnection.Insert);
|
||||||
}
|
fileConnection.Table<User> ().ForEach(memoryConnection.Insert);
|
||||||
foreach (var obj in fileConnection.Table<Folder>())
|
fileConnection.Table<FolderAccess> ().ForEach(memoryConnection.Insert);
|
||||||
{
|
fileConnection.Table<InstallationAccess> ().ForEach(memoryConnection.Insert);
|
||||||
memoryConnection.Insert(obj);
|
fileConnection.Table<OrderNumber2Installation>().ForEach(memoryConnection.Insert);
|
||||||
}
|
|
||||||
foreach (var obj in fileConnection.Table<Installation>())
|
|
||||||
{
|
|
||||||
memoryConnection.Insert(obj);
|
|
||||||
}
|
|
||||||
foreach (var obj in fileConnection.Table<User>())
|
|
||||||
{
|
|
||||||
memoryConnection.Insert(obj);
|
|
||||||
}
|
|
||||||
foreach (var obj in fileConnection.Table<FolderAccess>())
|
|
||||||
{
|
|
||||||
memoryConnection.Insert(obj);
|
|
||||||
}
|
|
||||||
foreach (var obj in fileConnection.Table<InstallationAccess>())
|
|
||||||
{
|
|
||||||
memoryConnection.Insert(obj);
|
|
||||||
}
|
|
||||||
foreach (var obj in fileConnection.Table<OrderNumber2Installation>())
|
|
||||||
{
|
|
||||||
memoryConnection.Insert(obj);
|
|
||||||
}
|
|
||||||
return memoryConnection;
|
return memoryConnection;
|
||||||
}))();
|
}))();
|
||||||
|
|
||||||
|
@ -152,34 +131,53 @@ public static partial class Db
|
||||||
private static async Task UpdateS3Urls()
|
private static async Task UpdateS3Urls()
|
||||||
{
|
{
|
||||||
var regions = Installations
|
var regions = Installations
|
||||||
.Select(i => i.S3Region)
|
.Select(i => i.S3Region)
|
||||||
.Distinct().ToList();
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
|
|
||||||
const String provider = "exo.io";
|
const String provider = "exo.io";
|
||||||
|
|
||||||
foreach (var region in regions)
|
foreach (var region in regions)
|
||||||
{
|
{
|
||||||
var bucketList = await new S3Region($"https://{region}.{provider}", ExoCmd.S3Creds!).ListAllBuckets();
|
var s3Region = new S3Region($"https://{region}.{provider}", ExoCmd.S3Creds!);
|
||||||
|
var bucketList = await s3Region.ListAllBuckets();
|
||||||
|
|
||||||
foreach (var bucket in bucketList.Buckets)
|
var installations = from bucket in bucketList.Buckets
|
||||||
|
from installation in Installations
|
||||||
|
where installation.BucketName() == bucket.BucketName
|
||||||
|
select installation;
|
||||||
|
|
||||||
|
foreach (var installation in installations)
|
||||||
{
|
{
|
||||||
foreach (var installation in Installations)
|
await installation.RenewS3Credentials();
|
||||||
{
|
|
||||||
if (installation.BucketName() == bucket.BucketName)
|
|
||||||
{
|
|
||||||
await installation.RenewS3Credentials();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Boolean SendPasswordResetEmail(User user, String sessionToken)
|
public static async Task<Boolean> SendPasswordResetEmail(User user, String sessionToken)
|
||||||
{
|
{
|
||||||
return Email.Email.SendPasswordResetMessage(user, sessionToken);
|
try
|
||||||
|
{
|
||||||
|
await user.SendPasswordResetEmail(sessionToken);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Boolean SendNewUserEmail(User user)
|
public static async Task<Boolean> SendNewUserEmail(User user)
|
||||||
{
|
{
|
||||||
return Email.Email.SendNewUserMessage(user);
|
try
|
||||||
|
{
|
||||||
|
await user.SendNewUserWelcomeMessage();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Boolean DeleteUserPassword(User user)
|
public static Boolean DeleteUserPassword(User user)
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
using InnovEnergy.App.Backend.DataTypes;
|
using InnovEnergy.App.Backend.DataTypes;
|
||||||
using InnovEnergy.App.Backend.DataTypes.Methods;
|
|
||||||
using InnovEnergy.App.Backend.Relations;
|
using InnovEnergy.App.Backend.Relations;
|
||||||
|
|
||||||
|
|
||||||
|
@ -26,10 +25,10 @@ public static partial class Db
|
||||||
.FirstOrDefault(u => u.Id == id);
|
.FirstOrDefault(u => u.Id == id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static User? GetUserByEmail(String userName)
|
public static User? GetUserByEmail(String email)
|
||||||
{
|
{
|
||||||
return Users
|
return Users
|
||||||
.FirstOrDefault(u => u.Email == userName);
|
.FirstOrDefault(u => u.Email == email);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Session? GetSession(String token)
|
public static Session? GetSession(String token)
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using InnovEnergy.App.Backend.DataTypes;
|
|
||||||
using InnovEnergy.Lib.Mailer;
|
|
||||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
|
||||||
|
|
||||||
namespace InnovEnergy.App.Backend.Email;
|
|
||||||
public static class Email
|
|
||||||
{
|
|
||||||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
|
|
||||||
public static Boolean SendVerificationMessage(User emailRecipientUser)
|
|
||||||
{
|
|
||||||
var config = JsonSerializer.Deserialize<SmtpConfig>(File.OpenRead("./Resources/smtpConfig.json"))!;
|
|
||||||
var mailer = new Mailer();
|
|
||||||
|
|
||||||
mailer.From("InnovEnergy", "noreply@innov.energy");
|
|
||||||
mailer.To(emailRecipientUser.Name, emailRecipientUser.Email);
|
|
||||||
|
|
||||||
mailer.Subject("Create a new password for your Innovenergy-Account");
|
|
||||||
mailer.Body("Dear " + emailRecipientUser.Name +
|
|
||||||
"\n Please create a new password for your Innovenergy-account." +
|
|
||||||
"\n To do this just login at https://HEEEEELP");
|
|
||||||
|
|
||||||
return mailer.SendEmailUsingSmtpConfig(config);
|
|
||||||
}
|
|
||||||
|
|
||||||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
|
|
||||||
public static Boolean SendPasswordResetMessage (User emailRecipientUser, String token)
|
|
||||||
{
|
|
||||||
var config = JsonSerializer.Deserialize<SmtpConfig>(File.OpenRead("./Resources/smtpConfig.json"))!;
|
|
||||||
|
|
||||||
//todo am I right?
|
|
||||||
const String resetLink = "https://monitor.innov.energy/api/ResetPassword";
|
|
||||||
var mailer = new Mailer();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
|
|
||||||
mailer.From("InnovEnergy", "noreply@innov.energy");
|
|
||||||
mailer.To(emailRecipientUser.Name, emailRecipientUser.Email);
|
|
||||||
|
|
||||||
mailer.Subject("Reset the password of your Innovenergy-Account");
|
|
||||||
mailer.Body("Dear " + emailRecipientUser.Name
|
|
||||||
+ "\n To reset your password open this link:"
|
|
||||||
+ resetLink + "?token="
|
|
||||||
+ token);
|
|
||||||
|
|
||||||
return mailer.SendEmailUsingSmtpConfig(config);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
|
|
||||||
public static Boolean SendNewUserMessage (User emailRecipientUser)
|
|
||||||
{
|
|
||||||
var config = JsonSerializer.Deserialize<SmtpConfig>(File.OpenRead("./Resources/smtpConfig.json"))!;
|
|
||||||
|
|
||||||
//todo am I right?
|
|
||||||
var resetLink = $"https://monitor.innov.energy/?username={emailRecipientUser.Email}";
|
|
||||||
var mailer = new Mailer();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
|
|
||||||
mailer.From("InnovEnergy", "noreply@innov.energy");
|
|
||||||
mailer.To(emailRecipientUser.Name, emailRecipientUser.Email);
|
|
||||||
|
|
||||||
mailer.Subject("Your new Innovenergy-Account");
|
|
||||||
mailer.Body("Dear " + emailRecipientUser.Name
|
|
||||||
+ "\n To set your password and log in to your Innovenergy-Account open this link:"
|
|
||||||
+ resetLink);
|
|
||||||
|
|
||||||
return mailer.SendEmailUsingSmtpConfig(config);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
{
|
||||||
|
"SmtpServerUrl" : "mail.agenturserver.de",
|
||||||
|
"SmtpUsername" : "p518526p69",
|
||||||
|
"SmtpPassword" : "i;b*xqm4iB5uhl",
|
||||||
|
"SmtpPort" : 587,
|
||||||
|
"SenderName" : "InnovEnergy",
|
||||||
|
"SenderAddress" : "noreply@innov.energy"
|
||||||
|
}
|
|
@ -3,7 +3,6 @@ using InnovEnergy.App.Backend.Database;
|
||||||
using Microsoft.AspNetCore.HttpOverrides;
|
using Microsoft.AspNetCore.HttpOverrides;
|
||||||
using Microsoft.AspNetCore.Mvc;
|
using Microsoft.AspNetCore.Mvc;
|
||||||
using Microsoft.OpenApi.Models;
|
using Microsoft.OpenApi.Models;
|
||||||
using System.Net;
|
|
||||||
using InnovEnergy.Lib.Utils;
|
using InnovEnergy.Lib.Utils;
|
||||||
|
|
||||||
namespace InnovEnergy.App.Backend;
|
namespace InnovEnergy.App.Backend;
|
||||||
|
@ -20,7 +19,7 @@ public static class Program
|
||||||
builder.Services.AddProblemDetails(setup =>
|
builder.Services.AddProblemDetails(setup =>
|
||||||
{
|
{
|
||||||
//This includes the stacktrace in Development Env
|
//This includes the stacktrace in Development Env
|
||||||
setup.IncludeExceptionDetails = (ctx, env) => builder.Environment.IsDevelopment() || builder.Environment.IsStaging();
|
setup.IncludeExceptionDetails = (_, _) => builder.Environment.IsDevelopment() || builder.Environment.IsStaging();
|
||||||
|
|
||||||
//This handles our Exceptions
|
//This handles our Exceptions
|
||||||
setup.Map<Exceptions>(exception => new ProblemDetails
|
setup.Map<Exceptions>(exception => new ProblemDetails
|
||||||
|
@ -43,8 +42,6 @@ public static class Program
|
||||||
|
|
||||||
app.Use(async (context, next) =>
|
app.Use(async (context, next) =>
|
||||||
{
|
{
|
||||||
var x = 2;
|
|
||||||
|
|
||||||
context.Request.WriteLine();
|
context.Request.WriteLine();
|
||||||
|
|
||||||
await next(context);
|
await next(context);
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
"dotnetRunMessages": true,
|
"dotnetRunMessages": true,
|
||||||
|
|
||||||
"launchUrl": "swagger",
|
"launchUrl": "swagger",
|
||||||
"applicationUrl": "https://localhost:7087",
|
"applicationUrl": "http://localhost:7087",
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
"HOME":"~/backend"
|
"HOME":"~/backend"
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
|
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="AWSSDK.S3" Version="3.7.203.12" />
|
<PackageReference Include="AWSSDK.S3" Version="3.7.205.17" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -1,55 +1,41 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
using System.Text.Json;
|
||||||
using MailKit.Net.Smtp;
|
using MailKit.Net.Smtp;
|
||||||
using MimeKit;
|
using MimeKit;
|
||||||
|
|
||||||
namespace InnovEnergy.Lib.Mailer;
|
namespace InnovEnergy.Lib.Mailer;
|
||||||
|
|
||||||
|
|
||||||
public class Mailer
|
public static class Mailer
|
||||||
{
|
{
|
||||||
private static MimeMessage Email = new();
|
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
|
||||||
|
public static async Task Send(String recipientName, String recipientEmailAddress, String subject, String body)
|
||||||
public MimeMessage To(String name, String emailAddress)
|
|
||||||
{
|
{
|
||||||
Email.To.Add(new MailboxAddress(name, emailAddress));
|
var config = await ReadMailerConfig();
|
||||||
return Email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MimeMessage From(String name, String emailAddress)
|
var from = new MailboxAddress(config!.SenderName, config.SenderAddress);
|
||||||
{
|
var to = new MailboxAddress(recipientName, recipientEmailAddress);
|
||||||
Email.From.Add(new MailboxAddress(name, emailAddress));
|
|
||||||
return Email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MimeMessage Subject(String subjectText)
|
var msg = new MimeMessage
|
||||||
{
|
|
||||||
Email.Subject = subjectText;
|
|
||||||
return Email;
|
|
||||||
}
|
|
||||||
|
|
||||||
public MimeMessage Body(String bodyText)
|
|
||||||
{
|
|
||||||
Email.Body = new TextPart(MimeKit.Text.TextFormat.Plain)
|
|
||||||
{
|
{
|
||||||
Text = bodyText
|
From = { from },
|
||||||
|
To = { to },
|
||||||
|
Subject = subject,
|
||||||
|
Body = new TextPart { Text = body }
|
||||||
};
|
};
|
||||||
return Email;
|
|
||||||
|
using var smtp = new SmtpClient();
|
||||||
|
|
||||||
|
await smtp.ConnectAsync(config.SmtpServerUrl, config.SmtpPort, false);
|
||||||
|
await smtp.AuthenticateAsync(config.SmtpUsername, config.SmtpPassword);
|
||||||
|
await smtp.SendAsync(msg);
|
||||||
|
await smtp.DisconnectAsync(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Boolean SendEmailUsingSmtpConfig(SmtpConfig config)
|
[RequiresUnreferencedCode("Calls System.Text.Json.JsonSerializer.DeserializeAsync<TValue>(Stream, JsonSerializerOptions, CancellationToken)")]
|
||||||
|
private static async Task<MailerConfig?> ReadMailerConfig()
|
||||||
{
|
{
|
||||||
try{
|
await using var fileStream = File.OpenRead(MailerConfig.DefaultFile);
|
||||||
using var smtp = new SmtpClient();
|
return await JsonSerializer.DeserializeAsync<MailerConfig>(fileStream);
|
||||||
smtp.Connect(config.Url, config.Port, false);
|
|
||||||
|
|
||||||
smtp.Authenticate(config.Username, config.Password);
|
|
||||||
|
|
||||||
smtp.Send(Email);
|
|
||||||
smtp.Disconnect(true);
|
|
||||||
}
|
|
||||||
catch (Exception)
|
|
||||||
{
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,4 +7,16 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="MailKit" Version="4.2.0" />
|
<PackageReference Include="MailKit" Version="4.2.0" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<Content Update="MailerConfig.json">
|
||||||
|
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
|
||||||
|
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
|
||||||
|
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
|
||||||
|
</Content>
|
||||||
|
</ItemGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<ProjectReference Include="../Utils/Utils.csproj" />
|
||||||
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
||||||
|
|
|
@ -0,0 +1,17 @@
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
namespace InnovEnergy.Lib.Mailer;
|
||||||
|
|
||||||
|
|
||||||
|
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||||
|
public class MailerConfig
|
||||||
|
{
|
||||||
|
public required String SmtpServerUrl { get; init; }
|
||||||
|
public required String SmtpUsername { get; init; }
|
||||||
|
public required String SmtpPassword { get; init; }
|
||||||
|
public UInt16 SmtpPort { get; init; } = 587;
|
||||||
|
|
||||||
|
public required String SenderName { get; init; }
|
||||||
|
public required String SenderAddress { get; init; }
|
||||||
|
|
||||||
|
public const String DefaultFile = $"{nameof(MailerConfig)}.json";
|
||||||
|
}
|
|
@ -0,0 +1,9 @@
|
||||||
|
{
|
||||||
|
"SmtpServerUrl" : "mail.agenturserver.de",
|
||||||
|
"SmtpUsername" : "p518526p69",
|
||||||
|
"SmtpPassword" : "i;b*xqm4iB5uhl",
|
||||||
|
"SmtpPort" : 587,
|
||||||
|
"SenderName" : "InnovEnergy",
|
||||||
|
"SenderAddress" : "noreply@innov.energy"
|
||||||
|
}
|
||||||
|
|
|
@ -1,12 +0,0 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
namespace InnovEnergy.Lib.Mailer;
|
|
||||||
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
|
||||||
public class SmtpConfig
|
|
||||||
{
|
|
||||||
public String Url { get; init; } = null!;
|
|
||||||
public String Username { get; init; } = null!;
|
|
||||||
public String Password { get; init; } = null!;
|
|
||||||
public Int32 Port { get; init; } = 587;
|
|
||||||
}
|
|
|
@ -42,11 +42,11 @@ public static class S3
|
||||||
.Select(o => new S3Url(o.Key, bucket));
|
.Select(o => new S3Url(o.Key, bucket));
|
||||||
}
|
}
|
||||||
|
|
||||||
public static async Task<ListBucketsResponse> ListAllBuckets(this S3Region region)
|
public static Task<ListBucketsResponse> ListAllBuckets(this S3Region region)
|
||||||
{
|
{
|
||||||
return await region
|
return region
|
||||||
.GetS3Client()
|
.GetS3Client()
|
||||||
.ListBucketsAsync();
|
.ListBucketsAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Task<Boolean> PutObject(this S3Url path, String data, Encoding encoding) => path.PutObject(encoding.GetBytes(data));
|
public static Task<Boolean> PutObject(this S3Url path, String data, Encoding encoding) => path.PutObject(encoding.GetBytes(data));
|
||||||
|
|
|
@ -21,7 +21,7 @@
|
||||||
"chart.js": "^4.4.0",
|
"chart.js": "^4.4.0",
|
||||||
"clsx": "1.1.1",
|
"clsx": "1.1.1",
|
||||||
"cytoscape": "^3.26.0",
|
"cytoscape": "^3.26.0",
|
||||||
"date-fns": "2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"history": "5.3.0",
|
"history": "5.3.0",
|
||||||
"linq-to-typescript": "^11.0.0",
|
"linq-to-typescript": "^11.0.0",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
|
@ -38,9 +38,11 @@
|
||||||
"react-icons": "^4.11.0",
|
"react-icons": "^4.11.0",
|
||||||
"react-icons-converter": "^1.1.4",
|
"react-icons-converter": "^1.1.4",
|
||||||
"react-intl": "^6.4.4",
|
"react-intl": "^6.4.4",
|
||||||
|
"react-redux": "^8.1.3",
|
||||||
"react-router": "6.3.0",
|
"react-router": "6.3.0",
|
||||||
"react-router-dom": "6.3.0",
|
"react-router-dom": "6.3.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
|
"redux": "^4.2.1",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"simplytyped": "^3.3.0",
|
"simplytyped": "^3.3.0",
|
||||||
"stylis": "4.1.1",
|
"stylis": "4.1.1",
|
||||||
|
@ -4662,6 +4664,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
|
||||||
"integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
|
"integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/use-sync-external-store": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
|
||||||
|
},
|
||||||
"node_modules/@types/ws": {
|
"node_modules/@types/ws": {
|
||||||
"version": "8.5.3",
|
"version": "8.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||||
|
@ -15155,6 +15162,49 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||||
},
|
},
|
||||||
|
"node_modules/react-redux": {
|
||||||
|
"version": "8.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz",
|
||||||
|
"integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.12.1",
|
||||||
|
"@types/hoist-non-react-statics": "^3.3.1",
|
||||||
|
"@types/use-sync-external-store": "^0.0.3",
|
||||||
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
|
"react-is": "^18.0.0",
|
||||||
|
"use-sync-external-store": "^1.0.0"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@types/react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"@types/react-dom": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-dom": "^16.8 || ^17.0 || ^18.0",
|
||||||
|
"react-native": ">=0.59",
|
||||||
|
"redux": "^4 || ^5.0.0-beta.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"@types/react": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"@types/react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-dom": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"react-native": {
|
||||||
|
"optional": true
|
||||||
|
},
|
||||||
|
"redux": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/react-redux/node_modules/react-is": {
|
||||||
|
"version": "18.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||||
|
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
|
||||||
|
},
|
||||||
"node_modules/react-refresh": {
|
"node_modules/react-refresh": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
||||||
|
@ -15334,6 +15384,14 @@
|
||||||
"node": "*"
|
"node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/redux": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||||
|
"dependencies": {
|
||||||
|
"@babel/runtime": "^7.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/regenerate": {
|
"node_modules/regenerate": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||||
|
@ -17185,6 +17243,14 @@
|
||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/use-sync-external-store": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
|
||||||
|
"peerDependencies": {
|
||||||
|
"react": "^16.8.0 || ^17.0.0 || ^18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/util-deprecate": {
|
"node_modules/util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
@ -21400,6 +21466,11 @@
|
||||||
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.2.tgz",
|
||||||
"integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
|
"integrity": "sha512-F5DIZ36YVLE+PN+Zwws4kJogq47hNgX3Nx6WyDJ3kcplxyke3XIzB8uK5n/Lpm1HBsbGzd6nmGehL8cPekP+Tg=="
|
||||||
},
|
},
|
||||||
|
"@types/use-sync-external-store": {
|
||||||
|
"version": "0.0.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/use-sync-external-store/-/use-sync-external-store-0.0.3.tgz",
|
||||||
|
"integrity": "sha512-EwmlvuaxPNej9+T4v5AuBPJa2x2UOJVdjCtDHgcDqitUeOtjnJKJ+apYjVcAoBEMjKW1VVFGZLUb5+qqa09XFA=="
|
||||||
|
},
|
||||||
"@types/ws": {
|
"@types/ws": {
|
||||||
"version": "8.5.3",
|
"version": "8.5.3",
|
||||||
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
"resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.5.3.tgz",
|
||||||
|
@ -28886,6 +28957,26 @@
|
||||||
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz",
|
||||||
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
"integrity": "sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w=="
|
||||||
},
|
},
|
||||||
|
"react-redux": {
|
||||||
|
"version": "8.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-redux/-/react-redux-8.1.3.tgz",
|
||||||
|
"integrity": "sha512-n0ZrutD7DaX/j9VscF+uTALI3oUPa/pO4Z3soOBIjuRn/FzVu6aehhysxZCLi6y7duMf52WNZGMl7CtuK5EnRw==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.12.1",
|
||||||
|
"@types/hoist-non-react-statics": "^3.3.1",
|
||||||
|
"@types/use-sync-external-store": "^0.0.3",
|
||||||
|
"hoist-non-react-statics": "^3.3.2",
|
||||||
|
"react-is": "^18.0.0",
|
||||||
|
"use-sync-external-store": "^1.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"react-is": {
|
||||||
|
"version": "18.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
|
||||||
|
"integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w=="
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
"react-refresh": {
|
"react-refresh": {
|
||||||
"version": "0.11.0",
|
"version": "0.11.0",
|
||||||
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
"resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.11.0.tgz",
|
||||||
|
@ -29020,6 +29111,14 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"redux": {
|
||||||
|
"version": "4.2.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/redux/-/redux-4.2.1.tgz",
|
||||||
|
"integrity": "sha512-LAUYz4lc+Do8/g7aeRa8JkyDErK6ekstQaqWQrNRW//MY1TvCEpMtpTWvlQ+FPbWCx+Xixu/6SHt5N0HR+SB4w==",
|
||||||
|
"requires": {
|
||||||
|
"@babel/runtime": "^7.9.2"
|
||||||
|
}
|
||||||
|
},
|
||||||
"regenerate": {
|
"regenerate": {
|
||||||
"version": "1.4.2",
|
"version": "1.4.2",
|
||||||
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
"resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz",
|
||||||
|
@ -30399,6 +30498,12 @@
|
||||||
"punycode": "^2.1.0"
|
"punycode": "^2.1.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"use-sync-external-store": {
|
||||||
|
"version": "1.2.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz",
|
||||||
|
"integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==",
|
||||||
|
"requires": {}
|
||||||
|
},
|
||||||
"util-deprecate": {
|
"util-deprecate": {
|
||||||
"version": "1.0.2",
|
"version": "1.0.2",
|
||||||
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
|
||||||
|
|
|
@ -17,7 +17,7 @@
|
||||||
"chart.js": "^4.4.0",
|
"chart.js": "^4.4.0",
|
||||||
"clsx": "1.1.1",
|
"clsx": "1.1.1",
|
||||||
"cytoscape": "^3.26.0",
|
"cytoscape": "^3.26.0",
|
||||||
"date-fns": "2.28.0",
|
"date-fns": "^2.28.0",
|
||||||
"history": "5.3.0",
|
"history": "5.3.0",
|
||||||
"linq-to-typescript": "^11.0.0",
|
"linq-to-typescript": "^11.0.0",
|
||||||
"nprogress": "0.2.0",
|
"nprogress": "0.2.0",
|
||||||
|
@ -34,9 +34,11 @@
|
||||||
"react-icons": "^4.11.0",
|
"react-icons": "^4.11.0",
|
||||||
"react-icons-converter": "^1.1.4",
|
"react-icons-converter": "^1.1.4",
|
||||||
"react-intl": "^6.4.4",
|
"react-intl": "^6.4.4",
|
||||||
|
"react-redux": "^8.1.3",
|
||||||
"react-router": "6.3.0",
|
"react-router": "6.3.0",
|
||||||
"react-router-dom": "6.3.0",
|
"react-router-dom": "6.3.0",
|
||||||
"react-scripts": "5.0.1",
|
"react-scripts": "5.0.1",
|
||||||
|
"redux": "^4.2.1",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"simplytyped": "^3.3.0",
|
"simplytyped": "^3.3.0",
|
||||||
"stylis": "4.1.1",
|
"stylis": "4.1.1",
|
||||||
|
|
|
@ -1,10 +1,9 @@
|
||||||
/* Disable text selection for the entire page */
|
/* Disable text selection for the entire page */
|
||||||
body {
|
.mainframe {
|
||||||
user-select: none;
|
user-select: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Style the selected text */
|
|
||||||
::selection {
|
.apexcharts-toolbar {
|
||||||
background-color: transparent; /* Set the background color to transparent */
|
display: none !important;
|
||||||
color: #000; /* Set the text color for selected text */
|
|
||||||
}
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
import { Navigate, Route, Routes } from 'react-router-dom';
|
import { Navigate, Route, Routes, useNavigate } from 'react-router-dom';
|
||||||
import { CssBaseline } from '@mui/material';
|
import { CssBaseline } from '@mui/material';
|
||||||
import ThemeProvider from './theme/ThemeProvider';
|
import ThemeProvider from './theme/ThemeProvider';
|
||||||
import React, { lazy, Suspense, useContext, useState } from 'react';
|
import React, { lazy, Suspense, useContext, useState } from 'react';
|
||||||
|
@ -9,15 +9,16 @@ import en from './lang/en.json';
|
||||||
import de from './lang/de.json';
|
import de from './lang/de.json';
|
||||||
import fr from './lang/fr.json';
|
import fr from './lang/fr.json';
|
||||||
import SuspenseLoader from './components/SuspenseLoader';
|
import SuspenseLoader from './components/SuspenseLoader';
|
||||||
import { RouteObject } from 'react-router';
|
|
||||||
import BaseLayout from './layouts/BaseLayout';
|
|
||||||
import SidebarLayout from './layouts/SidebarLayout';
|
import SidebarLayout from './layouts/SidebarLayout';
|
||||||
import { TokenContext } from './contexts/tokenContext';
|
import { TokenContext } from './contexts/tokenContext';
|
||||||
import ResetPassword from './components/ResetPassword';
|
import ResetPassword from './components/ResetPassword';
|
||||||
import ForgotPassword from './components/ForgotPassword';
|
|
||||||
import InstallationTabs from './content/dashboards/Installations/index';
|
import InstallationTabs from './content/dashboards/Installations/index';
|
||||||
import routes from 'src/Resources/routes.json';
|
import routes from 'src/Resources/routes.json';
|
||||||
import './App.css';
|
import './App.css';
|
||||||
|
import ForgotPassword from './components/ForgotPassword';
|
||||||
|
import { axiosConfigWithoutToken } from './Resources/axiosConfig';
|
||||||
|
import UsersContextProvider from './contexts/UsersContextProvider';
|
||||||
|
import InstallationsContextProvider from './contexts/InstallationsContextProvider';
|
||||||
|
|
||||||
function App() {
|
function App() {
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
|
@ -25,6 +26,9 @@ function App() {
|
||||||
const tokencontext = useContext(TokenContext);
|
const tokencontext = useContext(TokenContext);
|
||||||
const { token, setNewToken, removeToken } = tokencontext;
|
const { token, setNewToken, removeToken } = tokencontext;
|
||||||
const [forgotPassword, setForgotPassword] = useState(false);
|
const [forgotPassword, setForgotPassword] = useState(false);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const searchParams = new URLSearchParams(location.search);
|
||||||
|
const username = searchParams.get('username');
|
||||||
|
|
||||||
const [language, setLanguage] = useState('en');
|
const [language, setLanguage] = useState('en');
|
||||||
const getTranslations = () => {
|
const getTranslations = () => {
|
||||||
|
@ -62,9 +66,26 @@ function App() {
|
||||||
lazy(() => import('src/components/ResetPassword'))
|
lazy(() => import('src/components/ResetPassword'))
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const SetNewPassword = Loader(
|
||||||
|
lazy(() => import('src/components/SetNewPassword'))
|
||||||
|
);
|
||||||
|
|
||||||
const Login = Loader(lazy(() => import('src/components/login')));
|
const Login = Loader(lazy(() => import('src/components/login')));
|
||||||
const Users = Loader(lazy(() => import('src/content/dashboards/Users')));
|
const Users = Loader(lazy(() => import('src/content/dashboards/Users')));
|
||||||
|
|
||||||
|
const loginToResetPassword = () => {
|
||||||
|
axiosConfigWithoutToken
|
||||||
|
.post('/Login', null, { params: { username, password: '' } })
|
||||||
|
.then((response) => {
|
||||||
|
if (response.data && response.data.token) {
|
||||||
|
setNewToken(response.data.token);
|
||||||
|
setUser(response.data.user);
|
||||||
|
navigate(routes.installations);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {});
|
||||||
|
};
|
||||||
|
|
||||||
// Status
|
// Status
|
||||||
const Status404 = Loader(
|
const Status404 = Loader(
|
||||||
lazy(() => import('src/content/pages/Status/Status404'))
|
lazy(() => import('src/content/pages/Status/Status404'))
|
||||||
|
@ -79,61 +100,25 @@ function App() {
|
||||||
lazy(() => import('src/content/pages/Status/Maintenance'))
|
lazy(() => import('src/content/pages/Status/Maintenance'))
|
||||||
);
|
);
|
||||||
|
|
||||||
const routesArray: RouteObject[] = [
|
if (username) {
|
||||||
{
|
loginToResetPassword();
|
||||||
path: '',
|
|
||||||
element: <BaseLayout />,
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '/',
|
|
||||||
element: <Navigate to="installations/" replace />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'status',
|
|
||||||
children: [
|
|
||||||
{
|
|
||||||
path: '',
|
|
||||||
element: <Navigate to="404" replace />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '404',
|
|
||||||
element: <Status404 />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '500',
|
|
||||||
element: <Status500 />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'maintenance',
|
|
||||||
element: <StatusMaintenance />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: 'coming-soon',
|
|
||||||
element: <StatusComingSoon />
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '*',
|
|
||||||
element: <Status404 />
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
];
|
|
||||||
if (forgotPassword) {
|
|
||||||
return (
|
|
||||||
<ThemeProvider>
|
|
||||||
<CssBaseline />
|
|
||||||
<ForgotPassword resetPassword={resetPassword} />
|
|
||||||
</ThemeProvider>
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!token) {
|
if (!token) {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<Login onForgotPassword={onForgotPassword}></Login>
|
<Routes>
|
||||||
|
<Route
|
||||||
|
path={''}
|
||||||
|
element={<Navigate to={routes.login}></Navigate>}
|
||||||
|
></Route>
|
||||||
|
<Route path={routes.login} element={<Login></Login>}></Route>
|
||||||
|
<Route
|
||||||
|
path={routes.forgotPassword}
|
||||||
|
element={<ForgotPassword />}
|
||||||
|
></Route>
|
||||||
|
</Routes>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -142,7 +127,7 @@ function App() {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<ResetPassword></ResetPassword>
|
<SetNewPassword></SetNewPassword>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -156,18 +141,10 @@ function App() {
|
||||||
>
|
>
|
||||||
<CssBaseline />
|
<CssBaseline />
|
||||||
<Routes>
|
<Routes>
|
||||||
{routesArray.map((route, index) => (
|
<Route
|
||||||
<Route key={index} path={route.path} element={route.element}>
|
path={''}
|
||||||
{route.children &&
|
element={<Navigate to={routes.installations}></Navigate>}
|
||||||
route.children.map((childRoute, childIndex) => (
|
></Route>
|
||||||
<Route
|
|
||||||
key={childIndex}
|
|
||||||
path={childRoute.path}
|
|
||||||
element={childRoute.element}
|
|
||||||
/>
|
|
||||||
))}
|
|
||||||
</Route>
|
|
||||||
))}
|
|
||||||
<Route
|
<Route
|
||||||
path="/"
|
path="/"
|
||||||
element={
|
element={
|
||||||
|
@ -179,11 +156,18 @@ function App() {
|
||||||
>
|
>
|
||||||
<Route
|
<Route
|
||||||
path={routes.installations + '*'}
|
path={routes.installations + '*'}
|
||||||
element={<InstallationTabs />}
|
element={
|
||||||
|
<UsersContextProvider>
|
||||||
|
<InstallationsContextProvider>
|
||||||
|
<InstallationTabs />
|
||||||
|
</InstallationsContextProvider>
|
||||||
|
</UsersContextProvider>
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<Route path={routes.users + '*'} element={<Users />} />
|
<Route path={routes.users + '*'} element={<Users />} />
|
||||||
|
|
||||||
<Route path="ResetPassword" element={<ResetPassword />}></Route>
|
<Route path="ResetPassword" element={<ResetPassword />}></Route>
|
||||||
<Route path="Login" element={<Login />}></Route>
|
|
||||||
</Route>
|
</Route>
|
||||||
</Routes>
|
</Routes>
|
||||||
</IntlProvider>
|
</IntlProvider>
|
||||||
|
|
|
@ -37,7 +37,7 @@ export function findPower(value) {
|
||||||
value = Math.abs(value);
|
value = Math.abs(value);
|
||||||
|
|
||||||
// Calculate the power of 10 that's greater or equal to the absolute value
|
// Calculate the power of 10 that's greater or equal to the absolute value
|
||||||
let exponent = Math.floor(Math.log10(value));
|
const exponent = Math.floor(Math.log10(value));
|
||||||
|
|
||||||
// Compute the nearest power of 10
|
// Compute the nearest power of 10
|
||||||
const nearestPowerOf10 = Math.pow(10, exponent);
|
const nearestPowerOf10 = Math.pow(10, exponent);
|
||||||
|
|
|
@ -0,0 +1,88 @@
|
||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!-- Generator: Adobe Illustrator 28.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||||
|
<svg version="1.1" id="Ebene_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||||
|
viewBox="0 0 841.89 595.28" style="enable-background:new 0 0 841.89 595.28;" xml:space="preserve">
|
||||||
|
<style type="text/css">
|
||||||
|
.st0{fill:#646363;}
|
||||||
|
.st1{fill:#F39200;}
|
||||||
|
</style>
|
||||||
|
<g>
|
||||||
|
<path class="st0" d="M114.05,430.69c2.55,1.07,6.32,1.94,10.5,1.94c6.47,0,12.13-2.7,12.13-9.69c0-7.03-4.99-9.02-11.06-11.01
|
||||||
|
c-4.79-1.53-7.55-2.91-7.55-7.04c0-3.82,2.55-5.91,7.9-5.91c2.45,0,5.15,0.61,7.54,1.38l0.76-3.11c-2.24-0.87-5.71-1.53-8.56-1.53
|
||||||
|
c-6.98,0-11.42,3.21-11.42,9.23c0,5.71,3.52,8.21,9.53,10.14c5.45,1.68,9.07,3.11,9.07,7.85c0,4.38-3.26,6.42-8.46,6.42
|
||||||
|
c-3.98,0-7.29-1.07-9.68-1.94L114.05,430.69L114.05,430.69z M165.59,418.56c0-7.54-3.98-12.85-11.98-12.85
|
||||||
|
c-2.86,0-6.42,0.71-9.64,2.39v33.34h3.57v-10.14c1.38,0.66,3.26,1.27,5.86,1.27C161.76,432.58,165.59,426.72,165.59,418.56
|
||||||
|
L165.59,418.56z M147.54,428.09V410.3c1.53-0.87,3.57-1.48,5.91-1.48c5.91,0,8.41,4.38,8.41,9.79c0,6.98-2.96,10.91-8.82,10.91
|
||||||
|
C150.6,429.52,148.92,428.81,147.54,428.09L147.54,428.09z M192.96,427.84c-2.4,1.02-5.1,1.68-7.7,1.68
|
||||||
|
c-5.81,0-9.48-3.11-9.79-9.63h18.35c0.1-0.92,0.15-1.89,0.15-3.01c0-6.22-3.62-11.16-10.4-11.16c-7.34,0-11.77,5.61-11.77,13.41
|
||||||
|
c0,8.77,5.2,13.51,13.2,13.51c3.21,0,6.12-0.61,8.61-1.78L192.96,427.84L192.96,427.84z M183.58,408.77c4.89,0,6.88,3.72,6.88,7.6
|
||||||
|
c0,0.41,0,0.61-0.05,0.97h-14.88C175.99,412.24,178.79,408.77,183.58,408.77L183.58,408.77z M205.55,406.33h-3.57v25.69h3.57
|
||||||
|
V406.33L205.55,406.33z M203.77,400.98c1.53,0,2.4-1.07,2.4-2.45c0-1.33-0.87-2.35-2.4-2.35c-1.53,0-2.4,1.02-2.4,2.35
|
||||||
|
C201.37,399.9,202.24,400.98,203.77,400.98L203.77,400.98z M225.89,405.72c-7.85,0-12.29,5.51-12.29,13.66
|
||||||
|
c0,8.41,4.64,13.25,12.9,13.25c2.55,0,5.15-0.66,6.98-1.63l-0.71-2.96c-1.89,0.92-4.03,1.48-6.12,1.48
|
||||||
|
c-6.17,0-9.38-3.57-9.38-10.19c0-6.37,3.01-10.5,8.82-10.5c2.14,0,4.43,0.56,6.37,1.32l0.76-2.9
|
||||||
|
C231.4,406.38,228.8,405.72,225.89,405.72L225.89,405.72z M244.24,432.02v-21.26c2.04-1.22,4.38-1.94,7.14-1.94
|
||||||
|
c4.54,0,6.73,2.5,6.73,6.52v16.67h3.57V415.1c0-5.81-3.57-9.38-9.94-9.38c-3.21,0-5.45,0.82-7.49,1.89v-12.49l-3.57,0.25v36.65
|
||||||
|
H244.24L244.24,432.02z M290.42,427.84c-2.4,1.02-5.1,1.68-7.7,1.68c-5.81,0-9.48-3.11-9.79-9.63h18.35
|
||||||
|
c0.1-0.92,0.15-1.89,0.15-3.01c0-6.22-3.62-11.16-10.4-11.16c-7.34,0-11.78,5.61-11.78,13.41c0,8.77,5.2,13.51,13.2,13.51
|
||||||
|
c3.21,0,6.12-0.61,8.61-1.78L290.42,427.84L290.42,427.84z M281.05,408.77c4.89,0,6.88,3.72,6.88,7.6c0,0.41,0,0.61-0.05,0.97
|
||||||
|
h-14.88C273.45,412.24,276.25,408.77,281.05,408.77L281.05,408.77z M299.45,408.32v23.7h3.57v-21.46c1.78-1.02,4.23-1.73,7.34-1.73
|
||||||
|
c0.61,0,1.48,0.05,2.14,0.25l0.51-3.11c-0.87-0.15-1.73-0.25-2.85-0.25C306.02,405.72,302.15,406.79,299.45,408.32L299.45,408.32z
|
||||||
|
M322.44,432.02v-21.46c2.19-1.17,4.69-1.73,7.24-1.73c4.59,0,6.63,2.55,6.63,6.47v16.72h3.57V415.1c0-5.81-3.31-9.38-10.2-9.38
|
||||||
|
c-3.98,0-7.7,1.02-10.81,2.6v23.7H322.44L322.44,432.02z M402.21,432.02V415.1c0-5.56-3.11-9.38-9.89-9.38
|
||||||
|
c-3.62,0-6.63,1.17-9.12,2.65c-1.63-1.68-4.03-2.65-7.29-2.65c-3.82,0-7.39,1.02-10.45,2.6v23.7h3.57v-21.46
|
||||||
|
c2.09-1.17,4.43-1.73,6.78-1.73c4.64,0,6.27,2.75,6.27,6.47v16.72h3.52v-16.46c0-1.68-0.26-3.16-0.71-4.49
|
||||||
|
c1.89-1.27,4.64-2.24,7.34-2.24c4.59,0,6.42,2.75,6.42,6.47v16.72H402.21L402.21,432.02z M415.21,406.33h-3.57v25.69h3.57V406.33
|
||||||
|
L415.21,406.33z M413.42,400.98c1.53,0,2.4-1.07,2.4-2.45c0-1.33-0.87-2.35-2.4-2.35c-1.53,0-2.4,1.02-2.4,2.35
|
||||||
|
C411.03,399.9,411.89,400.98,413.42,400.98L413.42,400.98z M436.67,432.12l-0.26-3.01c-0.87,0.25-2.09,0.41-3.11,0.41
|
||||||
|
c-2.75,0-4.38-1.27-4.38-5.05v-15.14h6.93v-3.01h-6.93v-6.32l-3.57,0.26v6.07h-3.87v3.01h3.87v15.14c0,5.96,3.01,8.16,7.49,8.16
|
||||||
|
C434.32,432.63,435.49,432.43,436.67,432.12L436.67,432.12z M457.31,430.69c2.55,1.07,6.32,1.94,10.5,1.94
|
||||||
|
c6.47,0,12.13-2.7,12.13-9.69c0-7.03-5-9.02-11.06-11.01c-4.79-1.53-7.54-2.91-7.54-7.04c0-3.82,2.55-5.91,7.9-5.91
|
||||||
|
c2.45,0,5.15,0.61,7.54,1.38l0.76-3.11c-2.24-0.87-5.71-1.53-8.56-1.53c-6.98,0-11.42,3.21-11.42,9.23c0,5.71,3.52,8.21,9.53,10.14
|
||||||
|
c5.45,1.68,9.07,3.11,9.07,7.85c0,4.38-3.26,6.42-8.46,6.42c-3.98,0-7.29-1.07-9.69-1.94L457.31,430.69L457.31,430.69z
|
||||||
|
M495.28,429.78c-3.98,0-6.37-1.58-6.37-4.79c0-3.82,2.85-5.1,6.68-5.1c2.29,0,4.13,0.36,5.81,0.71v7.75
|
||||||
|
C500.28,429.06,498.34,429.78,495.28,429.78L495.28,429.78z M504.92,430.24V415.2c0-6.12-3.31-9.48-9.94-9.48
|
||||||
|
c-2.9,0-5.96,0.61-7.95,1.48l0.82,2.96c1.89-0.81,4.38-1.32,6.88-1.32c4.64,0,6.68,2.29,6.68,6.22V418
|
||||||
|
c-1.84-0.41-4.08-0.71-6.17-0.71c-5.45,0.05-9.89,2.29-9.89,7.8c0,4.43,3.31,7.55,9.63,7.55
|
||||||
|
C499.51,432.63,502.67,431.56,504.92,430.24L504.92,430.24z M517.66,395.11l-3.57,0.25v29.41c0,5.15,2.09,7.85,6.47,7.85
|
||||||
|
c0.71,0,1.63-0.15,2.29-0.31l-0.31-2.91c-0.36,0.05-0.87,0.15-1.48,0.15c-2.6,0-3.41-1.63-3.41-4.84V395.11L517.66,395.11z
|
||||||
|
M527.4,406.33v3.01h14.53c-3.62,6.47-9.18,13.41-15.39,20.14v2.55h20.14v-3.01h-15.65c6.07-6.63,11.01-13.15,14.89-20.14v-2.55
|
||||||
|
H527.4L527.4,406.33z M558.08,396.34h-4.03v12.13c0,3.31,0.2,7.39,0.61,11.98h2.8c0.41-4.64,0.61-8.61,0.61-11.93V396.34
|
||||||
|
L558.08,396.34z M558.44,430.03c0-1.33-0.87-2.29-2.29-2.29c-1.53,0-2.4,0.97-2.4,2.29s0.87,2.35,2.4,2.35
|
||||||
|
C557.57,432.38,558.44,431.36,558.44,430.03L558.44,430.03z"/>
|
||||||
|
<path class="st0" d="M305.72,269.82v-65.28c5.44-2.72,12-4.16,18.72-4.16c12.16,0,18.08,6.72,18.08,18.4v51.04h16.16v-52.16
|
||||||
|
c0-19.52-11.84-30.88-34.24-30.88c-12.32,0-24.96,2.88-34.88,7.52v75.52H305.72L305.72,269.82z M399,269.82v-65.28
|
||||||
|
c5.44-2.72,12-4.16,18.72-4.16c12.16,0,18.08,6.72,18.08,18.4v51.04h16.16v-52.16c0-19.52-11.84-30.88-34.24-30.88
|
||||||
|
c-12.32,0-24.96,2.88-34.88,7.52v75.52H399L399,269.82z M547.32,229.18c0-25.28-14.08-42.4-38.08-42.4
|
||||||
|
c-23.68,0-37.92,17.12-37.92,42.4c0,25.44,14.24,42.56,37.92,42.56C533.24,271.74,547.32,254.62,547.32,229.18L547.32,229.18z
|
||||||
|
M487.8,229.18c0-17.44,7.36-28.8,21.44-28.8c14.56,0,21.44,11.36,21.44,28.8c0,17.6-6.88,28.96-21.44,28.96
|
||||||
|
C495.16,258.14,487.8,246.78,487.8,229.18L487.8,229.18z M575.8,188.7h-16.32c3.2,31.2,11.84,53.12,28.16,81.12h17.92
|
||||||
|
c16-28,24.32-49.92,28.16-81.12h-16.16c-2.56,27.36-9.92,47.2-20.96,67.68C585.88,235.9,578.68,216.06,575.8,188.7L575.8,188.7z"/>
|
||||||
|
<path class="st1" d="M307.68,360.51c-7.2,3.04-15.36,4.96-23.36,4.96c-15.68,0-25.44-7.84-27.04-24.96h54.56
|
||||||
|
c0.32-3.04,0.64-6.56,0.64-10.72c0-20.32-12.48-35.52-33.6-35.52c-23.36,0-38.24,17.44-38.24,42.4c0,27.04,16.8,42.56,42.24,42.56
|
||||||
|
c10.4,0,20-2.08,27.52-5.6L307.68,360.51L307.68,360.51z M278.88,307.55c12.32,0,17.92,9.28,17.92,20c0,0.96,0,1.6-0.16,2.56h-39.2
|
||||||
|
C258.88,316.35,266.4,307.55,278.88,307.55L278.88,307.55z M348.07,377.31v-65.28c5.44-2.72,12-4.16,18.72-4.16
|
||||||
|
c12.16,0,18.08,6.72,18.08,18.4v51.04h16.16v-52.16c0-19.52-11.84-30.88-34.24-30.88c-12.32,0-24.96,2.88-34.88,7.52v75.52H348.07
|
||||||
|
L348.07,377.31z M487.43,360.51c-7.2,3.04-15.36,4.96-23.36,4.96c-15.68,0-25.44-7.84-27.04-24.96h54.56
|
||||||
|
c0.32-3.04,0.64-6.56,0.64-10.72c0-20.32-12.48-35.52-33.6-35.52c-23.36,0-38.24,17.44-38.24,42.4c0,27.04,16.8,42.56,42.24,42.56
|
||||||
|
c10.4,0,20-2.08,27.52-5.6L487.43,360.51L487.43,360.51z M458.63,307.55c12.32,0,17.92,9.28,17.92,20c0,0.96,0,1.6-0.16,2.56h-39.2
|
||||||
|
C438.63,316.35,446.15,307.55,458.63,307.55L458.63,307.55z M512.71,301.95v75.36h16.16v-65.28c4.48-2.4,10.56-4.16,18.56-4.16
|
||||||
|
c2.56,0,5.6,0.32,8,1.12l2.24-13.6c-2.88-0.64-6.88-1.12-11.36-1.12C533.99,294.27,521.03,297.31,512.71,301.95L512.71,301.95z
|
||||||
|
M636.05,375.07v-74.4c-7.04-3.36-17.92-6.4-29.92-6.4c-24.8,0-41.12,15.84-41.12,41.44c0,24.32,14.4,38.24,37.28,38.24
|
||||||
|
c6.72,0,12.64-1.6,17.6-4.16v6.72c0,11.04-7.52,17.6-21.76,17.6c-8.48,0-15.36-1.28-22.4-4l-3.36,13.12
|
||||||
|
c8.16,3.2,15.2,4.64,26.72,4.64C621.81,407.87,636.05,396.35,636.05,375.07L636.05,375.07z M581.49,334.43
|
||||||
|
c0-16.48,9.44-26.56,24.64-26.56c5.12,0,10.24,1.28,13.76,2.56v45.6c-4,2.56-8.96,4.16-15.36,4.16
|
||||||
|
C589.97,360.19,581.49,350.43,581.49,334.43L581.49,334.43z M700.69,372.83c12.8-22.88,22.56-47.68,25.76-76.64h-16.16
|
||||||
|
c-2.4,27.36-8.48,45.12-18.56,65.28c-11.04-18.56-19.36-37.92-21.92-65.28h-16.32c3.36,30.72,12.64,53.44,29.76,77.76
|
||||||
|
c-6.24,10.4-16.96,16.96-27.36,19.04l2.72,13.92C674.45,403.71,688.53,394.11,700.69,372.83L700.69,372.83z"/>
|
||||||
|
<polygon class="st0" points="263.6,188.59 247.44,188.59 247.44,269.71 263.6,269.71 263.6,188.59 "/>
|
||||||
|
<path class="st1" d="M255.44,174.51c6.24,0,9.92-4,9.92-9.92c0-5.6-3.68-9.6-9.92-9.6c-6.4,0-10.08,4-10.08,9.6
|
||||||
|
C245.36,170.51,249.04,174.51,255.44,174.51L255.44,174.51z"/>
|
||||||
|
<path class="st0" d="M666.38,255.96c0,8.32-4.87,12.94-12.92,12.94c-8.12,0-12.85-4.62-12.85-12.94c0-8.25,4.73-12.94,12.85-12.94
|
||||||
|
C661.51,243.03,666.38,247.71,666.38,255.96L666.38,255.96z M637.36,255.96c0,9.51,6.29,15.78,16.1,15.78
|
||||||
|
c9.61,0,16.17-6.27,16.17-15.78s-6.49-15.78-16.17-15.78C643.72,240.19,637.36,246.46,637.36,255.96L637.36,255.96z M647.78,263.69
|
||||||
|
h3.11v-5.81h2.37c1.02,1.98,2.23,3.96,3.79,5.81h3.79c-1.83-2.18-3.31-4.23-4.4-6.27c2.23-0.73,3.25-2.38,3.25-4.62
|
||||||
|
c0-3.1-2.03-5.15-6.49-5.15h-5.41V263.69L647.78,263.69z M653.19,250.15c2.3,0,3.52,0.79,3.52,2.64c0,1.85-1.22,2.64-3.52,2.64
|
||||||
|
h-2.3v-5.28H653.19L653.19,250.15z"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 9.0 KiB |
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"installation": "installation/",
|
"installation": "installation/",
|
||||||
"liveView": "liveView/",
|
"live": "live",
|
||||||
"users": "/users/",
|
"users": "/users/",
|
||||||
"log": "log/",
|
"log": "log/",
|
||||||
"installations": "/installations/",
|
"installations": "/installations/",
|
||||||
|
@ -9,6 +9,13 @@
|
||||||
"folder": "folder/",
|
"folder": "folder/",
|
||||||
"manageAccess": "manageAccess/",
|
"manageAccess": "manageAccess/",
|
||||||
"user": "user/",
|
"user": "user/",
|
||||||
"tree": "tree",
|
"tree": "tree/",
|
||||||
"list": "list"
|
"list": "list/",
|
||||||
|
"overview": "overview",
|
||||||
|
"manage": "manage",
|
||||||
|
"log": "log",
|
||||||
|
"information": "information",
|
||||||
|
"configuration": "configuration",
|
||||||
|
"login": "/login/",
|
||||||
|
"forgotPassword": "/forgotPassword/"
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,12 +16,14 @@ import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
import Avatar from '@mui/material/Avatar';
|
import Avatar from '@mui/material/Avatar';
|
||||||
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
|
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
|
||||||
import axiosConfig from 'src/Resources/axiosConfig';
|
import axiosConfig from 'src/Resources/axiosConfig';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import routes from 'src/Resources/routes.json';
|
||||||
|
|
||||||
interface ForgotPasswordPromps {
|
interface ForgotPasswordPromps {
|
||||||
resetPassword: () => void;
|
resetPassword: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
function ForgotPassword(props: ForgotPasswordPromps) {
|
function ForgotPassword() {
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [open, setOpen] = useState(false);
|
const [open, setOpen] = useState(false);
|
||||||
|
@ -35,6 +37,7 @@ function ForgotPassword(props: ForgotPasswordPromps) {
|
||||||
const { currentUser, setUser, removeUser } = context;
|
const { currentUser, setUser, removeUser } = context;
|
||||||
const tokencontext = useContext(TokenContext);
|
const tokencontext = useContext(TokenContext);
|
||||||
const { token, setNewToken, removeToken } = tokencontext;
|
const { token, setNewToken, removeToken } = tokencontext;
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleUsernameChange = (e) => {
|
const handleUsernameChange = (e) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
|
@ -43,7 +46,8 @@ function ForgotPassword(props: ForgotPasswordPromps) {
|
||||||
|
|
||||||
const handleReturn = () => {
|
const handleReturn = () => {
|
||||||
setOpen(false);
|
setOpen(false);
|
||||||
props.resetPassword();
|
navigate(routes.login);
|
||||||
|
//props.resetPassword();
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
|
@ -72,7 +76,7 @@ function ForgotPassword(props: ForgotPasswordPromps) {
|
||||||
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
||||||
<a href="https://www.innov.energy/de/">
|
<a href="https://monitor.innov.energy/">
|
||||||
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
||||||
</a>
|
</a>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -122,6 +126,12 @@ function ForgotPassword(props: ForgotPasswordPromps) {
|
||||||
margin="normal"
|
margin="normal"
|
||||||
required
|
required
|
||||||
sx={{ width: 350 }}
|
sx={{ width: 350 }}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSubmit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{loading && <CircularProgress sx={{ color: '#ffc04d' }} />}
|
{loading && <CircularProgress sx={{ color: '#ffc04d' }} />}
|
||||||
|
|
|
@ -73,7 +73,7 @@ function ResetPassword() {
|
||||||
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
||||||
<a href="https://www.innov.energy/de/">
|
<a href="https://monitor.innov.energy/">
|
||||||
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
||||||
</a>
|
</a>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -0,0 +1,214 @@
|
||||||
|
import React, { useContext, useState } from 'react';
|
||||||
|
import {
|
||||||
|
Box,
|
||||||
|
Button,
|
||||||
|
CircularProgress,
|
||||||
|
Container,
|
||||||
|
Grid,
|
||||||
|
Modal,
|
||||||
|
TextField,
|
||||||
|
Typography,
|
||||||
|
useTheme
|
||||||
|
} from '@mui/material';
|
||||||
|
import innovenergyLogo from 'src/Resources/innoveng_logo_on_orange.png';
|
||||||
|
import axiosConfig from 'src/Resources/axiosConfig';
|
||||||
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
|
import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import Avatar from '@mui/material/Avatar';
|
||||||
|
import LockOutlinedIcon from '@mui/icons-material/LockOutlined';
|
||||||
|
|
||||||
|
function SetNewPassword() {
|
||||||
|
const [username, setUsername] = useState('');
|
||||||
|
const [loading, setLoading] = useState(false);
|
||||||
|
const [rememberMe, setRememberMe] = useState(false);
|
||||||
|
const [open, setOpen] = useState(false);
|
||||||
|
const theme = useTheme();
|
||||||
|
const context = useContext(UserContext);
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const [password, setPassword] = useState<string>('');
|
||||||
|
const [verifypassword, setVerifyPassword] = useState<string>('');
|
||||||
|
|
||||||
|
if (!context) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const { currentUser, setUser, removeUser } = context;
|
||||||
|
|
||||||
|
const tokencontext = useContext(TokenContext);
|
||||||
|
const { token, setNewToken, removeToken } = tokencontext;
|
||||||
|
|
||||||
|
const handlePasswordChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setPassword(value);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleVerifiedPasswordChange = (e) => {
|
||||||
|
const { name, value } = e.target;
|
||||||
|
setVerifyPassword(value);
|
||||||
|
};
|
||||||
|
const handleSubmit = () => {
|
||||||
|
setLoading(true);
|
||||||
|
axiosConfig
|
||||||
|
.put('/UpdatePassword', undefined, {
|
||||||
|
params: { newPassword: password }
|
||||||
|
})
|
||||||
|
.then((res) => {
|
||||||
|
setLoading(false);
|
||||||
|
currentUser.mustResetPassword = false;
|
||||||
|
setUser(currentUser);
|
||||||
|
window.location.reload();
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
setLoading(false);
|
||||||
|
setOpen(true);
|
||||||
|
|
||||||
|
if (error.response && error.response.status == 401) {
|
||||||
|
removeToken();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
||||||
|
<Grid container>
|
||||||
|
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
||||||
|
<a href="https://monitor.innov.energy/">
|
||||||
|
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
||||||
|
</a>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
</Container>
|
||||||
|
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
marginTop: 8,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center',
|
||||||
|
width: 500,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderRadius: 4,
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 6,
|
||||||
|
position: 'absolute',
|
||||||
|
top: '30%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Avatar sx={{ m: 1, bgcolor: '#ffc04d' }}>
|
||||||
|
<LockOutlinedIcon />
|
||||||
|
</Avatar>
|
||||||
|
<Typography component="h1" variant="h5">
|
||||||
|
Set New Password
|
||||||
|
</Typography>
|
||||||
|
<Box
|
||||||
|
component="form"
|
||||||
|
noValidate
|
||||||
|
sx={{
|
||||||
|
mt: 1,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<TextField
|
||||||
|
label="Password"
|
||||||
|
variant="outlined"
|
||||||
|
type="password"
|
||||||
|
value={password}
|
||||||
|
onChange={handlePasswordChange}
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
sx={{ width: 350 }}
|
||||||
|
/>
|
||||||
|
<TextField
|
||||||
|
label="Verify Password"
|
||||||
|
type="password"
|
||||||
|
variant="outlined"
|
||||||
|
value={verifypassword}
|
||||||
|
onChange={handleVerifiedPasswordChange}
|
||||||
|
fullWidth
|
||||||
|
margin="normal"
|
||||||
|
required
|
||||||
|
sx={{ width: 350 }}
|
||||||
|
/>
|
||||||
|
|
||||||
|
{loading && (
|
||||||
|
<CircularProgress sx={{ color: '#ffc04d', marginLeft: '0px' }} />
|
||||||
|
)}
|
||||||
|
|
||||||
|
{password != verifypassword && (
|
||||||
|
<Typography
|
||||||
|
component="h1"
|
||||||
|
variant="h5"
|
||||||
|
sx={{ color: '#FF0000', marginTop: 1 }}
|
||||||
|
>
|
||||||
|
Passwords do not match
|
||||||
|
</Typography>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
mt: 3,
|
||||||
|
mb: 2,
|
||||||
|
textTransform: 'none',
|
||||||
|
bgcolor: '#ffc04d',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
variant="contained"
|
||||||
|
fullWidth={true}
|
||||||
|
color="primary"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
>
|
||||||
|
Submit
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
<Modal
|
||||||
|
open={open}
|
||||||
|
onClose={() => setOpen(false)}
|
||||||
|
aria-labelledby="error-modal"
|
||||||
|
aria-describedby="error-modal-description"
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: 340,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderRadius: 4,
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography variant="body1" gutterBottom>
|
||||||
|
Setting new password failed. Please try again.
|
||||||
|
</Typography>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginTop: 2,
|
||||||
|
textTransform: 'none',
|
||||||
|
bgcolor: '#ffc04d',
|
||||||
|
color: '#111111',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
onClick={() => setOpen(false)}
|
||||||
|
>
|
||||||
|
Close
|
||||||
|
</Button>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default SetNewPassword;
|
|
@ -23,12 +23,9 @@ import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
|
import CheckBoxOutlineBlankIcon from '@mui/icons-material/CheckBoxOutlineBlank';
|
||||||
import CheckBoxIcon from '@mui/icons-material/CheckBox';
|
import CheckBoxIcon from '@mui/icons-material/CheckBox';
|
||||||
|
import routes from 'src/Resources/routes.json';
|
||||||
|
|
||||||
interface loginPromps {
|
function Login() {
|
||||||
onForgotPassword: () => void;
|
|
||||||
}
|
|
||||||
|
|
||||||
function Login(props: loginPromps) {
|
|
||||||
const [username, setUsername] = useState('');
|
const [username, setUsername] = useState('');
|
||||||
const [password, setPassword] = useState('');
|
const [password, setPassword] = useState('');
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
|
@ -43,11 +40,10 @@ function Login(props: loginPromps) {
|
||||||
if (!context) {
|
if (!context) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
const { currentUser, setUser, removeUser } = context;
|
|
||||||
|
|
||||||
|
const { currentUser, setUser, removeUser } = context;
|
||||||
const tokencontext = useContext(TokenContext);
|
const tokencontext = useContext(TokenContext);
|
||||||
const { token, setNewToken, removeToken } = tokencontext;
|
const { token, setNewToken, removeToken } = tokencontext;
|
||||||
|
|
||||||
const cookies = new Cookies();
|
const cookies = new Cookies();
|
||||||
|
|
||||||
const handleUsernameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
const handleUsernameChange = (event: React.ChangeEvent<HTMLInputElement>) => {
|
||||||
|
@ -61,6 +57,11 @@ function Login(props: loginPromps) {
|
||||||
const handleRememberMeChange = () => {
|
const handleRememberMeChange = () => {
|
||||||
setRememberMe(!rememberMe);
|
setRememberMe(!rememberMe);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const onForgotPassword = () => {
|
||||||
|
navigate(routes.forgotPassword);
|
||||||
|
};
|
||||||
|
|
||||||
const handleSubmit = () => {
|
const handleSubmit = () => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
axiosConfigWithoutToken
|
axiosConfigWithoutToken
|
||||||
|
@ -76,7 +77,7 @@ function Login(props: loginPromps) {
|
||||||
cookies.set('rememberedUsername', username, { path: '/' });
|
cookies.set('rememberedUsername', username, { path: '/' });
|
||||||
cookies.set('rememberedPassword', password, { path: '/' });
|
cookies.set('rememberedPassword', password, { path: '/' });
|
||||||
}
|
}
|
||||||
navigate('/');
|
navigate(routes.installations);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
|
@ -91,10 +92,10 @@ function Login(props: loginPromps) {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Container maxWidth="xl" sx={{ pt: 2 }}>
|
<Container maxWidth="xl" sx={{ pt: 2 }} className="login">
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
<Grid item xs={3} container justifyContent="flex-start" mb={2}>
|
||||||
<a href="https://www.innov.energy/de/">
|
<a href="https://monitor.innov.energy/">
|
||||||
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
||||||
</a>
|
</a>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -113,7 +114,6 @@ function Login(props: loginPromps) {
|
||||||
boxShadow: 24,
|
boxShadow: 24,
|
||||||
p: 6,
|
p: 6,
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
|
|
||||||
top: '30%',
|
top: '30%',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, -50%)'
|
transform: 'translate(-50%, -50%)'
|
||||||
|
@ -143,6 +143,12 @@ function Login(props: loginPromps) {
|
||||||
margin="normal"
|
margin="normal"
|
||||||
required
|
required
|
||||||
sx={{ width: 350 }}
|
sx={{ width: 350 }}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSubmit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<TextField
|
<TextField
|
||||||
|
@ -155,6 +161,12 @@ function Login(props: loginPromps) {
|
||||||
margin="normal"
|
margin="normal"
|
||||||
required
|
required
|
||||||
sx={{ width: 350 }}
|
sx={{ width: 350 }}
|
||||||
|
onKeyDown={(e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
handleSubmit();
|
||||||
|
}
|
||||||
|
}}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<FormControlLabel
|
<FormControlLabel
|
||||||
|
@ -188,7 +200,7 @@ function Login(props: loginPromps) {
|
||||||
{loading && (
|
{loading && (
|
||||||
<CircularProgress
|
<CircularProgress
|
||||||
sx={{
|
sx={{
|
||||||
color: theme.palette.primary.main,
|
color: '#ffc04d',
|
||||||
marginLeft: '20px'
|
marginLeft: '20px'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -242,7 +254,7 @@ function Login(props: loginPromps) {
|
||||||
sx={{ color: '#111111' }}
|
sx={{ color: '#111111' }}
|
||||||
onClick={(e) => {
|
onClick={(e) => {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
props.onForgotPassword();
|
onForgotPassword();
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Forgot password?
|
Forgot password?
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { TopologyValues } from '../Log/graph.util';
|
import { TopologyValues } from '../Log/graph.util';
|
||||||
import { Box, CardContent, Container, Grid, TextField } from '@mui/material';
|
import { Box, CardContent, Container, Grid, TextField } from '@mui/material';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
interface ConfigurationProps {
|
interface ConfigurationProps {
|
||||||
values: TopologyValues;
|
values: TopologyValues;
|
||||||
|
@ -32,7 +33,12 @@ function Configuration(props: ConfigurationProps) {
|
||||||
>
|
>
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
label="Minimum SoC"
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="minimum_soc"
|
||||||
|
defaultMessage="Minimum SoC"
|
||||||
|
/>
|
||||||
|
}
|
||||||
value={props.values.minimumSoC.values[0].value + ' %'}
|
value={props.values.minimumSoC.values[0].value + ' %'}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
@ -40,14 +46,24 @@ function Configuration(props: ConfigurationProps) {
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
label="Calibration Charge forced"
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="calibration_charge_forced"
|
||||||
|
defaultMessage="Calibration Charge forced"
|
||||||
|
/>
|
||||||
|
}
|
||||||
value={props.values.calibrationChargeForced.values[0].value}
|
value={props.values.calibrationChargeForced.values[0].value}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
label="Grid Set Point"
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="grid_set_point"
|
||||||
|
defaultMessage="Grid Set Point"
|
||||||
|
/>
|
||||||
|
}
|
||||||
value={
|
value={
|
||||||
(
|
(
|
||||||
(props.values.gridSetPoint.values[0].value as number) /
|
(props.values.gridSetPoint.values[0].value as number) /
|
||||||
|
@ -59,7 +75,12 @@ function Configuration(props: ConfigurationProps) {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
label="Installed Power DC1010"
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="Installed_Power_DC1010"
|
||||||
|
defaultMessage="Installed Power DC1010"
|
||||||
|
/>
|
||||||
|
}
|
||||||
value={
|
value={
|
||||||
(
|
(
|
||||||
(props.values.installedDcDcPower.values[0]
|
(props.values.installedDcDcPower.values[0]
|
||||||
|
@ -71,7 +92,12 @@ function Configuration(props: ConfigurationProps) {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
label="Maximum Discharge Power"
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="Maximum_Discharge_Power"
|
||||||
|
defaultMessage="Maximum Discharge Power"
|
||||||
|
/>
|
||||||
|
}
|
||||||
value={
|
value={
|
||||||
(
|
(
|
||||||
(props.values.maximumDischargePower.values[0]
|
(props.values.maximumDischargePower.values[0]
|
||||||
|
@ -83,7 +109,12 @@ function Configuration(props: ConfigurationProps) {
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<TextField
|
<TextField
|
||||||
label="Number of Batteries"
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="Number_of_Batteries"
|
||||||
|
defaultMessage="Number of Batteries"
|
||||||
|
/>
|
||||||
|
}
|
||||||
value={props.values.battery.values.length - 4}
|
value={props.values.battery.values.length - 4}
|
||||||
fullWidth
|
fullWidth
|
||||||
/>
|
/>
|
||||||
|
|
|
@ -18,7 +18,6 @@ import CancelIcon from '@mui/icons-material/Cancel';
|
||||||
import { LogContext } from 'src/contexts/LogContextProvider';
|
import { LogContext } from 'src/contexts/LogContextProvider';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
import routes from 'src/Resources/routes.json';
|
|
||||||
|
|
||||||
interface FlatInstallationViewProps {
|
interface FlatInstallationViewProps {
|
||||||
installations: I_Installation[];
|
installations: I_Installation[];
|
||||||
|
@ -29,7 +28,6 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
const logContext = useContext(LogContext);
|
const logContext = useContext(LogContext);
|
||||||
const { getStatus } = logContext;
|
const { getStatus } = logContext;
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(location.search);
|
const searchParams = new URLSearchParams(location.search);
|
||||||
const installationId = parseInt(searchParams.get('installation'));
|
const installationId = parseInt(searchParams.get('installation'));
|
||||||
const [selectedInstallation, setSelectedInstallation] = useState<number>(-1);
|
const [selectedInstallation, setSelectedInstallation] = useState<number>(-1);
|
||||||
|
@ -37,15 +35,9 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
const handleSelectOneInstallation = (installationID: number): void => {
|
const handleSelectOneInstallation = (installationID: number): void => {
|
||||||
if (selectedInstallation != installationID) {
|
if (selectedInstallation != installationID) {
|
||||||
setSelectedInstallation(installationID);
|
setSelectedInstallation(installationID);
|
||||||
navigate(
|
navigate(`?installation=${installationID}`, {
|
||||||
routes.installations +
|
replace: true
|
||||||
routes.list +
|
});
|
||||||
'?installation=' +
|
|
||||||
installationID.toString(),
|
|
||||||
{
|
|
||||||
replace: true
|
|
||||||
}
|
|
||||||
);
|
|
||||||
} else {
|
} else {
|
||||||
setSelectedInstallation(-1);
|
setSelectedInstallation(-1);
|
||||||
}
|
}
|
||||||
|
@ -88,8 +80,8 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="order"
|
id="orderNumbers"
|
||||||
defaultMessage="Order Values"
|
defaultMessage="Order Numbers"
|
||||||
/>
|
/>
|
||||||
</TableCell>
|
</TableCell>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
|
@ -234,6 +226,7 @@ const FlatInstallationView = (props: FlatInstallationViewProps) => {
|
||||||
</TableContainer>
|
</TableContainer>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{props.installations.map((installation) => (
|
{props.installations.map((installation) => (
|
||||||
<Installation
|
<Installation
|
||||||
key={installation.id}
|
key={installation.id}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { ChangeEvent, useContext, useEffect, useState } from 'react';
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Alert,
|
Alert,
|
||||||
Box,
|
Box,
|
||||||
|
@ -8,9 +8,9 @@ import {
|
||||||
Container,
|
Container,
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
Tab,
|
Modal,
|
||||||
Tabs,
|
|
||||||
TextField,
|
TextField,
|
||||||
|
Typography,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Close as CloseIcon } from '@mui/icons-material';
|
import { Close as CloseIcon } from '@mui/icons-material';
|
||||||
|
@ -18,7 +18,6 @@ import { I_Installation } from 'src/interfaces/InstallationTypes';
|
||||||
import Button from '@mui/material/Button';
|
import Button from '@mui/material/Button';
|
||||||
import { TokenContext } from 'src/contexts/tokenContext';
|
import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
import { UserContext } from 'src/contexts/userContext';
|
import { UserContext } from 'src/contexts/userContext';
|
||||||
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
|
||||||
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
||||||
import AccessContextProvider from 'src/contexts/AccessContextProvider';
|
import AccessContextProvider from 'src/contexts/AccessContextProvider';
|
||||||
import Access from '../ManageAccess/Access';
|
import Access from '../ManageAccess/Access';
|
||||||
|
@ -38,43 +37,12 @@ import Configuration from '../Configuration/Configuration';
|
||||||
import { fetchData } from 'src/content/dashboards/Installations/fetchData';
|
import { fetchData } from 'src/content/dashboards/Installations/fetchData';
|
||||||
|
|
||||||
interface singleInstallationProps {
|
interface singleInstallationProps {
|
||||||
current_installation: I_Installation;
|
current_installation?: I_Installation;
|
||||||
type: string;
|
type?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
function Installation(props: singleInstallationProps) {
|
function Installation(props: singleInstallationProps) {
|
||||||
const tabs = [
|
|
||||||
{
|
|
||||||
value: 'live',
|
|
||||||
label: <FormattedMessage id="live" defaultMessage="Live" />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'overview',
|
|
||||||
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
|
||||||
},
|
|
||||||
,
|
|
||||||
{
|
|
||||||
value: 'manage',
|
|
||||||
label: <FormattedMessage id="manage" defaultMessage="Access Management" />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'log',
|
|
||||||
label: <FormattedMessage id="log" defaultMessage="Log" />
|
|
||||||
},
|
|
||||||
{
|
|
||||||
value: 'information',
|
|
||||||
label: <FormattedMessage id="information" defaultMessage="Information" />
|
|
||||||
},
|
|
||||||
|
|
||||||
{
|
|
||||||
value: 'configuration',
|
|
||||||
label: (
|
|
||||||
<FormattedMessage id="configuration" defaultMessage="Configuration" />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
];
|
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [currentTab, setCurrentTab] = useState<string>('live');
|
|
||||||
const [formValues, setFormValues] = useState(props.current_installation);
|
const [formValues, setFormValues] = useState(props.current_installation);
|
||||||
const requiredFields = ['name', 'region', 'location', 'country'];
|
const requiredFields = ['name', 'region', 'location', 'country'];
|
||||||
const context = useContext(UserContext);
|
const context = useContext(UserContext);
|
||||||
|
@ -100,17 +68,16 @@ function Installation(props: singleInstallationProps) {
|
||||||
const { installationStatus, handleLogWarningOrError, getStatus } = logContext;
|
const { installationStatus, handleLogWarningOrError, getStatus } = logContext;
|
||||||
const searchParams = new URLSearchParams(location.search);
|
const searchParams = new URLSearchParams(location.search);
|
||||||
const installationId = parseInt(searchParams.get('installation'));
|
const installationId = parseInt(searchParams.get('installation'));
|
||||||
|
const currentTab = searchParams.get('tab');
|
||||||
|
|
||||||
const [values, setValues] = useState<TopologyValues | null>(null);
|
const [values, setValues] = useState<TopologyValues | null>(null);
|
||||||
|
const [openModalDeleteInstallation, setOpenModalDeleteInstallation] =
|
||||||
|
useState(false);
|
||||||
|
|
||||||
if (formValues == undefined) {
|
if (formValues == undefined) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
|
||||||
setCurrentTab(value);
|
|
||||||
setError(false);
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleChange = (e) => {
|
const handleChange = (e) => {
|
||||||
const { name, value } = e.target;
|
const { name, value } = e.target;
|
||||||
setFormValues({
|
setFormValues({
|
||||||
|
@ -127,7 +94,18 @@ function Installation(props: singleInstallationProps) {
|
||||||
const handleDelete = (e) => {
|
const handleDelete = (e) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(false);
|
setError(false);
|
||||||
|
setOpenModalDeleteInstallation(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteInstallationModalHandle = (e) => {
|
||||||
|
setOpenModalDeleteInstallation(false);
|
||||||
deleteInstallation(formValues, props.type);
|
deleteInstallation(formValues, props.type);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteInstallationModalHandleCancel = (e) => {
|
||||||
|
setOpenModalDeleteInstallation(false);
|
||||||
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const areRequiredFieldsFilled = () => {
|
const areRequiredFieldsFilled = () => {
|
||||||
|
@ -155,8 +133,8 @@ function Installation(props: singleInstallationProps) {
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
setFormValues(props.current_installation);
|
setFormValues(props.current_installation);
|
||||||
|
|
||||||
setErrorLoadingS3Data(false);
|
setErrorLoadingS3Data(false);
|
||||||
|
let disconnectedStatusResult = [];
|
||||||
|
|
||||||
const fetchDataPeriodically = async () => {
|
const fetchDataPeriodically = async () => {
|
||||||
const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20));
|
const now = UnixTime.now().earlier(TimeSpan.fromSeconds(20));
|
||||||
|
@ -165,9 +143,6 @@ function Installation(props: singleInstallationProps) {
|
||||||
try {
|
try {
|
||||||
const res = await fetchData(now, s3Credentials);
|
const res = await fetchData(now, s3Credentials);
|
||||||
|
|
||||||
if (installationId == 2) {
|
|
||||||
console.log('Fetched data from unix timestamp ' + now);
|
|
||||||
}
|
|
||||||
if (!isMounted) {
|
if (!isMounted) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -176,8 +151,22 @@ function Installation(props: singleInstallationProps) {
|
||||||
const newErrors: Notification[] = [];
|
const newErrors: Notification[] = [];
|
||||||
|
|
||||||
if (res === FetchResult.notAvailable || res === FetchResult.tryLater) {
|
if (res === FetchResult.notAvailable || res === FetchResult.tryLater) {
|
||||||
setErrorLoadingS3Data(true);
|
|
||||||
handleLogWarningOrError(props.current_installation.id, -1);
|
handleLogWarningOrError(props.current_installation.id, -1);
|
||||||
|
|
||||||
|
disconnectedStatusResult.unshift(-1);
|
||||||
|
disconnectedStatusResult = disconnectedStatusResult.slice(0, 5);
|
||||||
|
|
||||||
|
let i = 0;
|
||||||
|
//If at least one status value shows an error, then show error
|
||||||
|
for (i; i < disconnectedStatusResult.length; i++) {
|
||||||
|
if (disconnectedStatusResult[i] != -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (i === disconnectedStatusResult.length) {
|
||||||
|
setErrorLoadingS3Data(true);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
setErrorLoadingS3Data(false);
|
setErrorLoadingS3Data(false);
|
||||||
setValues(
|
setValues(
|
||||||
|
@ -237,9 +226,15 @@ function Installation(props: singleInstallationProps) {
|
||||||
|
|
||||||
if (newErrors.length > 0) {
|
if (newErrors.length > 0) {
|
||||||
handleLogWarningOrError(props.current_installation.id, 2);
|
handleLogWarningOrError(props.current_installation.id, 2);
|
||||||
|
disconnectedStatusResult.unshift(2);
|
||||||
|
disconnectedStatusResult = disconnectedStatusResult.slice(0, 5);
|
||||||
} else if (newWarnings.length > 0) {
|
} else if (newWarnings.length > 0) {
|
||||||
|
disconnectedStatusResult.unshift(1);
|
||||||
|
disconnectedStatusResult = disconnectedStatusResult.slice(0, 5);
|
||||||
handleLogWarningOrError(props.current_installation.id, 1);
|
handleLogWarningOrError(props.current_installation.id, 1);
|
||||||
} else {
|
} else {
|
||||||
|
disconnectedStatusResult.unshift(0);
|
||||||
|
disconnectedStatusResult = disconnectedStatusResult.slice(0, 5);
|
||||||
handleLogWarningOrError(props.current_installation.id, 0);
|
handleLogWarningOrError(props.current_installation.id, 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -259,291 +254,394 @@ function Installation(props: singleInstallationProps) {
|
||||||
|
|
||||||
if (installationId == props.current_installation.id) {
|
if (installationId == props.current_installation.id) {
|
||||||
return (
|
return (
|
||||||
<Grid item xs={12} md={12}>
|
<>
|
||||||
<TabsContainerWrapper>
|
{openModalDeleteInstallation && (
|
||||||
<Tabs
|
<Modal
|
||||||
onChange={handleTabsChange}
|
open={openModalDeleteInstallation}
|
||||||
value={currentTab}
|
onClose={() => setOpenModalDeleteInstallation(false)}
|
||||||
variant="scrollable"
|
aria-labelledby="error-modal"
|
||||||
scrollButtons="auto"
|
aria-describedby="error-modal-description"
|
||||||
textColor="primary"
|
|
||||||
indicatorColor="primary"
|
|
||||||
>
|
>
|
||||||
{tabs.map((tab) => (
|
<Box
|
||||||
<Tab key={tab.value} label={tab.label} value={tab.value} />
|
sx={{
|
||||||
))}
|
position: 'absolute',
|
||||||
</Tabs>
|
top: '50%',
|
||||||
</TabsContainerWrapper>
|
left: '50%',
|
||||||
<Card variant="outlined">
|
transform: 'translate(-50%, -50%)',
|
||||||
<Grid
|
width: 350,
|
||||||
container
|
bgcolor: 'background.paper',
|
||||||
direction="row"
|
borderRadius: 4,
|
||||||
justifyContent="center"
|
boxShadow: 24,
|
||||||
alignItems="stretch"
|
p: 4,
|
||||||
spacing={0}
|
display: 'flex',
|
||||||
>
|
flexDirection: 'column',
|
||||||
{currentTab === 'information' && (
|
alignItems: 'center'
|
||||||
<Container maxWidth="xl">
|
}}
|
||||||
<Grid
|
>
|
||||||
container
|
<Typography
|
||||||
direction="row"
|
variant="body1"
|
||||||
justifyContent="center"
|
gutterBottom
|
||||||
alignItems="stretch"
|
sx={{ fontWeight: 'bold' }}
|
||||||
spacing={3}
|
>
|
||||||
|
Do you want to delete this installation?
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginTop: 2,
|
||||||
|
textTransform: 'none',
|
||||||
|
bgcolor: '#ffc04d',
|
||||||
|
color: '#111111',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
onClick={deleteInstallationModalHandle}
|
||||||
>
|
>
|
||||||
<Grid item xs={12} md={12}>
|
Delete
|
||||||
<CardContent>
|
</Button>
|
||||||
<Box
|
<Button
|
||||||
component="form"
|
sx={{
|
||||||
sx={{
|
marginTop: 2,
|
||||||
'& .MuiTextField-root': { m: 1, width: '50ch' }
|
marginLeft: 2,
|
||||||
}}
|
textTransform: 'none',
|
||||||
noValidate
|
bgcolor: '#ffc04d',
|
||||||
autoComplete="off"
|
color: '#111111',
|
||||||
>
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
<div>
|
}}
|
||||||
<TextField
|
onClick={deleteInstallationModalHandleCancel}
|
||||||
label={
|
>
|
||||||
<FormattedMessage
|
Cancel
|
||||||
id="customerName"
|
</Button>
|
||||||
defaultMessage="Customer Name"
|
</div>
|
||||||
/>
|
</Box>
|
||||||
}
|
</Modal>
|
||||||
name="name"
|
)}
|
||||||
value={formValues.name}
|
|
||||||
onChange={handleChange}
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
error={formValues.name === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="region"
|
|
||||||
defaultMessage="Region"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="region"
|
|
||||||
value={formValues.region}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
error={formValues.name === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="location"
|
|
||||||
defaultMessage="Location"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="location"
|
|
||||||
value={formValues.location}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
error={formValues.name === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="country"
|
|
||||||
defaultMessage="Country"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="country"
|
|
||||||
value={formValues.country}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
required
|
|
||||||
error={formValues.name === ''}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<TextField
|
|
||||||
label={
|
|
||||||
<FormattedMessage
|
|
||||||
id="orderNumbers"
|
|
||||||
defaultMessage="Order Numbers"
|
|
||||||
/>
|
|
||||||
}
|
|
||||||
name="orderNumbers"
|
|
||||||
value={formValues.orderNumbers}
|
|
||||||
onChange={handleChange}
|
|
||||||
variant="outlined"
|
|
||||||
fullWidth
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{currentUser.hasWriteAccess && (
|
<Grid item xs={12} md={12}>
|
||||||
<>
|
<div style={{ display: 'flex', alignItems: 'center' }}>
|
||||||
<div>
|
<Typography
|
||||||
<TextField
|
fontWeight="bold"
|
||||||
label="S3 Write Key"
|
color="text.primary"
|
||||||
name="s3writekey"
|
noWrap
|
||||||
value={formValues.s3WriteKey}
|
sx={{
|
||||||
variant="outlined"
|
marginTop: '-20px',
|
||||||
fullWidth
|
marginBottom: '10px',
|
||||||
/>
|
fontSize: '14px'
|
||||||
</div>
|
}}
|
||||||
|
>
|
||||||
|
<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>
|
<Card variant="outlined">
|
||||||
<TextField
|
<Grid
|
||||||
label="S3 Write Secret Key"
|
container
|
||||||
name="s3writesecretkey"
|
direction="row"
|
||||||
value={formValues.s3WriteSecret}
|
justifyContent="center"
|
||||||
variant="outlined"
|
alignItems="stretch"
|
||||||
fullWidth
|
spacing={0}
|
||||||
/>
|
>
|
||||||
</div>
|
{currentTab === 'information' && (
|
||||||
|
<Container maxWidth="xl">
|
||||||
<div>
|
<Grid
|
||||||
<TextField
|
container
|
||||||
label="S3 Bucket Name"
|
direction="row"
|
||||||
name="s3writesecretkey"
|
justifyContent="center"
|
||||||
value={
|
alignItems="stretch"
|
||||||
formValues.id +
|
spacing={3}
|
||||||
'-3e5b3069-214a-43ee-8d85-57d72000c19d'
|
>
|
||||||
}
|
<Grid item xs={12} md={12}>
|
||||||
variant="outlined"
|
<CardContent>
|
||||||
fullWidth
|
<Box
|
||||||
/>
|
component="form"
|
||||||
</div>
|
sx={{
|
||||||
</>
|
'& .MuiTextField-root': { m: 1, width: '50ch' }
|
||||||
)}
|
|
||||||
|
|
||||||
<div
|
|
||||||
style={{
|
|
||||||
display: 'flex',
|
|
||||||
alignItems: 'center',
|
|
||||||
marginTop: 10
|
|
||||||
}}
|
}}
|
||||||
|
noValidate
|
||||||
|
autoComplete="off"
|
||||||
>
|
>
|
||||||
{currentUser.hasWriteAccess && (
|
<div>
|
||||||
<Button
|
<TextField
|
||||||
variant="contained"
|
label={
|
||||||
onClick={handleSubmit}
|
<FormattedMessage
|
||||||
sx={{
|
id="customerName"
|
||||||
marginLeft: '10px'
|
defaultMessage="Customer Name"
|
||||||
}}
|
/>
|
||||||
disabled={!areRequiredFieldsFilled()}
|
}
|
||||||
>
|
name="name"
|
||||||
<FormattedMessage
|
value={formValues.name}
|
||||||
id="applyChanges"
|
onChange={handleChange}
|
||||||
defaultMessage="Apply Changes"
|
fullWidth
|
||||||
/>
|
required
|
||||||
</Button>
|
error={formValues.name === ''}
|
||||||
)}
|
|
||||||
{currentUser.hasWriteAccess && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleDelete}
|
|
||||||
sx={{
|
|
||||||
marginLeft: '10px'
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="deleteInstallation"
|
|
||||||
defaultMessage="Delete Installation"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
{loading && (
|
|
||||||
<CircularProgress
|
|
||||||
sx={{
|
|
||||||
color: theme.palette.primary.main,
|
|
||||||
marginLeft: '20px'
|
|
||||||
}}
|
|
||||||
/>
|
/>
|
||||||
)}
|
</div>
|
||||||
{error && (
|
<div>
|
||||||
<Alert
|
<TextField
|
||||||
severity="error"
|
label={
|
||||||
sx={{
|
<FormattedMessage
|
||||||
ml: 1,
|
id="region"
|
||||||
display: 'flex',
|
defaultMessage="Region"
|
||||||
alignItems: 'center'
|
/>
|
||||||
}}
|
}
|
||||||
>
|
name="region"
|
||||||
<FormattedMessage
|
value={formValues.region}
|
||||||
id="errorOccured"
|
onChange={handleChange}
|
||||||
defaultMessage="An error has occurred"
|
variant="outlined"
|
||||||
/>
|
fullWidth
|
||||||
<IconButton
|
required
|
||||||
color="inherit"
|
error={formValues.name === ''}
|
||||||
size="small"
|
/>
|
||||||
onClick={() => setError(false)}
|
</div>
|
||||||
sx={{ marginLeft: '4px' }}
|
<div>
|
||||||
>
|
<TextField
|
||||||
<CloseIcon fontSize="small" />
|
label={
|
||||||
</IconButton>
|
<FormattedMessage
|
||||||
</Alert>
|
id="location"
|
||||||
)}
|
defaultMessage="Location"
|
||||||
{updated && (
|
/>
|
||||||
<Alert
|
}
|
||||||
severity="success"
|
name="location"
|
||||||
sx={{
|
value={formValues.location}
|
||||||
ml: 1,
|
onChange={handleChange}
|
||||||
display: 'flex',
|
variant="outlined"
|
||||||
alignItems: 'center'
|
fullWidth
|
||||||
}}
|
required
|
||||||
>
|
error={formValues.name === ''}
|
||||||
<FormattedMessage
|
/>
|
||||||
id="successfullyUpdated"
|
</div>
|
||||||
defaultMessage="Successfully updated"
|
<div>
|
||||||
/>
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="country"
|
||||||
|
defaultMessage="Country"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="country"
|
||||||
|
value={formValues.country}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
required
|
||||||
|
error={formValues.name === ''}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="orderNumbers"
|
||||||
|
defaultMessage="Order Numbers"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="orderNumbers"
|
||||||
|
value={formValues.orderNumbers}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label={
|
||||||
|
<FormattedMessage
|
||||||
|
id="installation_name"
|
||||||
|
defaultMessage="Installation Name"
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
name="installationName"
|
||||||
|
value={formValues.installationName}
|
||||||
|
onChange={handleChange}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<IconButton
|
{currentUser.hasWriteAccess && (
|
||||||
color="inherit"
|
<>
|
||||||
size="small"
|
<div>
|
||||||
onClick={() => setUpdated(false)} // Set error state to false on click
|
<TextField
|
||||||
sx={{ marginLeft: '4px' }}
|
label="S3 Write Key"
|
||||||
>
|
name="s3writekey"
|
||||||
<CloseIcon fontSize="small" />
|
value={formValues.s3WriteKey}
|
||||||
</IconButton>
|
variant="outlined"
|
||||||
</Alert>
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label="S3 Write Secret Key"
|
||||||
|
name="s3writesecretkey"
|
||||||
|
value={formValues.s3WriteSecret}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<TextField
|
||||||
|
label="S3 Bucket Name"
|
||||||
|
name="s3writesecretkey"
|
||||||
|
value={
|
||||||
|
formValues.id +
|
||||||
|
'-3e5b3069-214a-43ee-8d85-57d72000c19d'
|
||||||
|
}
|
||||||
|
variant="outlined"
|
||||||
|
fullWidth
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</Box>
|
<div
|
||||||
</CardContent>
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
{currentUser.hasWriteAccess && (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleDelete}
|
||||||
|
sx={{
|
||||||
|
marginLeft: '10px'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="deleteInstallation"
|
||||||
|
defaultMessage="Delete Installation"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
{currentUser.hasWriteAccess && (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleSubmit}
|
||||||
|
sx={{
|
||||||
|
marginLeft: '10px'
|
||||||
|
}}
|
||||||
|
disabled={!areRequiredFieldsFilled()}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="applyChanges"
|
||||||
|
defaultMessage="Apply Changes"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{loading && (
|
||||||
|
<CircularProgress
|
||||||
|
sx={{
|
||||||
|
color: theme.palette.primary.main,
|
||||||
|
marginLeft: '20px'
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
)}
|
||||||
|
{error && (
|
||||||
|
<Alert
|
||||||
|
severity="error"
|
||||||
|
sx={{
|
||||||
|
ml: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="errorOccured"
|
||||||
|
defaultMessage="An error has occurred"
|
||||||
|
/>
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setError(false)}
|
||||||
|
sx={{ marginLeft: '4px' }}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
{updated && (
|
||||||
|
<Alert
|
||||||
|
severity="success"
|
||||||
|
sx={{
|
||||||
|
ml: 1,
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="successfullyUpdated"
|
||||||
|
defaultMessage="Successfully updated"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<IconButton
|
||||||
|
color="inherit"
|
||||||
|
size="small"
|
||||||
|
onClick={() => setUpdated(false)} // Set error state to false on click
|
||||||
|
sx={{ marginLeft: '4px' }}
|
||||||
|
>
|
||||||
|
<CloseIcon fontSize="small" />
|
||||||
|
</IconButton>
|
||||||
|
</Alert>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</CardContent>
|
||||||
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Container>
|
||||||
</Container>
|
)}
|
||||||
)}
|
{currentTab === 'overview' && (
|
||||||
{currentTab === 'overview' && (
|
<Overview s3Credentials={s3Credentials}></Overview>
|
||||||
<Overview s3Credentials={s3Credentials}></Overview>
|
)}
|
||||||
)}
|
{currentTab === 'configuration' && currentUser.hasWriteAccess && (
|
||||||
{currentTab === 'configuration' && currentUser.hasWriteAccess && (
|
<Configuration values={values}></Configuration>
|
||||||
<Configuration values={values}></Configuration>
|
)}
|
||||||
)}
|
{currentTab === 'manage' && currentUser.hasWriteAccess && (
|
||||||
{currentTab === 'manage' && currentUser.hasWriteAccess && (
|
<AccessContextProvider>
|
||||||
<AccessContextProvider>
|
<Access
|
||||||
<Access
|
currentResource={formValues}
|
||||||
currentResource={formValues}
|
resourceType={props.type}
|
||||||
resourceType={props.type}
|
></Access>
|
||||||
></Access>
|
</AccessContextProvider>
|
||||||
</AccessContextProvider>
|
)}
|
||||||
)}
|
{currentTab === 'live' && <Topology values={values}></Topology>}
|
||||||
{currentTab === 'live' && <Topology values={values}></Topology>}
|
{currentTab === 'log' && (
|
||||||
{currentTab === 'log' && (
|
<Log
|
||||||
<Log
|
warnings={warnings}
|
||||||
warnings={warnings}
|
errors={errors}
|
||||||
errors={errors}
|
errorLoadingS3Data={errorLoadingS3Data}
|
||||||
errorLoadingS3Data={errorLoadingS3Data}
|
></Log>
|
||||||
></Log>
|
)}
|
||||||
)}
|
</Grid>
|
||||||
</Grid>
|
</Card>
|
||||||
</Card>
|
</Grid>
|
||||||
</Grid>
|
</>
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
return null;
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useContext, useEffect, useState } from 'react';
|
import React, { useEffect, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
FormControl,
|
FormControl,
|
||||||
Grid,
|
Grid,
|
||||||
|
@ -6,34 +6,32 @@ import {
|
||||||
TextField,
|
TextField,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
|
||||||
import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone';
|
import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone';
|
||||||
import FlatInstallationView from 'src/content/dashboards/Installations/FlatInstallationView';
|
import FlatInstallationView from 'src/content/dashboards/Installations/FlatInstallationView';
|
||||||
import LogContextProvider from 'src/contexts/LogContextProvider';
|
import LogContextProvider from '../../../contexts/LogContextProvider';
|
||||||
|
import { I_Installation } from '../../../interfaces/InstallationTypes';
|
||||||
|
|
||||||
function InstallationSearch() {
|
interface installationSearchProps {
|
||||||
|
installations: I_Installation[];
|
||||||
|
}
|
||||||
|
|
||||||
|
function InstallationSearch(props: installationSearchProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const [searchTerm, setSearchTerm] = useState('');
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
const { installations, fetchAllInstallations } =
|
|
||||||
useContext(InstallationsContext);
|
|
||||||
|
|
||||||
const searchParams = new URLSearchParams(location.search);
|
const searchParams = new URLSearchParams(location.search);
|
||||||
const installationId = parseInt(searchParams.get('installation'));
|
const installationId = parseInt(searchParams.get('installation'));
|
||||||
|
|
||||||
useEffect(() => {
|
const [filteredData, setFilteredData] = useState(props.installations);
|
||||||
fetchAllInstallations();
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const [filteredData, setFilteredData] = useState(installations);
|
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const filtered = installations.filter(
|
const filtered = props.installations.filter(
|
||||||
(item) =>
|
(item) =>
|
||||||
item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
item.name.toLowerCase().includes(searchTerm.toLowerCase()) ||
|
||||||
item.location.toLowerCase().includes(searchTerm.toLowerCase())
|
item.location.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
);
|
);
|
||||||
setFilteredData(filtered);
|
setFilteredData(filtered);
|
||||||
}, [searchTerm, installations]);
|
}, [searchTerm, props.installations]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -41,7 +39,7 @@ function InstallationSearch() {
|
||||||
<Grid
|
<Grid
|
||||||
item
|
item
|
||||||
xs={12}
|
xs={12}
|
||||||
md={4}
|
md={6}
|
||||||
sx={{ display: !installationId ? 'block' : 'none' }}
|
sx={{ display: !installationId ? 'block' : 'none' }}
|
||||||
>
|
>
|
||||||
<FormControl variant="outlined">
|
<FormControl variant="outlined">
|
||||||
|
|
|
@ -1,19 +0,0 @@
|
||||||
import { Box, Grid, useTheme } from '@mui/material';
|
|
||||||
import InstallationsContextProvider from 'src/contexts/InstallationsContextProvider';
|
|
||||||
import InstallationSearch from './InstallationSearch';
|
|
||||||
|
|
||||||
function FlatView() {
|
|
||||||
const theme = useTheme();
|
|
||||||
|
|
||||||
return (
|
|
||||||
<InstallationsContextProvider>
|
|
||||||
<Grid item xs={12}>
|
|
||||||
<Box p={4}>
|
|
||||||
<InstallationSearch />
|
|
||||||
</Box>
|
|
||||||
</Grid>
|
|
||||||
</InstallationsContextProvider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
export default FlatView;
|
|
|
@ -1,10 +1,9 @@
|
||||||
import React, { ChangeEvent, useEffect, useState } from 'react';
|
import React, { ChangeEvent, useContext, useEffect, useState } from 'react';
|
||||||
import Footer from 'src/components/Footer';
|
import Footer from 'src/components/Footer';
|
||||||
import { Card, Container, Grid, Tab, Tabs, useTheme } from '@mui/material';
|
import { Box, Card, Container, Grid, Tab, Tabs, useTheme } from '@mui/material';
|
||||||
import ListIcon from '@mui/icons-material/List';
|
import ListIcon from '@mui/icons-material/List';
|
||||||
import AccountTreeIcon from '@mui/icons-material/AccountTree';
|
import AccountTreeIcon from '@mui/icons-material/AccountTree';
|
||||||
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
||||||
import UsersContextProvider from 'src/contexts/UsersContextProvider';
|
|
||||||
import {
|
import {
|
||||||
Link,
|
Link,
|
||||||
Route,
|
Route,
|
||||||
|
@ -12,50 +11,220 @@ import {
|
||||||
useLocation,
|
useLocation,
|
||||||
useNavigate
|
useNavigate
|
||||||
} from 'react-router-dom';
|
} from 'react-router-dom';
|
||||||
import FlatView from './flatView';
|
|
||||||
import TreeView from '../Tree/treeView';
|
import TreeView from '../Tree/treeView';
|
||||||
import routes from 'src/Resources/routes.json';
|
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 LogContextProvider from '../../../contexts/LogContextProvider';
|
||||||
|
import Installation from './Installation';
|
||||||
|
|
||||||
function InstallationTabs() {
|
function InstallationTabs() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const tabs = [
|
|
||||||
{
|
const searchParams = new URLSearchParams(location.search);
|
||||||
value: 'list',
|
const installationId = parseInt(searchParams.get('installation'));
|
||||||
label: 'Flat view',
|
const [singleInstallationID, setSingleInstallationID] = useState(-1);
|
||||||
icon: <ListIcon id="mode-toggle-button-list-icon" />
|
const context = useContext(UserContext);
|
||||||
},
|
const { currentUser, setUser } = context;
|
||||||
{
|
|
||||||
value: 'tree',
|
|
||||||
label: 'Tree view',
|
|
||||||
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
|
||||||
}
|
|
||||||
];
|
|
||||||
const [currentTab, setCurrentTab] = useState<string>('list');
|
const [currentTab, setCurrentTab] = useState<string>('list');
|
||||||
|
const { installations, fetchAllInstallations } =
|
||||||
|
useContext(InstallationsContext);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
//console.log(location.pathname);
|
if (installations.length === 0) {
|
||||||
if (
|
fetchAllInstallations();
|
||||||
location.pathname === '/installations' ||
|
}
|
||||||
location.pathname === '/installations/'
|
|
||||||
) {
|
if (installations.length === 1) {
|
||||||
navigate(routes.installations + routes.list, {
|
navigate(`list?installation=${installations[0].id}&tab=live`, {
|
||||||
replace: true
|
replace: true
|
||||||
});
|
});
|
||||||
} else if (location.pathname === '/installations/tree') {
|
setCurrentTab('live');
|
||||||
setCurrentTab('tree');
|
} else {
|
||||||
|
if (
|
||||||
|
location.pathname === '/installations' ||
|
||||||
|
location.pathname === '/installations/'
|
||||||
|
) {
|
||||||
|
navigate(routes.installations + routes.list, {
|
||||||
|
replace: true
|
||||||
|
});
|
||||||
|
} else if (location.pathname === '/installations/tree/') {
|
||||||
|
setCurrentTab('tree');
|
||||||
|
} else if (location.pathname === '/installations/list/') {
|
||||||
|
setCurrentTab('list');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (installationId) {
|
||||||
|
navigate(`?installation=${installationId}&tab=live`, {
|
||||||
|
replace: true
|
||||||
|
});
|
||||||
|
setCurrentTab('live');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}, [location.pathname, navigate]);
|
}, [location.pathname, navigate, installationId, installations]);
|
||||||
|
|
||||||
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
||||||
setCurrentTab(value);
|
setCurrentTab(value);
|
||||||
navigate(value);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
const singleInstallationTabs = currentUser.hasWriteAccess
|
||||||
<UsersContextProvider>
|
? [
|
||||||
<Container maxWidth="xl" sx={{ marginTop: '20px' }}>
|
{
|
||||||
|
value: 'live',
|
||||||
|
label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'overview',
|
||||||
|
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||||
|
},
|
||||||
|
,
|
||||||
|
{
|
||||||
|
value: 'manage',
|
||||||
|
label: (
|
||||||
|
<FormattedMessage id="manage" defaultMessage="Access Management" />
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'log',
|
||||||
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'information',
|
||||||
|
label: (
|
||||||
|
<FormattedMessage id="information" defaultMessage="Information" />
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
value: 'configuration',
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id="configuration"
|
||||||
|
defaultMessage="Configuration"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
value: 'live',
|
||||||
|
label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'overview',
|
||||||
|
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||||
|
},
|
||||||
|
,
|
||||||
|
{
|
||||||
|
value: 'log',
|
||||||
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'information',
|
||||||
|
label: (
|
||||||
|
<FormattedMessage id="information" defaultMessage="Information" />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
const tabs = installationId
|
||||||
|
? currentUser.hasWriteAccess
|
||||||
|
? [
|
||||||
|
{
|
||||||
|
value: 'list',
|
||||||
|
icon: <ListIcon id="mode-toggle-button-list-icon" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'tree',
|
||||||
|
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'live',
|
||||||
|
label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'overview',
|
||||||
|
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||||
|
},
|
||||||
|
,
|
||||||
|
{
|
||||||
|
value: 'manage',
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id="manage"
|
||||||
|
defaultMessage="Access Management"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'log',
|
||||||
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'information',
|
||||||
|
label: (
|
||||||
|
<FormattedMessage id="information" defaultMessage="Information" />
|
||||||
|
)
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
value: 'configuration',
|
||||||
|
label: (
|
||||||
|
<FormattedMessage
|
||||||
|
id="configuration"
|
||||||
|
defaultMessage="Configuration"
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
]
|
||||||
|
: [
|
||||||
|
{
|
||||||
|
value: 'list',
|
||||||
|
icon: <ListIcon id="mode-toggle-button-list-icon" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'tree',
|
||||||
|
icon: <AccountTreeIcon id="mode-toggle-button-tree-icon" />
|
||||||
|
},
|
||||||
|
|
||||||
|
{
|
||||||
|
value: 'live',
|
||||||
|
label: <FormattedMessage id="live" defaultMessage="Live" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'overview',
|
||||||
|
label: <FormattedMessage id="overview" defaultMessage="Overview" />
|
||||||
|
},
|
||||||
|
,
|
||||||
|
{
|
||||||
|
value: 'log',
|
||||||
|
label: <FormattedMessage id="log" defaultMessage="Log" />
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 installations.length > 1 ? (
|
||||||
|
<>
|
||||||
|
<Container maxWidth="xl" sx={{ marginTop: '20px' }} className="mainframe">
|
||||||
<TabsContainerWrapper>
|
<TabsContainerWrapper>
|
||||||
<Tabs
|
<Tabs
|
||||||
onChange={handleTabsChange}
|
onChange={handleTabsChange}
|
||||||
|
@ -71,7 +240,12 @@ function InstallationTabs() {
|
||||||
value={tab.value}
|
value={tab.value}
|
||||||
icon={tab.icon}
|
icon={tab.icon}
|
||||||
component={Link}
|
component={Link}
|
||||||
to={routes[tab.value]}
|
label={tab.label}
|
||||||
|
to={
|
||||||
|
tab.value === 'list' || tab.value === 'tree'
|
||||||
|
? routes[tab.value]
|
||||||
|
: `?installation=${installationId}&tab=${routes[tab.value]}`
|
||||||
|
}
|
||||||
/>
|
/>
|
||||||
))}
|
))}
|
||||||
</Tabs>
|
</Tabs>
|
||||||
|
@ -85,15 +259,79 @@ function InstallationTabs() {
|
||||||
spacing={0}
|
spacing={0}
|
||||||
>
|
>
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route path={routes.list + '*'} element={<FlatView />} />
|
<Route
|
||||||
|
path={routes.list + '*'}
|
||||||
|
element={
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Box p={4}>
|
||||||
|
<InstallationSearch installations={installations} />
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
}
|
||||||
|
/>
|
||||||
<Route path={routes.tree + '*'} element={<TreeView />} />
|
<Route path={routes.tree + '*'} element={<TreeView />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Card>
|
</Card>
|
||||||
</Container>
|
</Container>
|
||||||
<Footer />
|
<Footer />
|
||||||
</UsersContextProvider>
|
</>
|
||||||
);
|
) : installations.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={`?installation=${installations[0].id}&tab=${
|
||||||
|
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}>
|
||||||
|
<LogContextProvider>
|
||||||
|
<Installation
|
||||||
|
current_installation={installations[0]}
|
||||||
|
type="installation"
|
||||||
|
></Installation>
|
||||||
|
</LogContextProvider>
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Routes>
|
||||||
|
</Grid>
|
||||||
|
</Card>
|
||||||
|
</Container>
|
||||||
|
<Footer />
|
||||||
|
</>
|
||||||
|
) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export default InstallationTabs;
|
export default InstallationTabs;
|
||||||
|
|
|
@ -66,6 +66,8 @@ function installationForm(props: installationFormProps) {
|
||||||
return true;
|
return true;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Modal
|
<Modal
|
||||||
|
@ -77,10 +79,10 @@ function installationForm(props: installationFormProps) {
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '30%',
|
top: isMobile ? '50%' : '30%',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
width: 600,
|
width: 500,
|
||||||
bgcolor: 'background.paper',
|
bgcolor: 'background.paper',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
boxShadow: 24,
|
boxShadow: 24,
|
||||||
|
|
|
@ -34,10 +34,10 @@ function Log(props: LogProps) {
|
||||||
<Grid container>
|
<Grid container>
|
||||||
<Grid item xs={12} md={12}>
|
<Grid item xs={12} md={12}>
|
||||||
{(props.errors.length > 0 || props.warnings.length > 0) && (
|
{(props.errors.length > 0 || props.warnings.length > 0) && (
|
||||||
<Card>
|
<Card sx={{ marginTop: '10px' }}>
|
||||||
<Divider />
|
<Divider />
|
||||||
<TableContainer>
|
<TableContainer>
|
||||||
<Table sx={{ height: 10 }}>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
<TableCell>
|
<TableCell>
|
||||||
|
@ -82,7 +82,7 @@ function Log(props: LogProps) {
|
||||||
color="text.primary"
|
color="text.primary"
|
||||||
gutterBottom
|
gutterBottom
|
||||||
noWrap
|
noWrap
|
||||||
sx={{ marginTop: '10px' }}
|
sx={{ marginTop: '5px' }}
|
||||||
>
|
>
|
||||||
{error.device}
|
{error.device}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -94,7 +94,7 @@ function Log(props: LogProps) {
|
||||||
color="text.primary"
|
color="text.primary"
|
||||||
gutterBottom
|
gutterBottom
|
||||||
noWrap
|
noWrap
|
||||||
sx={{ marginTop: '10px' }}
|
sx={{ marginTop: '5px' }}
|
||||||
>
|
>
|
||||||
{error.description}
|
{error.description}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -106,7 +106,7 @@ function Log(props: LogProps) {
|
||||||
color="text.primary"
|
color="text.primary"
|
||||||
gutterBottom
|
gutterBottom
|
||||||
noWrap
|
noWrap
|
||||||
sx={{ marginTop: '10px' }}
|
sx={{ marginTop: '5px' }}
|
||||||
>
|
>
|
||||||
{error.date}
|
{error.date}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -118,7 +118,7 @@ function Log(props: LogProps) {
|
||||||
color="text.primary"
|
color="text.primary"
|
||||||
gutterBottom
|
gutterBottom
|
||||||
noWrap
|
noWrap
|
||||||
sx={{ marginTop: '10px' }}
|
sx={{ marginTop: '5px' }}
|
||||||
>
|
>
|
||||||
{error.time}
|
{error.time}
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -202,11 +202,9 @@ function Log(props: LogProps) {
|
||||||
<Alert
|
<Alert
|
||||||
severity="error"
|
severity="error"
|
||||||
sx={{
|
sx={{
|
||||||
ml: 1,
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginTop: '20px',
|
marginTop: '20px'
|
||||||
marginBottom: '20px'
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
@ -227,7 +225,6 @@ function Log(props: LogProps) {
|
||||||
<Alert
|
<Alert
|
||||||
severity="error"
|
severity="error"
|
||||||
sx={{
|
sx={{
|
||||||
ml: 1,
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
marginTop: '20px'
|
marginTop: '20px'
|
||||||
|
@ -249,10 +246,10 @@ function Log(props: LogProps) {
|
||||||
<Alert
|
<Alert
|
||||||
severity="error"
|
severity="error"
|
||||||
sx={{
|
sx={{
|
||||||
ml: 1,
|
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center'
|
alignItems: 'center',
|
||||||
//marginBottom: '20px'
|
//marginBottom: '20px'
|
||||||
|
marginTop: '20px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
|
|
|
@ -35,6 +35,15 @@ export type BoxData = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type TopologyValues = {
|
export type TopologyValues = {
|
||||||
|
gridBox: BoxData;
|
||||||
|
pvOnAcGridBox: BoxData;
|
||||||
|
loadOnAcGridBox: BoxData;
|
||||||
|
pvOnIslandBusBox: BoxData;
|
||||||
|
loadOnIslandBusBox: BoxData;
|
||||||
|
pvOnDcBox: BoxData;
|
||||||
|
loadOnDcBox: BoxData;
|
||||||
|
batteryBox: BoxData;
|
||||||
|
|
||||||
grid: BoxData;
|
grid: BoxData;
|
||||||
gridToAcInConnection: BoxData;
|
gridToAcInConnection: BoxData;
|
||||||
gridBus: BoxData;
|
gridBus: BoxData;
|
||||||
|
@ -64,6 +73,15 @@ export type TopologyValues = {
|
||||||
type TopologyPaths = { [key in keyof TopologyValues]: string[] };
|
type TopologyPaths = { [key in keyof TopologyValues]: string[] };
|
||||||
|
|
||||||
export const topologyPaths: TopologyPaths = {
|
export const topologyPaths: TopologyPaths = {
|
||||||
|
gridBox: ['/Config/Devices/GridMeterIp/DeviceState'],
|
||||||
|
pvOnAcGridBox: ['/Config/Devices/PvOnAcGrid/DeviceState'],
|
||||||
|
loadOnAcGridBox: ['/Config/Devices/LoadOnAcGrid/DeviceState'],
|
||||||
|
pvOnIslandBusBox: ['/Config/Devices/PvOnAcIsland/DeviceState'],
|
||||||
|
loadOnIslandBusBox: ['/Config/Devices/IslandBusLoadMeterIp/DeviceState'],
|
||||||
|
pvOnDcBox: ['/Config/Devices/PvOnDc/DeviceState'],
|
||||||
|
loadOnDcBox: ['/Config/Devices/LoadOnDc/DeviceState'],
|
||||||
|
batteryBox: ['/Config/Devices/BatteryIp/DeviceState'],
|
||||||
|
|
||||||
grid: [
|
grid: [
|
||||||
'/GridMeter/Ac/L1/Power/Active',
|
'/GridMeter/Ac/L1/Power/Active',
|
||||||
'/GridMeter/Ac/L2/Power/Active',
|
'/GridMeter/Ac/L2/Power/Active',
|
||||||
|
@ -86,7 +104,7 @@ export const topologyPaths: TopologyPaths = {
|
||||||
'/AcDc/Ac/L2/Power/Active',
|
'/AcDc/Ac/L2/Power/Active',
|
||||||
'/AcDc/Ac/L3/Power/Active'
|
'/AcDc/Ac/L3/Power/Active'
|
||||||
],
|
],
|
||||||
islandBusToLoadOnIslandBusConnection: ['/LoadOnAcIsland/Power/Active'],
|
islandBusToLoadOnIslandBusConnection: ['/LoadOnAcIsland/Ac/Power/Active'],
|
||||||
islandBusToInverter: ['/AcDc/Dc/Power'],
|
islandBusToInverter: ['/AcDc/Dc/Power'],
|
||||||
pvOnIslandBusToIslandBusConnection: ['/PvOnAcIsland/Power/Active'],
|
pvOnIslandBusToIslandBusConnection: ['/PvOnAcIsland/Power/Active'],
|
||||||
|
|
||||||
|
@ -96,7 +114,7 @@ export const topologyPaths: TopologyPaths = {
|
||||||
'/AcDc/Devices/3/Status/Ac/Power/Active',
|
'/AcDc/Devices/3/Status/Ac/Power/Active',
|
||||||
'/AcDc/Devices/4/Status/Ac/Power/Active'
|
'/AcDc/Devices/4/Status/Ac/Power/Active'
|
||||||
],
|
],
|
||||||
inverterToDcBus: ['/AcDc/Dc/Power'],
|
inverterToDcBus: ['/AcDcToDcLink/Power'],
|
||||||
|
|
||||||
dcBus: ['/DcDc/Dc/Link/Voltage'],
|
dcBus: ['/DcDc/Dc/Link/Voltage'],
|
||||||
dcBusToDcDcConnection: ['/DcDc/Dc/Link/Power'],
|
dcBusToDcDcConnection: ['/DcDc/Dc/Link/Power'],
|
||||||
|
@ -104,8 +122,8 @@ export const topologyPaths: TopologyPaths = {
|
||||||
dcBusToLoadOnDcConnection: ['/LoadOnDc/Power'],
|
dcBusToLoadOnDcConnection: ['/LoadOnDc/Power'],
|
||||||
|
|
||||||
dcDc: ['/DcDc/Dc/Battery/Voltage'],
|
dcDc: ['/DcDc/Dc/Battery/Voltage'],
|
||||||
|
dcDCToBatteryConnection: ['/Battery/Dc/Power'],
|
||||||
|
|
||||||
dcDCToBatteryConnection: ['/DcDc/Dc/Link/Power'],
|
|
||||||
battery: [
|
battery: [
|
||||||
'/Battery/Soc',
|
'/Battery/Soc',
|
||||||
'/Battery/Dc/Voltage',
|
'/Battery/Dc/Voltage',
|
||||||
|
@ -124,7 +142,7 @@ export const topologyPaths: TopologyPaths = {
|
||||||
],
|
],
|
||||||
|
|
||||||
minimumSoC: ['/Config/MinSoc'],
|
minimumSoC: ['/Config/MinSoc'],
|
||||||
installedDcDcPower: ['/DcDc/SystemControl/TargetSlave'],
|
installedDcDcPower: ['/DcDc/SystemControl/NumberOfConnectedSlaves'],
|
||||||
gridSetPoint: ['/Config/GridSetPoint'],
|
gridSetPoint: ['/Config/GridSetPoint'],
|
||||||
maximumDischargePower: ['/Config/MaxBatteryDischargingCurrent'],
|
maximumDischargePower: ['/Config/MaxBatteryDischargingCurrent'],
|
||||||
calibrationChargeForced: ['/Config/ForceCalibrationCharge']
|
calibrationChargeForced: ['/Config/ForceCalibrationCharge']
|
||||||
|
@ -138,14 +156,10 @@ export const extractValues = (
|
||||||
for (const topologyKey of Object.keys(topologyPaths)) {
|
for (const topologyKey of Object.keys(topologyPaths)) {
|
||||||
const paths = topologyPaths[topologyKey];
|
const paths = topologyPaths[topologyKey];
|
||||||
let topologyValues: { unit: string; value: string | number }[] = [];
|
let topologyValues: { unit: string; value: string | number }[] = [];
|
||||||
//console.log('paths is ', paths);
|
|
||||||
|
|
||||||
// Check if any of the specified paths exist in the dataRecord
|
// Check if any of the specified paths exist in the dataRecord
|
||||||
for (const path of paths) {
|
for (const path of paths) {
|
||||||
//console.log(' path is ', path);
|
|
||||||
if (timeSeriesData.value.hasOwnProperty(path)) {
|
if (timeSeriesData.value.hasOwnProperty(path)) {
|
||||||
//console.log('matching path is ', path);
|
|
||||||
//console.log(timeSeriesData.value[path]);
|
|
||||||
topologyValues.push({
|
topologyValues.push({
|
||||||
unit: timeSeriesData.value[path].unit,
|
unit: timeSeriesData.value[path].unit,
|
||||||
value: timeSeriesData.value[path].value
|
value: timeSeriesData.value[path].value
|
||||||
|
@ -179,7 +193,19 @@ export const getAmount = (
|
||||||
highestConnectionValue: number,
|
highestConnectionValue: number,
|
||||||
values: I_BoxDataValue[]
|
values: I_BoxDataValue[]
|
||||||
) => {
|
) => {
|
||||||
return Math.abs(values[0].value as number) / highestConnectionValue;
|
// console.log(
|
||||||
|
// 'value=',
|
||||||
|
// Math.abs(values[0].value as number),
|
||||||
|
// 'highest is',
|
||||||
|
// highestConnectionValue,
|
||||||
|
// 'and amount is ',
|
||||||
|
// parseFloat(
|
||||||
|
// (Math.abs(values[0].value as number) / highestConnectionValue).toFixed(1)
|
||||||
|
// )
|
||||||
|
// );
|
||||||
|
return parseFloat(
|
||||||
|
(Math.abs(values[0].value as number) / highestConnectionValue).toFixed(1)
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export const createTimes = (
|
export const createTimes = (
|
||||||
|
@ -187,9 +213,13 @@ export const createTimes = (
|
||||||
numberOfNodes: number
|
numberOfNodes: number
|
||||||
): UnixTime[] => {
|
): UnixTime[] => {
|
||||||
const oneSpan = range.duration.divide(numberOfNodes);
|
const oneSpan = range.duration.divide(numberOfNodes);
|
||||||
|
//console.log(oneSpan);
|
||||||
|
|
||||||
const roundedRange = TimeRange.fromTimes(
|
const roundedRange = TimeRange.fromTimes(
|
||||||
range.start.round(oneSpan),
|
range.start.round(oneSpan),
|
||||||
range.end.round(oneSpan)
|
range.end.round(oneSpan)
|
||||||
);
|
);
|
||||||
return roundedRange.sample(oneSpan);
|
|
||||||
|
const unixTimes = range.sample(oneSpan);
|
||||||
|
return unixTimes;
|
||||||
};
|
};
|
||||||
|
|
|
@ -2,21 +2,45 @@
|
||||||
|
|
||||||
import { ApexOptions } from 'apexcharts';
|
import { ApexOptions } from 'apexcharts';
|
||||||
import { chartInfoInterface } from 'src/interfaces/Chart';
|
import { chartInfoInterface } from 'src/interfaces/Chart';
|
||||||
import { findPower, formatPowerForGraph } from '../../../Resources/formatPower';
|
import { findPower, formatPowerForGraph } from 'src/Resources/formatPower';
|
||||||
|
import { addHours, format } from 'date-fns';
|
||||||
|
|
||||||
export const getChartOptions = (chartInfo: chartInfoInterface): ApexOptions => {
|
export const getChartOptions = (chartInfo: chartInfoInterface): ApexOptions => {
|
||||||
|
// Custom datetime formatter for GMT+2
|
||||||
|
const customDatetimeFormatter = (timestamp, options) => {
|
||||||
|
const gmtDate = new Date(timestamp); // Convert Unix timestamp to milliseconds
|
||||||
|
const gmtPlus2Date = addHours(gmtDate, 4); // Add 2 hours to convert to GMT+2
|
||||||
|
|
||||||
|
// Use the specified options to format the date and time
|
||||||
|
const year = format(gmtDate, 'yyyy');
|
||||||
|
const month = format(gmtDate, "MMM 'yy");
|
||||||
|
const day = format(gmtDate, 'dd MMM');
|
||||||
|
const hour = format(gmtDate, 'HH:mm');
|
||||||
|
const minute = format(gmtDate, 'mm');
|
||||||
|
|
||||||
|
// Return the formatted date and time based on the provided options
|
||||||
|
return ` ${hour}:${minute}`;
|
||||||
|
};
|
||||||
const chartOptions: ApexOptions = {
|
const chartOptions: ApexOptions = {
|
||||||
chart: {
|
chart: {
|
||||||
id: 'area-datetime',
|
id: 'area-datetime',
|
||||||
|
toolbar: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
type: 'area',
|
type: 'area',
|
||||||
height: 350,
|
height: 350,
|
||||||
zoom: {
|
zoom: {
|
||||||
autoScaleYaxis: false
|
autoScaleYaxis: false
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
// markers: {
|
||||||
|
// size: 1,
|
||||||
|
// strokeColors: 'black'
|
||||||
|
// },
|
||||||
dataLabels: {
|
dataLabels: {
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
|
|
||||||
fill: {
|
fill: {
|
||||||
type: 'gradient',
|
type: 'gradient',
|
||||||
gradient: {
|
gradient: {
|
||||||
|
@ -36,7 +60,8 @@ export const getChartOptions = (chartInfo: chartInfoInterface): ApexOptions => {
|
||||||
year: 'yyyy',
|
year: 'yyyy',
|
||||||
month: "MMM 'yy",
|
month: "MMM 'yy",
|
||||||
day: 'dd MMM',
|
day: 'dd MMM',
|
||||||
hour: 'HH:mm'
|
hour: 'HH:mm',
|
||||||
|
minute: 'mm'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -45,11 +70,19 @@ export const getChartOptions = (chartInfo: chartInfoInterface): ApexOptions => {
|
||||||
width: 2
|
width: 2
|
||||||
},
|
},
|
||||||
yaxis: {
|
yaxis: {
|
||||||
min: chartInfo.min > 0 ? 0 : undefined,
|
min:
|
||||||
|
chartInfo.min >= 0
|
||||||
|
? 0
|
||||||
|
: chartInfo.max <= 0
|
||||||
|
? Math.ceil(chartInfo.min / findPower(chartInfo.min).value) *
|
||||||
|
findPower(chartInfo.min).value
|
||||||
|
: undefined,
|
||||||
max:
|
max:
|
||||||
chartInfo.min > 0
|
chartInfo.min >= 0
|
||||||
? Math.round(chartInfo.max / findPower(chartInfo.max).value) *
|
? Math.ceil(chartInfo.max / findPower(chartInfo.max).value) *
|
||||||
findPower(chartInfo.max).value
|
findPower(chartInfo.max).value
|
||||||
|
: chartInfo.max <= 0
|
||||||
|
? 0
|
||||||
: undefined,
|
: undefined,
|
||||||
title: {
|
title: {
|
||||||
text: chartInfo.unit,
|
text: chartInfo.unit,
|
||||||
|
@ -61,30 +94,26 @@ export const getChartOptions = (chartInfo: chartInfoInterface): ApexOptions => {
|
||||||
rotate: 0
|
rotate: 0
|
||||||
},
|
},
|
||||||
labels: {
|
labels: {
|
||||||
formatter:
|
formatter: function (value: number) {
|
||||||
chartInfo.min > 0
|
return formatPowerForGraph(
|
||||||
? function (value: number) {
|
value,
|
||||||
return formatPowerForGraph(
|
Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min))
|
||||||
value,
|
).value.toString();
|
||||||
chartInfo.max
|
}
|
||||||
).value.toString();
|
|
||||||
}
|
|
||||||
: function (value: number) {
|
|
||||||
return formatPowerForGraph(
|
|
||||||
value,
|
|
||||||
chartInfo.max
|
|
||||||
).value.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
tooltip: {
|
tooltip: {
|
||||||
x: {
|
x: {
|
||||||
format: 'dd MMM HH:mm'
|
format: 'dd MMM HH:mm:ss'
|
||||||
},
|
},
|
||||||
y: {
|
y: {
|
||||||
formatter: function (val, opts) {
|
formatter: function (val, opts) {
|
||||||
return (
|
return (
|
||||||
formatPowerForGraph(val, chartInfo.max).value.toFixed(2) +
|
formatPowerForGraph(
|
||||||
|
val,
|
||||||
|
Math.max(Math.abs(chartInfo.max), Math.abs(chartInfo.min))
|
||||||
|
).value.toFixed(2) +
|
||||||
' ' +
|
' ' +
|
||||||
chartInfo.unit
|
chartInfo.unit
|
||||||
);
|
);
|
||||||
|
|
|
@ -7,16 +7,24 @@ import {
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import ReactApexChart from 'react-apexcharts';
|
import ReactApexChart from 'react-apexcharts';
|
||||||
import { useEffect, useMemo, useState } from 'react';
|
import React, { useEffect, useMemo, useState } from 'react';
|
||||||
import DataCache from 'src/dataCache/dataCache';
|
import DataCache from 'src/dataCache/dataCache';
|
||||||
import { TimeSpan, UnixTime } from 'src/dataCache/time';
|
import { TimeRange, TimeSpan, UnixTime } from 'src/dataCache/time';
|
||||||
import { BehaviorSubject, startWith, throttleTime, withLatestFrom } from 'rxjs';
|
import {
|
||||||
|
BehaviorSubject,
|
||||||
|
combineLatest,
|
||||||
|
startWith,
|
||||||
|
tap,
|
||||||
|
throttleTime
|
||||||
|
} from 'rxjs';
|
||||||
import { RecordSeries } from 'src/dataCache/data';
|
import { RecordSeries } from 'src/dataCache/data';
|
||||||
import { createTimes } from '../Log/graph.util';
|
import { createTimes } from '../Log/graph.util';
|
||||||
import { I_S3Credentials } from 'src/interfaces/S3Types';
|
import { I_S3Credentials } from 'src/interfaces/S3Types';
|
||||||
import { getChartOptions } from './chartOptions';
|
import { getChartOptions } from './chartOptions';
|
||||||
import { chartDataInterface, overviewInterface } from 'src/interfaces/Chart';
|
import { chartDataInterface, overviewInterface } from 'src/interfaces/Chart';
|
||||||
import { fetchData } from 'src/content/dashboards/Installations/fetchData';
|
import { fetchData } from 'src/content/dashboards/Installations/fetchData';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
const prefixes = ['', 'k', 'M', 'G', 'T'];
|
||||||
const MAX_NUMBER = 9999999;
|
const MAX_NUMBER = 9999999;
|
||||||
|
@ -27,9 +35,12 @@ interface OverviewProps {
|
||||||
|
|
||||||
function Overview(props: OverviewProps) {
|
function Overview(props: OverviewProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
const timeRange = createTimes(
|
const numOfPointsToFetch = 100;
|
||||||
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)),
|
const [timeRange, setTimeRange] = useState(
|
||||||
200
|
createTimes(
|
||||||
|
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)),
|
||||||
|
numOfPointsToFetch
|
||||||
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
const [chartData, setChartData] = useState<chartDataInterface>({
|
const [chartData, setChartData] = useState<chartDataInterface>({
|
||||||
|
@ -102,38 +113,49 @@ function Overview(props: OverviewProps) {
|
||||||
magnitude: 0,
|
magnitude: 0,
|
||||||
unit: '',
|
unit: '',
|
||||||
min: MAX_NUMBER,
|
min: MAX_NUMBER,
|
||||||
max: 0
|
max: -MAX_NUMBER
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
console.log(input);
|
||||||
input.forEach((item) => {
|
input.forEach((item) => {
|
||||||
const csvContent = item.value;
|
const csvContent = item.value;
|
||||||
|
|
||||||
pathsToSearch.forEach((path) => {
|
pathsToSearch.forEach((path) => {
|
||||||
if (csvContent && csvContent[path]) {
|
if (csvContent) {
|
||||||
const timestamp = item.time.ticks * 1000;
|
const timestamp = item.time.ticks * 1000;
|
||||||
const value = csvContent[path];
|
|
||||||
// const result: { magnitude: number; value: number } = formatPower(
|
|
||||||
// value.value
|
|
||||||
// );
|
|
||||||
if (value.value < overviewData[path].min) {
|
|
||||||
overviewData[path].min = value.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value.value > overviewData[path].max) {
|
const adjustedTimestamp = new Date(timestamp);
|
||||||
overviewData[path].max = value.value;
|
adjustedTimestamp.setHours(adjustedTimestamp.getHours() + 2);
|
||||||
}
|
|
||||||
|
|
||||||
// if (result.magnitude > result.magnitude) {
|
if (csvContent[path]) {
|
||||||
// overviewData[path].magnitude = result.magnitude;
|
const value = csvContent[path];
|
||||||
// }
|
|
||||||
data[path].push([timestamp, value.value]);
|
if (value.value < overviewData[path].min) {
|
||||||
|
overviewData[path].min = value.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value.value > overviewData[path].max) {
|
||||||
|
overviewData[path].max = value.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
data[path].push([adjustedTimestamp, value.value]);
|
||||||
|
} else {
|
||||||
|
//data[path].push([adjustedTimestamp, null]);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// data[path].push([
|
||||||
|
// addHours(new Date(item.time.ticks * 1000), 2),
|
||||||
|
// null
|
||||||
|
// ]);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
pathsToSearch.forEach((path) => {
|
pathsToSearch.forEach((path) => {
|
||||||
let value = overviewData[path].max;
|
let value = Math.max(
|
||||||
|
Math.abs(overviewData[path].max),
|
||||||
|
Math.abs(overviewData[path].min)
|
||||||
|
);
|
||||||
let negative = false;
|
let negative = false;
|
||||||
let magnitude = 0;
|
let magnitude = 0;
|
||||||
|
|
||||||
|
@ -145,8 +167,6 @@ function Overview(props: OverviewProps) {
|
||||||
value /= 1000;
|
value /= 1000;
|
||||||
magnitude++;
|
magnitude++;
|
||||||
}
|
}
|
||||||
console.log(path, magnitude);
|
|
||||||
|
|
||||||
overviewData[path].magnitude = prefixes[magnitude];
|
overviewData[path].magnitude = prefixes[magnitude];
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -175,8 +195,6 @@ function Overview(props: OverviewProps) {
|
||||||
path = '/Battery/Dc/Power';
|
path = '/Battery/Dc/Power';
|
||||||
chartData.dcPower = [{ name: 'Battery Power', data: data[path] }];
|
chartData.dcPower = [{ name: 'Battery Power', data: data[path] }];
|
||||||
|
|
||||||
//console.log(overviewData[path]);
|
|
||||||
//console.log(data[path]);
|
|
||||||
chartOverview.dcPower = {
|
chartOverview.dcPower = {
|
||||||
magnitude: overviewData[path].magnitude,
|
magnitude: overviewData[path].magnitude,
|
||||||
unit: '(' + overviewData[path].magnitude + 'W' + ')',
|
unit: '(' + overviewData[path].magnitude + 'W' + ')',
|
||||||
|
@ -218,37 +236,129 @@ function Overview(props: OverviewProps) {
|
||||||
chartData: chartData,
|
chartData: chartData,
|
||||||
chartOverview: chartOverview
|
chartOverview: chartOverview
|
||||||
};
|
};
|
||||||
|
|
||||||
//return chartData;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const subscription = cache.gotData
|
const left = cache.gotData.pipe(
|
||||||
.pipe(
|
startWith(UnixTime.fromTicks(0)),
|
||||||
startWith(0),
|
throttleTime(200, undefined, { leading: true, trailing: true })
|
||||||
throttleTime(200, undefined, { leading: true, trailing: true }),
|
);
|
||||||
withLatestFrom(times$)
|
|
||||||
)
|
|
||||||
.subscribe(([_, times]) => {
|
|
||||||
const timeSeries = cache.getSeries(times);
|
|
||||||
|
|
||||||
console.log(timeSeries);
|
const right = times$.pipe(
|
||||||
|
tap((times) => {
|
||||||
|
//console.log(times);
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
const result: {
|
const combined = combineLatest([left, right]);
|
||||||
chartData: chartDataInterface;
|
|
||||||
chartOverview: overviewInterface;
|
|
||||||
} = transformToGraphData(timeSeries);
|
|
||||||
|
|
||||||
setChartData(result.chartData);
|
const subscription = combined.subscribe(([_, times]) => {
|
||||||
setChartOverview(result.chartOverview);
|
const timeSeries = cache.getSeries(times);
|
||||||
});
|
const result: {
|
||||||
|
chartData: chartDataInterface;
|
||||||
|
chartOverview: overviewInterface;
|
||||||
|
} = transformToGraphData(timeSeries);
|
||||||
|
|
||||||
|
setChartData(result.chartData);
|
||||||
|
setChartOverview(result.chartOverview);
|
||||||
|
});
|
||||||
return () => subscription.unsubscribe();
|
return () => subscription.unsubscribe();
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
|
const handleBeforeZoom = (chartContext, { xaxis }) => {
|
||||||
|
const startX = parseInt(xaxis.min) / 1000;
|
||||||
|
const endX = parseInt(xaxis.max) / 1000;
|
||||||
|
|
||||||
|
const times = createTimes(
|
||||||
|
TimeRange.fromTimes(UnixTime.fromTicks(startX), UnixTime.fromTicks(endX)),
|
||||||
|
numOfPointsToFetch
|
||||||
|
);
|
||||||
|
|
||||||
|
cache.getSeries(times);
|
||||||
|
times$.next(times);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleDoubleClick = () => {
|
||||||
|
const times = createTimes(
|
||||||
|
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)),
|
||||||
|
numOfPointsToFetch
|
||||||
|
);
|
||||||
|
cache.getSeries(times);
|
||||||
|
times$.next(times);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handle24HourData = () => {
|
||||||
|
const times = createTimes(
|
||||||
|
UnixTime.now().rangeBefore(TimeSpan.fromDays(1)),
|
||||||
|
numOfPointsToFetch
|
||||||
|
);
|
||||||
|
cache.getSeries(times);
|
||||||
|
times$.next(times);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleWeekData = () => {
|
||||||
|
const times = createTimes(
|
||||||
|
UnixTime.now().rangeBefore(TimeSpan.fromWeeks(1)),
|
||||||
|
numOfPointsToFetch
|
||||||
|
);
|
||||||
|
cache.getSeries(times);
|
||||||
|
times$.next(times);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleMonthData = () => {
|
||||||
|
const times = createTimes(
|
||||||
|
UnixTime.now().rangeBefore(TimeSpan.fromWeeks(4)),
|
||||||
|
numOfPointsToFetch
|
||||||
|
);
|
||||||
|
cache.getSeries(times);
|
||||||
|
times$.next(times);
|
||||||
|
};
|
||||||
|
|
||||||
const renderGraphs = () => {
|
const renderGraphs = () => {
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="xl">
|
<Container maxWidth="xl">
|
||||||
<Grid container>
|
<Grid container>
|
||||||
|
<Grid item xs={12} md={12}>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handle24HourData}
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
backgroundColor: '#ffc04d',
|
||||||
|
color: '#000000',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="24_hours" defaultMessage="24-hours" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleWeekData}
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginLeft: '10px',
|
||||||
|
backgroundColor: '#ffc04d',
|
||||||
|
color: '#000000',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="lastweek" defaultMessage="Last week" />
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleMonthData}
|
||||||
|
sx={{
|
||||||
|
marginTop: '20px',
|
||||||
|
marginLeft: '10px',
|
||||||
|
backgroundColor: '#ffc04d',
|
||||||
|
color: '#000000',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<FormattedMessage id="lastmonth" defaultMessage="Last Month" />
|
||||||
|
</Button>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
<Grid item xs={12} md={12}>
|
<Grid item xs={12} md={12}>
|
||||||
<Grid
|
<Grid
|
||||||
container
|
container
|
||||||
|
@ -273,7 +383,10 @@ function Overview(props: OverviewProps) {
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle1" noWrap>
|
<Typography variant="subtitle1" noWrap>
|
||||||
Battery SOC (State Of Charge)
|
<FormattedMessage
|
||||||
|
id="battery_soc"
|
||||||
|
defaultMessage="Battery SOC (State Of Charge)"
|
||||||
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -287,7 +400,14 @@ function Overview(props: OverviewProps) {
|
||||||
></Box>
|
></Box>
|
||||||
</Box>
|
</Box>
|
||||||
<ReactApexChart
|
<ReactApexChart
|
||||||
options={getChartOptions(chartOverview.soc)}
|
options={{
|
||||||
|
...getChartOptions(chartOverview.soc),
|
||||||
|
chart: {
|
||||||
|
events: {
|
||||||
|
beforeZoom: handleBeforeZoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
series={chartData.soc}
|
series={chartData.soc}
|
||||||
type="area"
|
type="area"
|
||||||
height={350}
|
height={350}
|
||||||
|
@ -310,7 +430,10 @@ function Overview(props: OverviewProps) {
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle1" noWrap>
|
<Typography variant="subtitle1" noWrap>
|
||||||
Battery Temperature
|
<FormattedMessage
|
||||||
|
id="battery_temperature"
|
||||||
|
defaultMessage="Battery Temperature"
|
||||||
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -323,12 +446,21 @@ function Overview(props: OverviewProps) {
|
||||||
}}
|
}}
|
||||||
></Box>
|
></Box>
|
||||||
</Box>
|
</Box>
|
||||||
<ReactApexChart
|
<div onDoubleClick={handleDoubleClick}>
|
||||||
options={getChartOptions(chartOverview.temperature)}
|
<ReactApexChart
|
||||||
series={chartData.temperature}
|
options={{
|
||||||
type="line"
|
...getChartOptions(chartOverview.temperature),
|
||||||
height={350}
|
chart: {
|
||||||
/>
|
events: {
|
||||||
|
beforeZoom: handleBeforeZoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
series={chartData.temperature}
|
||||||
|
type="area"
|
||||||
|
height={350}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -356,7 +488,10 @@ function Overview(props: OverviewProps) {
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle1" noWrap>
|
<Typography variant="subtitle1" noWrap>
|
||||||
Battery Power
|
<FormattedMessage
|
||||||
|
id="pv_production"
|
||||||
|
defaultMessage="PV Production"
|
||||||
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -369,12 +504,21 @@ function Overview(props: OverviewProps) {
|
||||||
}}
|
}}
|
||||||
></Box>
|
></Box>
|
||||||
</Box>
|
</Box>
|
||||||
<ReactApexChart
|
<div onDoubleClick={handleDoubleClick}>
|
||||||
options={getChartOptions(chartOverview.dcPower)}
|
<ReactApexChart
|
||||||
series={chartData.dcPower}
|
options={{
|
||||||
type="area"
|
...getChartOptions(chartOverview.pvProduction),
|
||||||
height={350}
|
chart: {
|
||||||
/>
|
events: {
|
||||||
|
beforeZoom: handleBeforeZoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
series={chartData.pvProduction}
|
||||||
|
type="area"
|
||||||
|
height={350}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={6} xs={12}>
|
<Grid item md={6} xs={12}>
|
||||||
|
@ -393,7 +537,10 @@ function Overview(props: OverviewProps) {
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle1" noWrap>
|
<Typography variant="subtitle1" noWrap>
|
||||||
Grid Power
|
<FormattedMessage
|
||||||
|
id="grid_power"
|
||||||
|
defaultMessage="Grid Power"
|
||||||
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -406,12 +553,21 @@ function Overview(props: OverviewProps) {
|
||||||
}}
|
}}
|
||||||
></Box>
|
></Box>
|
||||||
</Box>
|
</Box>
|
||||||
<ReactApexChart
|
<div onDoubleClick={handleDoubleClick}>
|
||||||
options={getChartOptions(chartOverview.gridPower)}
|
<ReactApexChart
|
||||||
series={chartData.gridPower}
|
options={{
|
||||||
type="area"
|
...getChartOptions(chartOverview.gridPower),
|
||||||
height={350}
|
chart: {
|
||||||
/>
|
events: {
|
||||||
|
beforeZoom: handleBeforeZoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
series={chartData.gridPower}
|
||||||
|
type="area"
|
||||||
|
height={350}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
@ -438,7 +594,10 @@ function Overview(props: OverviewProps) {
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle1" noWrap>
|
<Typography variant="subtitle1" noWrap>
|
||||||
PV Production
|
<FormattedMessage
|
||||||
|
id="battery_power"
|
||||||
|
defaultMessage="Battery Power"
|
||||||
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -451,12 +610,21 @@ function Overview(props: OverviewProps) {
|
||||||
}}
|
}}
|
||||||
></Box>
|
></Box>
|
||||||
</Box>
|
</Box>
|
||||||
<ReactApexChart
|
<div onDoubleClick={handleDoubleClick}>
|
||||||
options={getChartOptions(chartOverview.pvProduction)}
|
<ReactApexChart
|
||||||
series={chartData.pvProduction}
|
options={{
|
||||||
type="area"
|
...getChartOptions(chartOverview.dcPower),
|
||||||
height={350}
|
chart: {
|
||||||
/>
|
events: {
|
||||||
|
beforeZoom: handleBeforeZoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
series={chartData.dcPower}
|
||||||
|
type="area"
|
||||||
|
height={350}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid item md={6} xs={12}>
|
<Grid item md={6} xs={12}>
|
||||||
|
@ -475,7 +643,10 @@ function Overview(props: OverviewProps) {
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
<Box>
|
<Box>
|
||||||
<Typography variant="subtitle1" noWrap>
|
<Typography variant="subtitle1" noWrap>
|
||||||
DC Bus Voltage
|
<FormattedMessage
|
||||||
|
id="dc_voltage"
|
||||||
|
defaultMessage="DC Bus Voltage"
|
||||||
|
/>
|
||||||
</Typography>
|
</Typography>
|
||||||
</Box>
|
</Box>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -488,12 +659,21 @@ function Overview(props: OverviewProps) {
|
||||||
}}
|
}}
|
||||||
></Box>
|
></Box>
|
||||||
</Box>
|
</Box>
|
||||||
<ReactApexChart
|
<div onDoubleClick={handleDoubleClick}>
|
||||||
options={getChartOptions(chartOverview.dcBusVoltage)}
|
<ReactApexChart
|
||||||
series={chartData.dcBusVoltage}
|
options={{
|
||||||
type="line"
|
...getChartOptions(chartOverview.dcBusVoltage),
|
||||||
height={350}
|
chart: {
|
||||||
/>
|
events: {
|
||||||
|
beforeZoom: handleBeforeZoom
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}}
|
||||||
|
series={chartData.dcBusVoltage}
|
||||||
|
type="area"
|
||||||
|
height={350}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
|
@ -23,6 +23,8 @@ function Topology(props: TopologyProps) {
|
||||||
setShowValues(!showValues);
|
setShowValues(!showValues);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Container maxWidth="xl" style={{ backgroundColor: 'white' }}>
|
<Container maxWidth="xl" style={{ backgroundColor: 'white' }}>
|
||||||
<Grid container>
|
<Grid container>
|
||||||
|
@ -56,7 +58,7 @@ function Topology(props: TopologyProps) {
|
||||||
xs={12}
|
xs={12}
|
||||||
md={12}
|
md={12}
|
||||||
style={{
|
style={{
|
||||||
height: '600px',
|
height: isMobile ? '550px' : '600px',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'row',
|
flexDirection: 'row',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -66,7 +68,9 @@ function Topology(props: TopologyProps) {
|
||||||
<TopologyColumn
|
<TopologyColumn
|
||||||
centerBox={{
|
centerBox={{
|
||||||
title: 'Grid',
|
title: 'Grid',
|
||||||
data: props.values.grid
|
data: props.values.grid,
|
||||||
|
connected:
|
||||||
|
props.values.gridBox.values[0].value.toString() != 'Disabled'
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
orientation: 'horizontal',
|
orientation: 'horizontal',
|
||||||
|
@ -85,7 +89,10 @@ function Topology(props: TopologyProps) {
|
||||||
<TopologyColumn
|
<TopologyColumn
|
||||||
topBox={{
|
topBox={{
|
||||||
title: 'Pv Inverter',
|
title: 'Pv Inverter',
|
||||||
data: props.values.gridBusToPvOnGridbusConnection
|
data: props.values.gridBusToPvOnGridbusConnection,
|
||||||
|
connected:
|
||||||
|
props.values.pvOnAcGridBox.values[0].value.toString() !=
|
||||||
|
'Disabled'
|
||||||
}}
|
}}
|
||||||
topConnection={{
|
topConnection={{
|
||||||
orientation: 'vertical',
|
orientation: 'vertical',
|
||||||
|
@ -101,22 +108,27 @@ function Topology(props: TopologyProps) {
|
||||||
}}
|
}}
|
||||||
centerBox={{
|
centerBox={{
|
||||||
title: 'Grid Bus',
|
title: 'Grid Bus',
|
||||||
data: props.values.gridBus
|
data: props.values.gridBus,
|
||||||
|
connected: true
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
orientation: 'horizontal',
|
orientation: 'horizontal',
|
||||||
data: props.values.gridBusToIslandBusConnection,
|
|
||||||
amount: props.values.gridBusToIslandBusConnection
|
amount: props.values.gridBusToIslandBusConnection
|
||||||
? getAmount(
|
? getAmount(
|
||||||
highestConnectionValue,
|
highestConnectionValue,
|
||||||
props.values.gridBusToIslandBusConnection.values
|
props.values.gridBusToIslandBusConnection.values
|
||||||
)
|
)
|
||||||
: 0,
|
: 0,
|
||||||
|
data: props.values.gridBusToIslandBusConnection,
|
||||||
showValues: showValues
|
showValues: showValues
|
||||||
}}
|
}}
|
||||||
bottomBox={{
|
bottomBox={{
|
||||||
title: 'AC Loads',
|
title: 'AC Loads',
|
||||||
data: props.values.gridBusToLoadOnGridBusConnection
|
data: props.values.gridBusToLoadOnGridBusConnection,
|
||||||
|
connected:
|
||||||
|
props.values.loadOnAcGridBox.values[0].value.toString() !=
|
||||||
|
'Disabled'
|
||||||
}}
|
}}
|
||||||
bottomConnection={{
|
bottomConnection={{
|
||||||
orientation: 'vertical',
|
orientation: 'vertical',
|
||||||
|
@ -136,7 +148,10 @@ function Topology(props: TopologyProps) {
|
||||||
<TopologyColumn
|
<TopologyColumn
|
||||||
topBox={{
|
topBox={{
|
||||||
title: 'Pv Inverter',
|
title: 'Pv Inverter',
|
||||||
data: props.values.pvOnIslandBusToIslandBusConnection
|
data: props.values.pvOnIslandBusToIslandBusConnection,
|
||||||
|
connected:
|
||||||
|
props.values.pvOnIslandBusBox.values[0].value.toString() !=
|
||||||
|
'Disabled'
|
||||||
}}
|
}}
|
||||||
topConnection={{
|
topConnection={{
|
||||||
orientation: 'vertical',
|
orientation: 'vertical',
|
||||||
|
@ -152,7 +167,8 @@ function Topology(props: TopologyProps) {
|
||||||
}}
|
}}
|
||||||
centerBox={{
|
centerBox={{
|
||||||
title: 'Island Bus',
|
title: 'Island Bus',
|
||||||
data: props.values.islandBus
|
data: props.values.islandBus,
|
||||||
|
connected: true
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
orientation: 'horizontal',
|
orientation: 'horizontal',
|
||||||
|
@ -167,7 +183,10 @@ function Topology(props: TopologyProps) {
|
||||||
}}
|
}}
|
||||||
bottomBox={{
|
bottomBox={{
|
||||||
title: 'AC Loads',
|
title: 'AC Loads',
|
||||||
data: props.values.islandBusToLoadOnIslandBusConnection
|
data: props.values.islandBusToLoadOnIslandBusConnection,
|
||||||
|
connected:
|
||||||
|
props.values.loadOnIslandBusBox.values[0].value.toString() !=
|
||||||
|
'Disabled'
|
||||||
}}
|
}}
|
||||||
bottomConnection={{
|
bottomConnection={{
|
||||||
orientation: 'vertical',
|
orientation: 'vertical',
|
||||||
|
@ -187,7 +206,8 @@ function Topology(props: TopologyProps) {
|
||||||
<TopologyColumn
|
<TopologyColumn
|
||||||
centerBox={{
|
centerBox={{
|
||||||
title: 'AC-DC',
|
title: 'AC-DC',
|
||||||
data: props.values.inverter
|
data: props.values.inverter,
|
||||||
|
connected: true
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
orientation: 'horizontal',
|
orientation: 'horizontal',
|
||||||
|
@ -205,8 +225,10 @@ function Topology(props: TopologyProps) {
|
||||||
/>
|
/>
|
||||||
<TopologyColumn
|
<TopologyColumn
|
||||||
topBox={{
|
topBox={{
|
||||||
title: 'Pv DcDc',
|
title: 'Pv DC-DC',
|
||||||
data: props.values.pvOnDcBusToDcBusConnection
|
data: props.values.pvOnDcBusToDcBusConnection,
|
||||||
|
connected:
|
||||||
|
props.values.pvOnDcBox.values[0].value.toString() != 'Disabled'
|
||||||
}}
|
}}
|
||||||
topConnection={{
|
topConnection={{
|
||||||
orientation: 'vertical',
|
orientation: 'vertical',
|
||||||
|
@ -222,7 +244,8 @@ function Topology(props: TopologyProps) {
|
||||||
}}
|
}}
|
||||||
centerBox={{
|
centerBox={{
|
||||||
title: 'DC Link',
|
title: 'DC Link',
|
||||||
data: props.values.dcBus
|
data: props.values.dcBus,
|
||||||
|
connected: true
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
orientation: 'horizontal',
|
orientation: 'horizontal',
|
||||||
|
@ -237,7 +260,10 @@ function Topology(props: TopologyProps) {
|
||||||
}}
|
}}
|
||||||
bottomBox={{
|
bottomBox={{
|
||||||
title: 'DC Loads',
|
title: 'DC Loads',
|
||||||
data: props.values.dcBusToLoadOnDcConnection
|
data: props.values.dcBusToLoadOnDcConnection,
|
||||||
|
connected:
|
||||||
|
props.values.loadOnDcBox.values[0].value.toString() !=
|
||||||
|
'Disabled'
|
||||||
}}
|
}}
|
||||||
bottomConnection={{
|
bottomConnection={{
|
||||||
orientation: 'vertical',
|
orientation: 'vertical',
|
||||||
|
@ -257,7 +283,8 @@ function Topology(props: TopologyProps) {
|
||||||
<TopologyColumn
|
<TopologyColumn
|
||||||
centerBox={{
|
centerBox={{
|
||||||
title: 'DC-DC',
|
title: 'DC-DC',
|
||||||
data: props.values.dcDc
|
data: props.values.dcDc,
|
||||||
|
connected: true
|
||||||
}}
|
}}
|
||||||
centerConnection={{
|
centerConnection={{
|
||||||
orientation: 'horizontal',
|
orientation: 'horizontal',
|
||||||
|
@ -276,7 +303,9 @@ function Topology(props: TopologyProps) {
|
||||||
<TopologyColumn
|
<TopologyColumn
|
||||||
centerBox={{
|
centerBox={{
|
||||||
title: 'Battery',
|
title: 'Battery',
|
||||||
data: props.values.battery
|
data: props.values.battery,
|
||||||
|
connected:
|
||||||
|
props.values.batteryBox.values[0].value.toString() != 'Disabled'
|
||||||
}}
|
}}
|
||||||
isLast={true}
|
isLast={true}
|
||||||
isFirst={false}
|
isFirst={false}
|
||||||
|
|
|
@ -5,6 +5,17 @@
|
||||||
border-bottom: none;
|
border-bottom: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.isMobile.horizontalLine {
|
||||||
|
position: absolute;
|
||||||
|
overflow: hidden;
|
||||||
|
border-left: none;
|
||||||
|
margin-left: 142px;
|
||||||
|
border-right: none;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
.horizontalLine {
|
.horizontalLine {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
|
@ -15,59 +26,60 @@
|
||||||
|
|
||||||
.dotRight {
|
.dotRight {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: 35px;
|
|
||||||
width: 3px;
|
width: 3px;
|
||||||
height: 3px;
|
height: 3px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: #f7b34d;
|
background-color: #f7b34d;
|
||||||
animation: rightflow 2s linear infinite;
|
animation: rightflow 2s linear infinite;
|
||||||
|
transform: translateX(-39px); /* Initial position off-screen */
|
||||||
}
|
}
|
||||||
|
|
||||||
.dotLeft {
|
.dotLeft {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-left: 35px;
|
|
||||||
width: 3px;
|
width: 3px;
|
||||||
height: 3px;
|
height: 3px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: #f7b34d;
|
background-color: #f7b34d;
|
||||||
animation: leftflow 2s linear infinite;
|
animation: leftflow 2s linear infinite;
|
||||||
|
transform: translateX(-39px); /* Initial position off-screen */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
.verticalDotDown {
|
.verticalDotDown {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
margin-top: 35px;
|
|
||||||
width: 3px;
|
width: 3px;
|
||||||
height: 3px;
|
height: 3px;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
background-color: #f7b34d;
|
background-color: #f7b34d;
|
||||||
animation: verticalDownFlow 2s linear infinite;
|
animation: verticalDownFlow 2s linear infinite;
|
||||||
|
transform: translateY(-39px);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@keyframes rightflow {
|
@keyframes rightflow {
|
||||||
0% {
|
0% {
|
||||||
left: -35px;
|
transform: translateX(-35px);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
left: 110%;
|
transform: translateX(1000%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes leftflow {
|
@keyframes leftflow {
|
||||||
0% {
|
0% {
|
||||||
left: 110px;
|
transform: translateX(1000%);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
left: -35px;
|
transform: translateX(-35px);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@keyframes verticalDownFlow {
|
@keyframes verticalDownFlow {
|
||||||
0% {
|
0% {
|
||||||
top: -35px;
|
transform: translateY(-35px);
|
||||||
}
|
}
|
||||||
100% {
|
100% {
|
||||||
top: 100%;
|
transform: translateY(1050%);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,6 +12,7 @@ import BatteryCharging60Icon from '@mui/icons-material/BatteryCharging60';
|
||||||
import OutletIcon from '@mui/icons-material/Outlet';
|
import OutletIcon from '@mui/icons-material/Outlet';
|
||||||
import SolarPowerIcon from '@mui/icons-material/SolarPower';
|
import SolarPowerIcon from '@mui/icons-material/SolarPower';
|
||||||
import PowerInputIcon from '@mui/icons-material/PowerInput';
|
import PowerInputIcon from '@mui/icons-material/PowerInput';
|
||||||
|
import SignalWifiConnectedNoInternet4Icon from '@mui/icons-material/SignalWifiConnectedNoInternet4';
|
||||||
import BoltIcon from '@mui/icons-material/Bolt';
|
import BoltIcon from '@mui/icons-material/Bolt';
|
||||||
import { BoxData } from '../Log/graph.util';
|
import { BoxData } from '../Log/graph.util';
|
||||||
import inverterImage from 'src/Resources/images/inverter.png';
|
import inverterImage from 'src/Resources/images/inverter.png';
|
||||||
|
@ -21,6 +22,7 @@ import converterImage from 'src/Resources/images/converter.png';
|
||||||
export interface TopologyBoxProps {
|
export interface TopologyBoxProps {
|
||||||
title: string;
|
title: string;
|
||||||
data?: BoxData;
|
data?: BoxData;
|
||||||
|
connected?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
const isInt = (value: number) => {
|
const isInt = (value: number) => {
|
||||||
|
@ -45,7 +47,7 @@ function formatPower(value) {
|
||||||
magnitude++;
|
magnitude++;
|
||||||
}
|
}
|
||||||
|
|
||||||
const roundedValue = value.toFixed(2);
|
const roundedValue = value.toFixed(1);
|
||||||
|
|
||||||
return negative === false
|
return negative === false
|
||||||
? `${roundedValue} ${prefixes[magnitude]}`
|
? `${roundedValue} ${prefixes[magnitude]}`
|
||||||
|
@ -54,28 +56,32 @@ function formatPower(value) {
|
||||||
|
|
||||||
function TopologyBox(props: TopologyBoxProps) {
|
function TopologyBox(props: TopologyBoxProps) {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Card
|
<Card
|
||||||
sx={{
|
sx={{
|
||||||
visibility:
|
visibility:
|
||||||
props.data && props.data.values[0].value != 0 ? 'visible' : 'hidden',
|
//props.data && props.data.values[0].value != 0 ? 'visible' : 'hidden',
|
||||||
width: '104px',
|
props.connected ? 'visible' : 'hidden',
|
||||||
|
|
||||||
|
width: isMobile ? '90px' : '104px',
|
||||||
height:
|
height:
|
||||||
props.title === 'Battery'
|
props.title === 'Battery'
|
||||||
? '165px'
|
? '165px'
|
||||||
: props.title === 'AC Loads' ||
|
: props.title === 'AC Loads' ||
|
||||||
props.title === 'DC Loads' ||
|
props.title === 'DC Loads' ||
|
||||||
props.title === 'Pv Inverter' ||
|
props.title === 'Pv Inverter' ||
|
||||||
props.title === 'Pv DcDc'
|
props.title === 'Pv DC-DC'
|
||||||
? '100px'
|
? '100px'
|
||||||
: '150px',
|
: '150px',
|
||||||
backgroundColor:
|
backgroundColor: !props.data
|
||||||
props.title === 'Grid Bus' ||
|
? 'darkgrey'
|
||||||
props.title === 'Island Bus' ||
|
: props.title === 'Grid Bus' ||
|
||||||
props.title === 'DC Link'
|
props.title === 'Island Bus' ||
|
||||||
? '#f7b34d'
|
props.title === 'DC Link'
|
||||||
: 'none',
|
? '#f7b34d'
|
||||||
|
: 'none',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
|
@ -93,7 +99,7 @@ function TopologyBox(props: TopologyBoxProps) {
|
||||||
textAlign: 'center'
|
textAlign: 'center'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{props.title === 'Battery' && (
|
{props.data && props.title === 'Battery' && (
|
||||||
<Typography variant="h5" noWrap>
|
<Typography variant="h5" noWrap>
|
||||||
{props.title} (x{props.data.values.length - 4})
|
{props.title} (x{props.data.values.length - 4})
|
||||||
</Typography>
|
</Typography>
|
||||||
|
@ -104,7 +110,20 @@ function TopologyBox(props: TopologyBoxProps) {
|
||||||
</Typography>
|
</Typography>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{props.title === 'AC-DC' && (
|
{!props.data && (
|
||||||
|
<SignalWifiConnectedNoInternet4Icon
|
||||||
|
style={{
|
||||||
|
fontSize: 40,
|
||||||
|
color: 'black',
|
||||||
|
backgroundColor: 'darkgrey',
|
||||||
|
padding: '5px',
|
||||||
|
borderTopLeftRadius: '4px',
|
||||||
|
borderTopRightRadius: '4px'
|
||||||
|
}}
|
||||||
|
></SignalWifiConnectedNoInternet4Icon>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{props.data && props.title === 'AC-DC' && (
|
||||||
<img
|
<img
|
||||||
src={inverterImage}
|
src={inverterImage}
|
||||||
style={{
|
style={{
|
||||||
|
@ -115,7 +134,7 @@ function TopologyBox(props: TopologyBoxProps) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{props.title === 'DC Link' && (
|
{props.data && props.title === 'DC Link' && (
|
||||||
<PowerInputIcon
|
<PowerInputIcon
|
||||||
style={{
|
style={{
|
||||||
fontSize: 40,
|
fontSize: 40,
|
||||||
|
@ -128,7 +147,7 @@ function TopologyBox(props: TopologyBoxProps) {
|
||||||
></PowerInputIcon>
|
></PowerInputIcon>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{props.title === 'DC-DC' && (
|
{props.data && props.title === 'DC-DC' && (
|
||||||
<img
|
<img
|
||||||
src={converterImage}
|
src={converterImage}
|
||||||
style={{
|
style={{
|
||||||
|
@ -139,18 +158,20 @@ function TopologyBox(props: TopologyBoxProps) {
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{(props.title === 'Grid Bus' || props.title === 'Island Bus') && (
|
{props.data &&
|
||||||
<img
|
(props.title === 'Grid Bus' || props.title === 'Island Bus') && (
|
||||||
src={acCurrentImage}
|
<img
|
||||||
style={{
|
src={acCurrentImage}
|
||||||
width: '40px',
|
style={{
|
||||||
height: '40px',
|
width: '40px',
|
||||||
backgroundColor: '#f7b34d'
|
height: '40px',
|
||||||
}}
|
backgroundColor: '#f7b34d'
|
||||||
/>
|
}}
|
||||||
)}
|
/>
|
||||||
|
)}
|
||||||
|
|
||||||
{props.title != 'AC-DC' &&
|
{props.data &&
|
||||||
|
props.title != 'AC-DC' &&
|
||||||
props.title != 'DC Link' &&
|
props.title != 'DC Link' &&
|
||||||
props.title != 'DC-DC' && (
|
props.title != 'DC-DC' && (
|
||||||
<AvatarWrapper
|
<AvatarWrapper
|
||||||
|
@ -163,7 +184,7 @@ function TopologyBox(props: TopologyBoxProps) {
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{(props.title === 'Pv Inverter' ||
|
{(props.title === 'Pv Inverter' ||
|
||||||
props.title === 'Pv DcDc') && (
|
props.title === 'Pv DC-DC') && (
|
||||||
<SolarPowerIcon
|
<SolarPowerIcon
|
||||||
style={{
|
style={{
|
||||||
fontSize: 30,
|
fontSize: 30,
|
||||||
|
@ -212,6 +233,7 @@ function TopologyBox(props: TopologyBoxProps) {
|
||||||
)}
|
)}
|
||||||
|
|
||||||
{props.data && <Divider sx={{ width: '150%', marginTop: '0px' }} />}
|
{props.data && <Divider sx={{ width: '150%', marginTop: '0px' }} />}
|
||||||
|
|
||||||
{props.data && (
|
{props.data && (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
|
|
@ -16,6 +16,8 @@ interface TopologyColumnProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function TopologyColumn(props: TopologyColumnProps) {
|
function TopologyColumn(props: TopologyColumnProps) {
|
||||||
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
|
@ -23,7 +25,7 @@ function TopologyColumn(props: TopologyColumnProps) {
|
||||||
flexDirection: 'column',
|
flexDirection: 'column',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
marginLeft: props.isFirst ? '0px' : '68px'
|
marginLeft: props.isFirst ? '0px' : isMobile ? '52px' : '68px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<TopologyBox {...props.topBox}></TopologyBox>
|
<TopologyBox {...props.topBox}></TopologyBox>
|
||||||
|
|
|
@ -45,7 +45,7 @@ function formatPower(value) {
|
||||||
magnitude++;
|
magnitude++;
|
||||||
}
|
}
|
||||||
|
|
||||||
const roundedValue = value.toFixed(2);
|
const roundedValue = value.toFixed(1);
|
||||||
|
|
||||||
return negative === false
|
return negative === false
|
||||||
? `${roundedValue} ${prefixes[magnitude]}`
|
? `${roundedValue} ${prefixes[magnitude]}`
|
||||||
|
@ -61,12 +61,14 @@ function TopologyFlow(props: TopologyFlowProps) {
|
||||||
{ animationDelay: string; left: string }[]
|
{ animationDelay: string; left: string }[]
|
||||||
>([]);
|
>([]);
|
||||||
|
|
||||||
const numOfDots = 400;
|
const numOfDots = 200;
|
||||||
const minNumberOfDots = 100;
|
const minNumberOfDots = 50;
|
||||||
const minHeight = 2;
|
const minHeight = 2;
|
||||||
const maxHeight = 70;
|
const maxHeight = 70;
|
||||||
const minWidth = 2;
|
const minWidth = 2;
|
||||||
const maxWidth = 68;
|
const maxWidth = 68;
|
||||||
|
const maxWidthMobile = 50;
|
||||||
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
||||||
const desiredNumOfDots =
|
const desiredNumOfDots =
|
||||||
numOfDots * props.amount < minNumberOfDots
|
numOfDots * props.amount < minNumberOfDots
|
||||||
|
@ -83,7 +85,7 @@ function TopologyFlow(props: TopologyFlowProps) {
|
||||||
setDotStyleVertical(
|
setDotStyleVertical(
|
||||||
Array.from({ length: desiredNumOfDots }, () => getRandomStyleVertical())
|
Array.from({ length: desiredNumOfDots }, () => getRandomStyleVertical())
|
||||||
);
|
);
|
||||||
}, []);
|
}, [desiredNumOfDots]);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -91,7 +93,7 @@ function TopologyFlow(props: TopologyFlowProps) {
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
marginLeft: '170px',
|
marginLeft: isMobile ? '142px' : '170px',
|
||||||
marginTop: '2px',
|
marginTop: '2px',
|
||||||
backgroundColor: 'transparent',
|
backgroundColor: 'transparent',
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
|
@ -103,6 +105,7 @@ function TopologyFlow(props: TopologyFlowProps) {
|
||||||
{props.showValues && props.data.values[0].value != 0 && (
|
{props.showValues && props.data.values[0].value != 0 && (
|
||||||
<Typography
|
<Typography
|
||||||
sx={{
|
sx={{
|
||||||
|
marginTop: '1px',
|
||||||
color: 'black',
|
color: 'black',
|
||||||
bgcolor: 'background.paper',
|
bgcolor: 'background.paper',
|
||||||
borderRadius: 5,
|
borderRadius: 5,
|
||||||
|
@ -116,16 +119,16 @@ function TopologyFlow(props: TopologyFlowProps) {
|
||||||
)}
|
)}
|
||||||
</Box>
|
</Box>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className={props.orientation === 'vertical' ? 'line' : 'horizontalLine'}
|
className={`${
|
||||||
|
props.orientation === 'vertical' ? 'line' : 'horizontalLine'
|
||||||
|
} ${isMobile ? 'isMobile' : ''}`}
|
||||||
style={{
|
style={{
|
||||||
display: 'flex',
|
display: 'flex',
|
||||||
alignItems: 'center',
|
alignItems: 'center',
|
||||||
justifyContent: 'center',
|
justifyContent: 'center',
|
||||||
position:
|
position:
|
||||||
props.orientation === 'horizontal' ? 'absolute' : 'relative',
|
props.orientation === 'horizontal' ? 'absolute' : 'relative',
|
||||||
|
|
||||||
height:
|
height:
|
||||||
props.orientation === 'horizontal'
|
props.orientation === 'horizontal'
|
||||||
? `${Math.min(
|
? `${Math.min(
|
||||||
|
@ -133,13 +136,14 @@ function TopologyFlow(props: TopologyFlowProps) {
|
||||||
maxHeight
|
maxHeight
|
||||||
)}px`
|
)}px`
|
||||||
: `${maxHeight}px`,
|
: `${maxHeight}px`,
|
||||||
|
|
||||||
width:
|
width:
|
||||||
props.orientation === 'vertical'
|
props.orientation === 'vertical'
|
||||||
? `${Math.min(
|
? `${Math.min(
|
||||||
Math.max(props.amount * maxWidth, minWidth),
|
Math.max(props.amount * maxWidth, minWidth),
|
||||||
maxWidth
|
maxWidth
|
||||||
)}px`
|
)}px`
|
||||||
|
: isMobile
|
||||||
|
? `${maxWidthMobile}px`
|
||||||
: `${maxWidth}px`
|
: `${maxWidth}px`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
|
|
@ -110,7 +110,7 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
||||||
height: '23px',
|
height: '23px',
|
||||||
color: 'red',
|
color: 'red',
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
marginLeft: '21px',
|
marginLeft: '30px',
|
||||||
marginTop: '30px'
|
marginTop: '30px'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -123,7 +123,7 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
||||||
size={20}
|
size={20}
|
||||||
sx={{
|
sx={{
|
||||||
color: '#f7b34d',
|
color: '#f7b34d',
|
||||||
marginLeft: '20px',
|
marginLeft: '22px',
|
||||||
marginTop: '30px'
|
marginTop: '30px'
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
|
@ -136,7 +136,7 @@ function CustomTreeItem(props: CustomTreeItemProps) {
|
||||||
width: '20px',
|
width: '20px',
|
||||||
height: '20px',
|
height: '20px',
|
||||||
borderRadius: '50%',
|
borderRadius: '50%',
|
||||||
marginLeft: '20px',
|
marginLeft: '17px',
|
||||||
backgroundColor:
|
backgroundColor:
|
||||||
status === 2
|
status === 2
|
||||||
? 'red'
|
? 'red'
|
||||||
|
|
|
@ -8,9 +8,11 @@ import {
|
||||||
Container,
|
Container,
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
Modal,
|
||||||
Tab,
|
Tab,
|
||||||
Tabs,
|
Tabs,
|
||||||
TextField,
|
TextField,
|
||||||
|
Typography,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Close as CloseIcon } from '@mui/icons-material';
|
import { Close as CloseIcon } from '@mui/icons-material';
|
||||||
|
@ -46,6 +48,7 @@ function Folder(props: singleFolderProps) {
|
||||||
const selectedBulkActions = selectedUser !== -1;
|
const selectedBulkActions = selectedUser !== -1;
|
||||||
const searchParams = new URLSearchParams(location.search);
|
const searchParams = new URLSearchParams(location.search);
|
||||||
const folderId = parseInt(searchParams.get('folder'));
|
const folderId = parseInt(searchParams.get('folder'));
|
||||||
|
const [openModalDeleteFolder, setOpenModalDeleteFolder] = useState(false);
|
||||||
|
|
||||||
const installationContext = useContext(InstallationsContext);
|
const installationContext = useContext(InstallationsContext);
|
||||||
const {
|
const {
|
||||||
|
@ -126,7 +129,18 @@ function Folder(props: singleFolderProps) {
|
||||||
const handleDeleteFolder = (e) => {
|
const handleDeleteFolder = (e) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(false);
|
setError(false);
|
||||||
|
setOpenModalDeleteFolder(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteFolderModalHandle = (e) => {
|
||||||
|
setOpenModalDeleteFolder(false);
|
||||||
deleteFolder(formValues);
|
deleteFolder(formValues);
|
||||||
|
setLoading(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteFolderModalHandleCancel = (e) => {
|
||||||
|
setOpenModalDeleteFolder(false);
|
||||||
|
setLoading(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleFolderFormSubmit = () => {
|
const handleFolderFormSubmit = () => {
|
||||||
|
@ -155,6 +169,80 @@ function Folder(props: singleFolderProps) {
|
||||||
if (folderId == props.current_folder.id) {
|
if (folderId == props.current_folder.id) {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
{openModalDeleteFolder && (
|
||||||
|
<Modal
|
||||||
|
open={openModalDeleteFolder}
|
||||||
|
onClose={() => setOpenModalDeleteFolder(false)}
|
||||||
|
aria-labelledby="error-modal"
|
||||||
|
aria-describedby="error-modal-description"
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: 350,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderRadius: 4,
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
gutterBottom
|
||||||
|
sx={{ fontWeight: 'bold' }}
|
||||||
|
>
|
||||||
|
Do you want to delete this folder?
|
||||||
|
</Typography>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
gutterBottom
|
||||||
|
sx={{ fontSize: '0.875rem' }}
|
||||||
|
>
|
||||||
|
All installations of this folder will be deleted.
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginTop: 2,
|
||||||
|
textTransform: 'none',
|
||||||
|
bgcolor: '#ffc04d',
|
||||||
|
color: '#111111',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
onClick={deleteFolderModalHandle}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginTop: 2,
|
||||||
|
marginLeft: 2,
|
||||||
|
textTransform: 'none',
|
||||||
|
bgcolor: '#ffc04d',
|
||||||
|
color: '#111111',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
onClick={deleteFolderModalHandleCancel}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
{openModalFolder && (
|
{openModalFolder && (
|
||||||
<FolderForm
|
<FolderForm
|
||||||
cancel={handleFormCancel}
|
cancel={handleFormCancel}
|
||||||
|
@ -169,7 +257,7 @@ function Folder(props: singleFolderProps) {
|
||||||
parentid={props.current_folder.id}
|
parentid={props.current_folder.id}
|
||||||
/>
|
/>
|
||||||
)}
|
)}
|
||||||
<Grid item xs={12} md={9}>
|
<Grid item xs={12} md={12}>
|
||||||
<TabsContainerWrapper>
|
<TabsContainerWrapper>
|
||||||
<Tabs
|
<Tabs
|
||||||
onChange={handleTabsChange}
|
onChange={handleTabsChange}
|
||||||
|
@ -253,30 +341,14 @@ function Folder(props: singleFolderProps) {
|
||||||
{currentUser.hasWriteAccess && (
|
{currentUser.hasWriteAccess && (
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleFolderInformationUpdate}
|
onClick={handleDeleteFolder}
|
||||||
sx={{
|
|
||||||
marginLeft: '10px'
|
|
||||||
}}
|
|
||||||
disabled={!areRequiredFieldsFilled()}
|
|
||||||
>
|
|
||||||
<FormattedMessage
|
|
||||||
id="applyChanges"
|
|
||||||
defaultMessage="Apply Changes"
|
|
||||||
/>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{currentUser.hasWriteAccess && (
|
|
||||||
<Button
|
|
||||||
variant="contained"
|
|
||||||
onClick={handleNewInstallationInsertion}
|
|
||||||
sx={{
|
sx={{
|
||||||
marginLeft: '10px'
|
marginLeft: '10px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="addNewInstallation"
|
id="deleteFolder"
|
||||||
defaultMessage="Add new installation"
|
defaultMessage="Delete Folder"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
@ -299,14 +371,30 @@ function Folder(props: singleFolderProps) {
|
||||||
{currentUser.hasWriteAccess && (
|
{currentUser.hasWriteAccess && (
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
onClick={handleDeleteFolder}
|
onClick={handleNewInstallationInsertion}
|
||||||
sx={{
|
sx={{
|
||||||
marginLeft: '10px'
|
marginLeft: '10px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="deleteFolder"
|
id="addNewInstallation"
|
||||||
defaultMessage="Delete Folder"
|
defaultMessage="Add new installation"
|
||||||
|
/>
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{currentUser.hasWriteAccess && (
|
||||||
|
<Button
|
||||||
|
variant="contained"
|
||||||
|
onClick={handleFolderInformationUpdate}
|
||||||
|
sx={{
|
||||||
|
marginLeft: '10px'
|
||||||
|
}}
|
||||||
|
disabled={!areRequiredFieldsFilled()}
|
||||||
|
>
|
||||||
|
<FormattedMessage
|
||||||
|
id="applyChanges"
|
||||||
|
defaultMessage="Apply Changes"
|
||||||
/>
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
|
|
|
@ -7,7 +7,6 @@ import CustomTreeItem from './CustomTreeItem';
|
||||||
import Installation from '../Installations/Installation';
|
import Installation from '../Installations/Installation';
|
||||||
import Folder from './Folder';
|
import Folder from './Folder';
|
||||||
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
import { InstallationsContext } from 'src/contexts/InstallationsContextProvider';
|
||||||
import LogContextProvider from 'src/contexts/LogContextProvider';
|
|
||||||
|
|
||||||
function InstallationTree() {
|
function InstallationTree() {
|
||||||
const { foldersAndInstallations, fetchAllFoldersAndInstallations } =
|
const { foldersAndInstallations, fetchAllFoldersAndInstallations } =
|
||||||
|
@ -47,46 +46,44 @@ function InstallationTree() {
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<LogContextProvider>
|
<Grid container spacing={1} sx={{ marginTop: 0.1 }}>
|
||||||
<Grid container spacing={1} sx={{ marginTop: 0.1 }}>
|
<Grid item xs={12} md={12}>
|
||||||
<Grid item xs={12} md={3}>
|
<TreeView
|
||||||
<TreeView
|
defaultCollapseIcon={<ExpandMoreIcon />}
|
||||||
defaultCollapseIcon={<ExpandMoreIcon />}
|
defaultExpandIcon={<ChevronRightIcon />}
|
||||||
defaultExpandIcon={<ChevronRightIcon />}
|
defaultExpanded={['1Folder']}
|
||||||
defaultExpanded={['1Folder']}
|
>
|
||||||
>
|
{foldersAndInstallations.map((node, index) => {
|
||||||
{foldersAndInstallations.map((node, index) => {
|
|
||||||
return (
|
|
||||||
<TreeNode
|
|
||||||
key={node.id.toString() + node.type}
|
|
||||||
node={node}
|
|
||||||
parent_id={'0'}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
})}
|
|
||||||
</TreeView>
|
|
||||||
</Grid>
|
|
||||||
|
|
||||||
{foldersAndInstallations.map((installation) => {
|
|
||||||
if (installation.type == 'Installation') {
|
|
||||||
return (
|
return (
|
||||||
<Installation
|
<TreeNode
|
||||||
key={installation.id + installation.type}
|
key={node.id.toString() + node.type}
|
||||||
current_installation={installation}
|
node={node}
|
||||||
type="tree"
|
parent_id={'0'}
|
||||||
></Installation>
|
/>
|
||||||
);
|
);
|
||||||
} else {
|
})}
|
||||||
return (
|
</TreeView>
|
||||||
<Folder
|
|
||||||
key={installation.id + installation.type}
|
|
||||||
current_folder={installation}
|
|
||||||
></Folder>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
})}
|
|
||||||
</Grid>
|
</Grid>
|
||||||
</LogContextProvider>
|
|
||||||
|
{foldersAndInstallations.map((installation) => {
|
||||||
|
if (installation.type == 'Installation') {
|
||||||
|
return (
|
||||||
|
<Installation
|
||||||
|
key={installation.id + installation.type}
|
||||||
|
current_installation={installation}
|
||||||
|
type="tree"
|
||||||
|
></Installation>
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
return (
|
||||||
|
<Folder
|
||||||
|
key={installation.id + installation.type}
|
||||||
|
current_folder={installation}
|
||||||
|
></Folder>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
})}
|
||||||
|
</Grid>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -55,6 +55,8 @@ function folderForm(props: folderFormProps) {
|
||||||
props.cancel();
|
props.cancel();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
||||||
const areRequiredFieldsFilled = () => {
|
const areRequiredFieldsFilled = () => {
|
||||||
for (const field of requiredFields) {
|
for (const field of requiredFields) {
|
||||||
if (!formValues[field]) {
|
if (!formValues[field]) {
|
||||||
|
@ -75,7 +77,7 @@ function folderForm(props: folderFormProps) {
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '30%',
|
top: isMobile ? '50%' : '30%',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
width: 600,
|
width: 600,
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { Box, Grid, useTheme } from '@mui/material';
|
import { Box, Grid, useTheme } from '@mui/material';
|
||||||
import InstallationTree from './InstallationTree';
|
import InstallationTree from './InstallationTree';
|
||||||
import InstallationsContextProvider from 'src/contexts/InstallationsContextProvider';
|
import InstallationsContextProvider from 'src/contexts/InstallationsContextProvider';
|
||||||
|
import LogContextProvider from '../../../contexts/LogContextProvider';
|
||||||
|
|
||||||
function TreeView() {
|
function TreeView() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
@ -9,7 +10,9 @@ function TreeView() {
|
||||||
<InstallationsContextProvider>
|
<InstallationsContextProvider>
|
||||||
<Grid item xs={12}>
|
<Grid item xs={12}>
|
||||||
<Box p={4}>
|
<Box p={4}>
|
||||||
<InstallationTree />
|
<LogContextProvider>
|
||||||
|
<InstallationTree />
|
||||||
|
</LogContextProvider>
|
||||||
</Box>
|
</Box>
|
||||||
</Grid>
|
</Grid>
|
||||||
</InstallationsContextProvider>
|
</InstallationsContextProvider>
|
||||||
|
|
|
@ -14,6 +14,7 @@ import {
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { InnovEnergyUser } from 'src/interfaces/UserTypes';
|
import { InnovEnergyUser } from 'src/interfaces/UserTypes';
|
||||||
import User from './User';
|
import User from './User';
|
||||||
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
|
||||||
interface FlatUsersViewProps {
|
interface FlatUsersViewProps {
|
||||||
users: InnovEnergyUser[];
|
users: InnovEnergyUser[];
|
||||||
|
@ -23,10 +24,14 @@ interface FlatUsersViewProps {
|
||||||
const FlatUsersView = (props: FlatUsersViewProps) => {
|
const FlatUsersView = (props: FlatUsersViewProps) => {
|
||||||
const [selectedUser, setSelectedUser] = useState<number>(-1);
|
const [selectedUser, setSelectedUser] = useState<number>(-1);
|
||||||
const selectedBulkActions = selectedUser !== -1;
|
const selectedBulkActions = selectedUser !== -1;
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
const handleSelectOneUser = (installationID: number): void => {
|
const handleSelectOneUser = (installationID: number): void => {
|
||||||
if (selectedUser != installationID) {
|
if (selectedUser != installationID) {
|
||||||
setSelectedUser(installationID);
|
setSelectedUser(installationID);
|
||||||
|
// navigate(routes.users + '?user=' + installationID.toString(), {
|
||||||
|
// replace: true
|
||||||
|
// });
|
||||||
} else {
|
} else {
|
||||||
setSelectedUser(-1);
|
setSelectedUser(-1);
|
||||||
}
|
}
|
||||||
|
@ -46,12 +51,14 @@ const FlatUsersView = (props: FlatUsersViewProps) => {
|
||||||
return props.users.find((user) => user.id === id);
|
return props.users.find((user) => user.id === id);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Grid container spacing={1} sx={{ marginTop: '1px' }}>
|
<Grid container spacing={1} sx={{ marginTop: '1px' }}>
|
||||||
<Grid item xs={12} md={3}>
|
<Grid item xs={12} md={isMobile ? 5 : 4}>
|
||||||
<Card>
|
<Card>
|
||||||
<Divider />
|
<Divider />
|
||||||
<TableContainer>
|
<TableContainer sx={{ maxHeight: '520px', overflowY: 'auto' }}>
|
||||||
<Table>
|
<Table>
|
||||||
<TableHead>
|
<TableHead>
|
||||||
<TableRow>
|
<TableRow>
|
||||||
|
@ -67,7 +74,7 @@ const FlatUsersView = (props: FlatUsersViewProps) => {
|
||||||
isRowHovered === user.id
|
isRowHovered === user.id
|
||||||
? {
|
? {
|
||||||
cursor: 'pointer',
|
cursor: 'pointer',
|
||||||
backgroundColor: theme.colors.primary.lighter // Set your desired hover background color here
|
backgroundColor: theme.colors.primary.lighter
|
||||||
}
|
}
|
||||||
: {};
|
: {};
|
||||||
|
|
||||||
|
@ -113,7 +120,7 @@ const FlatUsersView = (props: FlatUsersViewProps) => {
|
||||||
</Card>
|
</Card>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|
||||||
{selectedBulkActions && (
|
{selectedUser && (
|
||||||
<User
|
<User
|
||||||
current_user={findUser(selectedUser)}
|
current_user={findUser(selectedUser)}
|
||||||
fetchDataAgain={props.fetchDataAgain}
|
fetchDataAgain={props.fetchDataAgain}
|
||||||
|
|
|
@ -8,9 +8,11 @@ import {
|
||||||
Container,
|
Container,
|
||||||
Grid,
|
Grid,
|
||||||
IconButton,
|
IconButton,
|
||||||
|
Modal,
|
||||||
Tab,
|
Tab,
|
||||||
Tabs,
|
Tabs,
|
||||||
TextField,
|
TextField,
|
||||||
|
Typography,
|
||||||
useTheme
|
useTheme
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { Close as CloseIcon } from '@mui/icons-material'; // Import CloseIcon
|
import { Close as CloseIcon } from '@mui/icons-material'; // Import CloseIcon
|
||||||
|
@ -19,6 +21,7 @@ import axiosConfig from 'src/Resources/axiosConfig';
|
||||||
import { InnovEnergyUser } from 'src/interfaces/UserTypes';
|
import { InnovEnergyUser } from 'src/interfaces/UserTypes';
|
||||||
import { TokenContext } from 'src/contexts/tokenContext';
|
import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
import { TabsContainerWrapper } from 'src/layouts/TabsContainerWrapper';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
interface singleUserProps {
|
interface singleUserProps {
|
||||||
current_user: InnovEnergyUser;
|
current_user: InnovEnergyUser;
|
||||||
|
@ -34,6 +37,8 @@ function User(props: singleUserProps) {
|
||||||
const [formValues, setFormValues] = useState(props.current_user);
|
const [formValues, setFormValues] = useState(props.current_user);
|
||||||
const tokencontext = useContext(TokenContext);
|
const tokencontext = useContext(TokenContext);
|
||||||
const { removeToken } = tokencontext;
|
const { removeToken } = tokencontext;
|
||||||
|
const tabs = [{ value: 'user', label: 'User' }];
|
||||||
|
const [openModalDeleteFolder, setOpenModalDeleteFolder] = useState(false);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
setFormValues(props.current_user);
|
setFormValues(props.current_user);
|
||||||
|
@ -43,8 +48,6 @@ function User(props: singleUserProps) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const tabs = [{ value: 'user', label: 'User' }];
|
|
||||||
|
|
||||||
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
const handleTabsChange = (_event: ChangeEvent<{}>, value: string): void => {
|
||||||
setCurrentTab(value);
|
setCurrentTab(value);
|
||||||
setError(false);
|
setError(false);
|
||||||
|
@ -60,32 +63,21 @@ function User(props: singleUserProps) {
|
||||||
const handleSubmit = (e) => {
|
const handleSubmit = (e) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(false);
|
setError(false);
|
||||||
|
|
||||||
axiosConfig
|
|
||||||
.put('/UpdateUser', formValues)
|
|
||||||
.then((response) => {
|
|
||||||
if (response) {
|
|
||||||
props.fetchDataAgain();
|
|
||||||
setLoading(false);
|
|
||||||
setUpdated(true);
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
setUpdated(false);
|
|
||||||
}, 3000);
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.catch((error) => {
|
|
||||||
setLoading(false);
|
|
||||||
setError(true);
|
|
||||||
if (error.response && error.response.status == 401) {
|
|
||||||
removeToken();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (e) => {
|
const handleDelete = (e) => {
|
||||||
setLoading(true);
|
setLoading(true);
|
||||||
setError(false);
|
setError(false);
|
||||||
|
setOpenModalDeleteFolder(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteUserModalHandleCancel = (e) => {
|
||||||
|
setLoading(false);
|
||||||
|
setError(false);
|
||||||
|
setOpenModalDeleteFolder(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteUserModalHandle = (e) => {
|
||||||
axiosConfig
|
axiosConfig
|
||||||
.delete(`/DeleteUser?userId=${formValues.id}`)
|
.delete(`/DeleteUser?userId=${formValues.id}`)
|
||||||
.then((response) => {
|
.then((response) => {
|
||||||
|
@ -102,15 +94,86 @@ function User(props: singleUserProps) {
|
||||||
.catch((error) => {
|
.catch((error) => {
|
||||||
setLoading(false);
|
setLoading(false);
|
||||||
setError(true);
|
setError(true);
|
||||||
|
setOpenModalDeleteFolder(false);
|
||||||
if (error.response && error.response.status == 401) {
|
if (error.response && error.response.status == 401) {
|
||||||
removeToken();
|
removeToken();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Grid item xs={12} md={9}>
|
{openModalDeleteFolder && (
|
||||||
|
<Modal
|
||||||
|
open={openModalDeleteFolder}
|
||||||
|
onClose={() => setOpenModalDeleteFolder(false)}
|
||||||
|
aria-labelledby="error-modal"
|
||||||
|
aria-describedby="error-modal-description"
|
||||||
|
>
|
||||||
|
<Box
|
||||||
|
sx={{
|
||||||
|
position: 'absolute',
|
||||||
|
top: '50%',
|
||||||
|
left: '50%',
|
||||||
|
transform: 'translate(-50%, -50%)',
|
||||||
|
width: 350,
|
||||||
|
bgcolor: 'background.paper',
|
||||||
|
borderRadius: 4,
|
||||||
|
boxShadow: 24,
|
||||||
|
p: 4,
|
||||||
|
display: 'flex',
|
||||||
|
flexDirection: 'column',
|
||||||
|
alignItems: 'center'
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
gutterBottom
|
||||||
|
sx={{ fontWeight: 'bold' }}
|
||||||
|
>
|
||||||
|
Do you want to delete this user?
|
||||||
|
</Typography>
|
||||||
|
|
||||||
|
<div
|
||||||
|
style={{
|
||||||
|
display: 'flex',
|
||||||
|
alignItems: 'center',
|
||||||
|
marginTop: 10
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginTop: 2,
|
||||||
|
textTransform: 'none',
|
||||||
|
bgcolor: '#ffc04d',
|
||||||
|
color: '#111111',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
onClick={deleteUserModalHandle}
|
||||||
|
>
|
||||||
|
Delete
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
sx={{
|
||||||
|
marginTop: 2,
|
||||||
|
marginLeft: 2,
|
||||||
|
textTransform: 'none',
|
||||||
|
bgcolor: '#ffc04d',
|
||||||
|
color: '#111111',
|
||||||
|
'&:hover': { bgcolor: '#f7b34d' }
|
||||||
|
}}
|
||||||
|
onClick={deleteUserModalHandleCancel}
|
||||||
|
>
|
||||||
|
Cancel
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</Box>
|
||||||
|
</Modal>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<Grid item xs={12} md={isMobile ? 7 : 8}>
|
||||||
<TabsContainerWrapper>
|
<TabsContainerWrapper>
|
||||||
<Tabs
|
<Tabs
|
||||||
onChange={handleTabsChange}
|
onChange={handleTabsChange}
|
||||||
|
@ -195,7 +258,10 @@ function User(props: singleUserProps) {
|
||||||
marginLeft: '10px'
|
marginLeft: '10px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Apply Changes
|
<FormattedMessage
|
||||||
|
id="apply_changes"
|
||||||
|
defaultMessage="Apply Changes"
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button
|
||||||
variant="contained"
|
variant="contained"
|
||||||
|
@ -204,7 +270,10 @@ function User(props: singleUserProps) {
|
||||||
marginLeft: '10px'
|
marginLeft: '10px'
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
Delete User
|
<FormattedMessage
|
||||||
|
id="delete_user"
|
||||||
|
defaultMessage="Delete User"
|
||||||
|
/>
|
||||||
</Button>
|
</Button>
|
||||||
|
|
||||||
{loading && (
|
{loading && (
|
||||||
|
|
|
@ -50,6 +50,8 @@ function UsersSearch() {
|
||||||
setOpenModal(false);
|
setOpenModal(false);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Grid container spacing={1}>
|
<Grid container spacing={1}>
|
||||||
|
@ -65,7 +67,7 @@ function UsersSearch() {
|
||||||
<UserForm cancel={handleUserFormCancel} submit={handleUserFormSubmit} />
|
<UserForm cancel={handleUserFormCancel} submit={handleUserFormSubmit} />
|
||||||
)}
|
)}
|
||||||
<Grid container spacing={1} sx={{ marginTop: '1px' }}>
|
<Grid container spacing={1} sx={{ marginTop: '1px' }}>
|
||||||
<Grid item xs={12} md={3}>
|
<Grid item xs={12} md={isMobile ? 5 : 3}>
|
||||||
<FormControl variant="outlined" fullWidth>
|
<FormControl variant="outlined" fullWidth>
|
||||||
<TextField
|
<TextField
|
||||||
placeholder="Search"
|
placeholder="Search"
|
||||||
|
|
|
@ -2,6 +2,7 @@ import Footer from 'src/components/Footer';
|
||||||
import { Box, Container, Grid, useTheme } from '@mui/material';
|
import { Box, Container, Grid, useTheme } from '@mui/material';
|
||||||
import UsersSearch from './UsersSearch';
|
import UsersSearch from './UsersSearch';
|
||||||
import UsersContextProvider from 'src/contexts/UsersContextProvider';
|
import UsersContextProvider from 'src/contexts/UsersContextProvider';
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
function Users() {
|
function Users() {
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
|
|
@ -103,6 +103,8 @@ function userForm(props: userFormProps) {
|
||||||
setSelectedInstallationNames(event.target.value);
|
setSelectedInstallationNames(event.target.value);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isMobile = window.innerWidth <= 1490;
|
||||||
|
|
||||||
const handleSubmit = async (e) => {
|
const handleSubmit = async (e) => {
|
||||||
const res = await axiosConfig.post('/CreateUser', {
|
const res = await axiosConfig.post('/CreateUser', {
|
||||||
...formValues,
|
...formValues,
|
||||||
|
@ -189,10 +191,10 @@ function userForm(props: userFormProps) {
|
||||||
<Box
|
<Box
|
||||||
sx={{
|
sx={{
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
top: '30%',
|
top: isMobile ? '50%' : '30%',
|
||||||
left: '50%',
|
left: '50%',
|
||||||
transform: 'translate(-50%, -50%)',
|
transform: 'translate(-50%, -50%)',
|
||||||
width: 600,
|
width: 500,
|
||||||
bgcolor: 'background.paper',
|
bgcolor: 'background.paper',
|
||||||
borderRadius: 4,
|
borderRadius: 4,
|
||||||
boxShadow: 24,
|
boxShadow: 24,
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { AxiosError } from 'axios';
|
import { AxiosError, AxiosResponse } from 'axios';
|
||||||
import {
|
import {
|
||||||
createContext,
|
createContext,
|
||||||
ReactNode,
|
ReactNode,
|
||||||
|
@ -54,7 +54,7 @@ const InstallationsContextProvider = ({
|
||||||
}: {
|
}: {
|
||||||
children: ReactNode;
|
children: ReactNode;
|
||||||
}) => {
|
}) => {
|
||||||
const [installations, setInstallations] = useState([]);
|
const [installations, setInstallations] = useState<I_Installation[]>([]);
|
||||||
const [foldersAndInstallations, setFoldersAndInstallations] = useState([]);
|
const [foldersAndInstallations, setFoldersAndInstallations] = useState([]);
|
||||||
const [loading, setLoading] = useState(false);
|
const [loading, setLoading] = useState(false);
|
||||||
const [error, setError] = useState(false);
|
const [error, setError] = useState(false);
|
||||||
|
@ -67,7 +67,7 @@ const InstallationsContextProvider = ({
|
||||||
let isMounted = true;
|
let isMounted = true;
|
||||||
axiosConfig
|
axiosConfig
|
||||||
.get('/GetAllInstallations', {})
|
.get('/GetAllInstallations', {})
|
||||||
.then((res) => {
|
.then((res: AxiosResponse<I_Installation[]>) => {
|
||||||
setInstallations(res.data);
|
setInstallations(res.data);
|
||||||
})
|
})
|
||||||
.catch((err: AxiosError) => {
|
.catch((err: AxiosError) => {
|
||||||
|
|
|
@ -16,9 +16,9 @@ export const LogContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
>({});
|
>({});
|
||||||
|
|
||||||
const BUFFER_LENGTH = 5;
|
const BUFFER_LENGTH = 5;
|
||||||
|
|
||||||
const handleLogWarningOrError = (installation_id: number, value: number) => {
|
const handleLogWarningOrError = (installation_id: number, value: number) => {
|
||||||
setInstallationStatus((prevStatus) => {
|
setInstallationStatus((prevStatus) => {
|
||||||
|
// Create a new object by spreading the previous state
|
||||||
const newStatus = { ...prevStatus };
|
const newStatus = { ...prevStatus };
|
||||||
|
|
||||||
if (!newStatus.hasOwnProperty(installation_id)) {
|
if (!newStatus.hasOwnProperty(installation_id)) {
|
||||||
|
@ -29,6 +29,7 @@ export const LogContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
0,
|
0,
|
||||||
BUFFER_LENGTH
|
BUFFER_LENGTH
|
||||||
);
|
);
|
||||||
|
|
||||||
return newStatus;
|
return newStatus;
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
@ -39,9 +40,19 @@ export const LogContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
if (!installationStatus.hasOwnProperty(installationId)) {
|
if (!installationStatus.hasOwnProperty(installationId)) {
|
||||||
status = -2;
|
status = -2;
|
||||||
} else {
|
} else {
|
||||||
if (installationId === 2) {
|
let i = 0;
|
||||||
console.log(installationStatus[2]);
|
//If at least one status value shows an error, then show error
|
||||||
|
for (i; i < installationStatus[installationId].length; i++) {
|
||||||
|
if (installationStatus[installationId][i] === 2) {
|
||||||
|
status = 2;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
|
if (installationStatus[installationId][i] === 1) {
|
||||||
|
status = 1;
|
||||||
|
return status;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (installationStatus[installationId][0] == -1) {
|
if (installationStatus[installationId][0] == -1) {
|
||||||
let i = 0;
|
let i = 0;
|
||||||
for (i; i < installationStatus[installationId].length; i++) {
|
for (i; i < installationStatus[installationId].length; i++) {
|
||||||
|
@ -49,7 +60,6 @@ export const LogContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (i === installationStatus[installationId].length) {
|
if (i === installationStatus[installationId].length) {
|
||||||
status = -1;
|
status = -1;
|
||||||
}
|
}
|
||||||
|
@ -57,7 +67,6 @@ export const LogContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
status = installationStatus[installationId][0];
|
status = installationStatus[installationId][0];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return status;
|
return status;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,9 +1,5 @@
|
||||||
import {createContext, ReactNode, useState} from 'react';
|
import {createContext, ReactNode, useState} from 'react';
|
||||||
|
|
||||||
//const setUser=(currentUser: InnovEnergyUser)=>{
|
|
||||||
// localStorage.setItem("currentUser",JSON.stringify(currentUser));
|
|
||||||
//}
|
|
||||||
|
|
||||||
// Define the shape of the context
|
// Define the shape of the context
|
||||||
interface TokenContextType {
|
interface TokenContextType {
|
||||||
token?: string | null;
|
token?: string | null;
|
||||||
|
@ -18,6 +14,8 @@ export const TokenContext = createContext<TokenContextType | undefined>(
|
||||||
|
|
||||||
// Create a UserContextProvider component
|
// Create a UserContextProvider component
|
||||||
export const TokenContextProvider = ({ children }: { children: ReactNode }) => {
|
export const TokenContextProvider = ({ children }: { children: ReactNode }) => {
|
||||||
|
const searchParams = new URLSearchParams(location.search);
|
||||||
|
const tokenId = parseInt(searchParams.get('authToken'));
|
||||||
//Initialize context state with a "null" user
|
//Initialize context state with a "null" user
|
||||||
const [token, setToken] = useState(localStorage.getItem('token'));
|
const [token, setToken] = useState(localStorage.getItem('token'));
|
||||||
|
|
||||||
|
|
|
@ -265,6 +265,7 @@ export class TimeRange {
|
||||||
for (let t = this.from; t < this.to; t += period.ticks)
|
for (let t = this.from; t < this.to; t += period.ticks)
|
||||||
samples.push(UnixTime.fromTicks(t));
|
samples.push(UnixTime.fromTicks(t));
|
||||||
|
|
||||||
|
samples.push(UnixTime.fromTicks(this.to));
|
||||||
return samples;
|
return samples;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -9,7 +9,8 @@ export interface I_Installation extends I_S3Credentials {
|
||||||
location: string;
|
location: string;
|
||||||
region: string;
|
region: string;
|
||||||
country: string;
|
country: string;
|
||||||
orderNumbers: string;
|
installationName: string;
|
||||||
|
orderNumbers: string[] | string;
|
||||||
lat: number;
|
lat: number;
|
||||||
long: number;
|
long: number;
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
@ -2,12 +2,12 @@
|
||||||
"information": "Information",
|
"information": "Information",
|
||||||
"addNewChild": "Neues Kind hinzufügen",
|
"addNewChild": "Neues Kind hinzufügen",
|
||||||
"addNewDialogButton": "Neue Dialogschaltfläche hinzufügen",
|
"addNewDialogButton": "Neue Dialogschaltfläche hinzufügen",
|
||||||
"addUser": "Nutzer erstellen",
|
"addUser": "Neuen Benutzer erstellen",
|
||||||
"alarms": "Alarme",
|
"alarms": "Alarme",
|
||||||
"applyChanges": "Änderungen speichern",
|
"applyChanges": "Änderungen speichern",
|
||||||
"country": "Land",
|
"country": "Land",
|
||||||
"createNewFolder": "Neuen Ordner erstellen",
|
"createNewFolder": "Neuer Ordner",
|
||||||
"createNewUser": "Neuen Nutzer erstellen",
|
"createNewUser": "Neuer Benutzer",
|
||||||
"customerName": "Kundenname",
|
"customerName": "Kundenname",
|
||||||
"email": "Email",
|
"email": "Email",
|
||||||
"english": "Englisch",
|
"english": "Englisch",
|
||||||
|
@ -16,27 +16,50 @@
|
||||||
"german": "Deutsch",
|
"german": "Deutsch",
|
||||||
"groupTabs": "Gruppen",
|
"groupTabs": "Gruppen",
|
||||||
"groupTree": "Gruppenbaum",
|
"groupTree": "Gruppenbaum",
|
||||||
|
"overview": "Überblick",
|
||||||
|
"manage": "Zugriffsverwaltung",
|
||||||
|
"configuration": "Aufbau",
|
||||||
|
"installation_name_simple": "Installationsname: ",
|
||||||
|
"language": "Sprache",
|
||||||
|
"minimum_soc": "Minimum SoC",
|
||||||
|
"calibration_charge_forced": "Kalibrierungsladung erzwungen",
|
||||||
|
"grid_set_point": "Sollwert Netzleistung",
|
||||||
|
"Installed_Power_DC1010": "Installierte Leistung DC1010 TODO",
|
||||||
|
"Maximum_Discharge_Power": "Maximale Entladeleistung",
|
||||||
|
"Number_of_Batteries": "Anzahl der Batterien",
|
||||||
|
"24_hours": "24 Stunden",
|
||||||
|
"lastweek": "Letzte Woche",
|
||||||
|
"lastmonth": "Vergangener Monat",
|
||||||
|
"apply_changes": "Änderungen übernehmen",
|
||||||
|
"delete_user": "Benutzer löschen",
|
||||||
|
"battery_temperature": "Batterietemperatur",
|
||||||
|
"pv_production": "Photovoltaik Produktion",
|
||||||
|
"grid_power": "Netzstrom",
|
||||||
|
"battery_power": "Batterieleistung",
|
||||||
|
"dc_voltage": "DC-Busspannung",
|
||||||
|
"battery_soc": "Ladezustand (SOC)",
|
||||||
|
"installation_name": "Installationsname",
|
||||||
"inheritedAccess": "Vererbter Zugriff von",
|
"inheritedAccess": "Vererbter Zugriff von",
|
||||||
"installation": "Installation",
|
"installation": "Installation",
|
||||||
"installationTabs": "Installationen",
|
"installationTabs": "Installationen",
|
||||||
"installations": "Installationen",
|
"installations": "Installationen",
|
||||||
"lastWeek": "Letzte Woche",
|
"lastWeek": "Letzte Woche",
|
||||||
"location": "Standort",
|
"location": "Standort",
|
||||||
"log": "Logbuch",
|
"log": "Log daten",
|
||||||
"logout": "Abmelden",
|
"logout": "Abmelden",
|
||||||
"makeASelection": "Bitte wählen Sie links eine Auswahl",
|
"makeASelection": "Bitte wählen Sie links eine Auswahl",
|
||||||
"manageAccess": "Zugriff verwalten",
|
"manageAccess": "Zugriffsverwaltung",
|
||||||
"move": "Verschieben",
|
"move": "Verschieben",
|
||||||
"moveTo": "Verschieben zu",
|
"moveTo": "Verschieben nach",
|
||||||
"moveTree": "Baum verschieben",
|
"moveTree": "Baum verschieben",
|
||||||
"name": "Name",
|
"name": "Name",
|
||||||
"navigationTabs": "Navigation",
|
"navigationTabs": "Navigation",
|
||||||
"orderNumbers": "Bestellnummer",
|
"orderNumbers": "Bestellnummern",
|
||||||
"region": "Region",
|
"region": "Region",
|
||||||
"requiredLocation": "Standort ist erforderlich",
|
"requiredLocation": "Standort ist erforderlich",
|
||||||
"requiredName": "Name ist erforderlich",
|
"requiredName": "Name ist erforderlich",
|
||||||
"requiredRegion": "Region ist erforderlich",
|
"requiredRegion": "Region ist erforderlich",
|
||||||
"search": "Suche",
|
"search": "Suchen",
|
||||||
"submit": "Senden",
|
"submit": "Senden",
|
||||||
"updateFolderErrorMessage": "Fehler, Ordner kann nicht aktualisiert werden",
|
"updateFolderErrorMessage": "Fehler, Ordner kann nicht aktualisiert werden",
|
||||||
"updatedSuccessfully": "Erfolgreich aktualisiert",
|
"updatedSuccessfully": "Erfolgreich aktualisiert",
|
||||||
|
@ -44,27 +67,27 @@
|
||||||
"userTabs": "Nutzer",
|
"userTabs": "Nutzer",
|
||||||
"users": "Nutzer",
|
"users": "Nutzer",
|
||||||
"status": "Status",
|
"status": "Status",
|
||||||
"live": "Live Übertragung",
|
"live": "Live Daten",
|
||||||
"deleteInstallation": "Installation löschen",
|
"deleteInstallation": "Installation löschen",
|
||||||
"errorOccured": "Ein Fehler ist aufgetreten",
|
"errorOccured": "Ein Fehler ist aufgetreten",
|
||||||
"successfullyUpdated": "Erfolgreich aktualisiert",
|
"successfullyUpdated": "Erfolgreich aktualisiert",
|
||||||
"grantAccess": "Zugriff gewähren",
|
"grantAccess": "Zugriff gewähren",
|
||||||
"UserswithDirectAccess": "Benutzer mit direktem Zugriff",
|
"UserswithDirectAccess": "Benutzer mit direktem Zugriff",
|
||||||
"UserswithInheritedAccess": "Benutzer mit geerbtem Zugriff",
|
"UserswithInheritedAccess": "Benutzer mit vererbtem Zugriff",
|
||||||
"noerrors": "Es liegen keine Fehler vor",
|
"noerrors": "Keine Fehler",
|
||||||
"nowarnings": "Es gibt keine Warnungen",
|
"nowarnings": "Keine Warnungen",
|
||||||
"noUsersWithDirectAccessToThis": "Keine Benutzer mit direktem Zugriff darauf ",
|
"noUsersWithDirectAccessToThis": "Keine Benutzer mit direktem Zugriff",
|
||||||
"selectUsers": "Wählen Sie Benutzer aus",
|
"selectUsers": "Benutzer auswählen",
|
||||||
"cancel": "Stornieren",
|
"cancel": "Abbrechen",
|
||||||
"addNewFolder": "Neuen Ordner hinzufügen",
|
"addNewFolder": "Neuen Ordner hinzufügen",
|
||||||
"addNewInstallation": "Neue Installation hinzufügen",
|
"addNewInstallation": "Neue Installation hinzufügen",
|
||||||
"deleteFolder": "Lösche Ordner",
|
"deleteFolder": "Ordner löschen",
|
||||||
"grantAccessToFolders": "Gewähren Sie Zugriff auf Ordner",
|
"grantAccessToFolders": "Zugriff auf Ordner gewähren",
|
||||||
"grantAccessToInstallations": "Gewähren Sie Zugriff auf Installationen",
|
"grantAccessToInstallations": "Zugriff auf Installationen gewähren",
|
||||||
"cannotloadloggingdata": "Protokollierungsdaten können nicht geladen werden",
|
"cannotloadloggingdata": "Log Daten können nicht geladen werden",
|
||||||
"grantedAccessToUsers": "Benutzern Zugriff gewährt: ",
|
"grantedAccessToUsers": "Den Benutzern wurde den Zugriff gewährt",
|
||||||
"unableToGrantAccess": "Der Zugriff auf kann nicht gewährt werden: ",
|
"unableToGrantAccess": "Der Zugriff kann nicht gewährt werden",
|
||||||
"unableToLoadData": "Daten können nicht geladen werden",
|
"unableToLoadData": "Daten können nicht geladen werden",
|
||||||
"unableToRevokeAccess": "Der Zugriff kann nicht widerrufen werden",
|
"unableToRevokeAccess": "Der Zugriff konnte nicht widerrufen werden",
|
||||||
"revokedAccessFromUser": "Zugriff vom Benutzer widerrufen: "
|
"revokedAccessFromUser": "Zugriff vom Benutzer widerrufen"
|
||||||
}
|
}
|
|
@ -1,42 +1,59 @@
|
||||||
{
|
{
|
||||||
"information": "Informations",
|
"information": "Information",
|
||||||
"addNewChild": "Ajouter un nouvel enfant",
|
|
||||||
"addNewDialogButton": "Ajouter un nouveau bouton de dialogue",
|
|
||||||
"addUser": "Créer un utilisateur",
|
"addUser": "Créer un utilisateur",
|
||||||
"alarms": "Alarmes",
|
"alarms": "Alarmes",
|
||||||
"applyChanges": "Appliquer les modifications",
|
"applyChanges": "Appliquer",
|
||||||
"country": "Pays",
|
"country": "Pays",
|
||||||
"createNewFolder": "Créer un nouveau dossier",
|
"createNewFolder": "Nouveau dossier",
|
||||||
"createNewUser": "Créer un nouvel utilisateur",
|
"createNewUser": "Nouvel utilisateur",
|
||||||
"customerName": "Nom du client",
|
"customerName": "Nom du client",
|
||||||
"email": "E-mail",
|
"email": "Courriel",
|
||||||
"english": "Anglais",
|
"english": "Anglais",
|
||||||
"error": "Erreur",
|
"error": "Erreur",
|
||||||
"folder": "Dossier",
|
"folder": "Dossier",
|
||||||
"german": "Allemand",
|
"german": "Allemand",
|
||||||
"groupTabs": "Onglets de groupe",
|
"overview": "Aperçu",
|
||||||
"groupTree": "Arbre de groupe",
|
"manage": "Gestion des accès",
|
||||||
|
"configuration": "Configuration",
|
||||||
|
"installation_name": "Nom de l'installation",
|
||||||
|
"apply_changes": "Appliquer",
|
||||||
|
"delete_user": "Supprimer l'utilisateur",
|
||||||
|
"installation_name_simple": "Nom de l'installation: ",
|
||||||
|
"language": "Langue",
|
||||||
|
"minimum_soc": "Soc minimum",
|
||||||
|
"calibration_charge_forced": "Charge d'étalonnage forcée",
|
||||||
|
"grid_set_point": "Point de consigne de grid",
|
||||||
|
"Installed_Power_DC1010": "Alimentation branchée",
|
||||||
|
"Maximum_Discharge_Power": "Puissance de décharge maximale",
|
||||||
|
"Number_of_Batteries": "Nombre de batteries",
|
||||||
|
"24_hours": "24-heures",
|
||||||
|
"lastweek": "Semaine dernière",
|
||||||
|
"lastmonth": "Mois dernier",
|
||||||
|
"battery_temperature": "Température de la batterie",
|
||||||
|
"pv_production": "Production photovoltaïque",
|
||||||
|
"grid_power": "Alimentation du réseau",
|
||||||
|
"battery_power": "Puissance de la batterie",
|
||||||
|
"dc_voltage": "Tension du bus CC",
|
||||||
|
"battery_soc": "État de charge de la batterie",
|
||||||
"inheritedAccess": "Accès hérité de",
|
"inheritedAccess": "Accès hérité de",
|
||||||
"installation": "Installation",
|
"installation": "Installation",
|
||||||
"installationTabs": "Onglets d'installation",
|
|
||||||
"installations": "Installations",
|
"installations": "Installations",
|
||||||
"lastWeek": "La semaine dernière",
|
"lastWeek": "La semaine dernière",
|
||||||
"location": "Localisation",
|
"location": "Localité",
|
||||||
"log": "Journal",
|
"log": "Journal",
|
||||||
"logout": "Déconnexion",
|
"logout": "Fermer las session",
|
||||||
"makeASelection": "Veuillez faire une sélection à gauche",
|
"makeASelection": "Veuillez faire une sélection à gauche",
|
||||||
"manageAccess": "Gérer l'accès",
|
"manageAccess": "Gérer l'accès",
|
||||||
"move": "Déplacer",
|
"move": "Déplacer",
|
||||||
"moveTo": "Déplacer à",
|
"moveTo": "Déplacer à",
|
||||||
"moveTree": "Déplacer l'arbre",
|
"moveTree": "Déplacer l'arbre",
|
||||||
"name": "Nom",
|
"name": "Nom",
|
||||||
"navigationTabs": "Onglets de navigation",
|
"orderNumbers": "Numéros de commande",
|
||||||
"orderNumbers": "Numéro de commande",
|
|
||||||
"region": "Région",
|
"region": "Région",
|
||||||
"requiredLocation": "L'emplacement est requis",
|
"requiredLocation": "La localité est requis",
|
||||||
"requiredName": "Le nom est obligatoire",
|
"requiredName": "Le nom est obligatoire",
|
||||||
"requiredRegion": "La région est obligatoire",
|
"requiredRegion": "La région est obligatoire",
|
||||||
"search": "Recherche",
|
"search": "Rechercher",
|
||||||
"submit": "Soumettre",
|
"submit": "Soumettre",
|
||||||
"updateFolderErrorMessage": "Une erreur s'est produite, impossible de mettre à jour le dossier.",
|
"updateFolderErrorMessage": "Une erreur s'est produite, impossible de mettre à jour le dossier.",
|
||||||
"updatedSuccessfully": "Mise à jour réussie",
|
"updatedSuccessfully": "Mise à jour réussie",
|
||||||
|
@ -46,14 +63,14 @@
|
||||||
"status": "Statut",
|
"status": "Statut",
|
||||||
"live": "Diffusion en direct",
|
"live": "Diffusion en direct",
|
||||||
"deleteInstallation": "Supprimer l'installation",
|
"deleteInstallation": "Supprimer l'installation",
|
||||||
"errorOccured": "Une erreur est survenue",
|
"errorOccured": "Une erreur s’est produite",
|
||||||
"successfullyUpdated": "Mise à jour réussie",
|
"successfullyUpdated": "Mise à jour réussie",
|
||||||
"grantAccess": "Accorder l'accès",
|
"grantAccess": "Accorder l'accès",
|
||||||
"UserswithDirectAccess": "Utilisateurs avec accès direct",
|
"UserswithDirectAccess": "Utilisateurs avec accès direct",
|
||||||
"UserswithInheritedAccess": "Utilisateurs avec accès hérité",
|
"UserswithInheritedAccess": "Utilisateurs avec accès hérité",
|
||||||
"noerrors": "Il n'y a pas d'erreurs",
|
"noerrors": "Il n'y a pas d'erreurs",
|
||||||
"nowarnings": "Il n'y a aucun avertissement",
|
"nowarnings": "Il n'y a aucun avertissement",
|
||||||
"noUsersWithDirectAccessToThis": "Aucun utilisateur ayant un accès direct à ceci ",
|
"noUsersWithDirectAccessToThis": "Aucun utilisateur ayant un accès direct",
|
||||||
"selectUsers": "Sélectionnez les utilisateurs",
|
"selectUsers": "Sélectionnez les utilisateurs",
|
||||||
"cancel": "Annuler",
|
"cancel": "Annuler",
|
||||||
"addNewFolder": "Ajouter un nouveau dossier",
|
"addNewFolder": "Ajouter un nouveau dossier",
|
||||||
|
@ -62,9 +79,9 @@
|
||||||
"grantAccessToFolders": "Accorder l'accès aux dossiers",
|
"grantAccessToFolders": "Accorder l'accès aux dossiers",
|
||||||
"grantAccessToInstallations": "Accorder l'accès aux installations",
|
"grantAccessToInstallations": "Accorder l'accès aux installations",
|
||||||
"cannotloadloggingdata": "Impossible de charger les données de journalisation",
|
"cannotloadloggingdata": "Impossible de charger les données de journalisation",
|
||||||
"grantedAccessToUsers": "Accès accordé aux utilisateurs: ",
|
"grantedAccessToUsers": "Accès accordé aux utilisateurs",
|
||||||
"unableToGrantAccess": "Impossible d'accorder l'accès à: ",
|
"unableToGrantAccess": "Impossible d'accorder l'accès à",
|
||||||
"unableToLoadData": "Impossible de charger les données",
|
"unableToLoadData": "Impossible de charger les données",
|
||||||
"unableToRevokeAccess": "Impossible de révoquer l'accès",
|
"unableToRevokeAccess": "Impossible de révoquer l'accès",
|
||||||
"revokedAccessFromUser": "Accès révoqué de l'utilisateur: "
|
"revokedAccessFromUser": "Accès révoqué de l'utilisateur"
|
||||||
}
|
}
|
|
@ -6,10 +6,11 @@ import {
|
||||||
Menu,
|
Menu,
|
||||||
MenuItem
|
MenuItem
|
||||||
} from '@mui/material';
|
} from '@mui/material';
|
||||||
import { useContext, useRef, useState } from 'react';
|
import React, { useContext, useRef, useState } from 'react';
|
||||||
import { styled } from '@mui/material/styles';
|
import { styled } from '@mui/material/styles';
|
||||||
import ExpandMoreTwoToneIcon from '@mui/icons-material/ExpandMoreTwoTone';
|
import ExpandMoreTwoToneIcon from '@mui/icons-material/ExpandMoreTwoTone';
|
||||||
import { ThemeContext } from '../../../../theme/ThemeProvider';
|
import { ThemeContext } from '../../../../theme/ThemeProvider';
|
||||||
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
|
||||||
interface HeaderButtonsProps {
|
interface HeaderButtonsProps {
|
||||||
language: string;
|
language: string;
|
||||||
|
@ -102,20 +103,18 @@ function HeaderMenu(props: HeaderButtonsProps) {
|
||||||
handleClose();
|
handleClose();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const isMobile = window.innerWidth <= 1280;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<ListWrapper
|
<ListWrapper
|
||||||
sx={{
|
sx={{
|
||||||
display: {
|
color: isMobile ? 'white' : ''
|
||||||
xs: 'none',
|
|
||||||
md: 'block'
|
|
||||||
}
|
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<List disablePadding component={Box} display="flex">
|
<List disablePadding component={Box} display="flex">
|
||||||
<ListItem
|
<ListItem
|
||||||
classes={{ root: 'MuiListItem-indicators' }}
|
classes={{ root: 'MuiListItem-indicators' }}
|
||||||
button
|
|
||||||
ref={ref}
|
ref={ref}
|
||||||
onClick={handleOpen}
|
onClick={handleOpen}
|
||||||
>
|
>
|
||||||
|
@ -123,7 +122,7 @@ function HeaderMenu(props: HeaderButtonsProps) {
|
||||||
primaryTypographyProps={{ noWrap: true }}
|
primaryTypographyProps={{ noWrap: true }}
|
||||||
primary={
|
primary={
|
||||||
<Box display="flex" alignItems="center">
|
<Box display="flex" alignItems="center">
|
||||||
Language
|
<FormattedMessage id="language" defaultMessage="Language" />
|
||||||
<Box display="flex" alignItems="center" pl={0.3}>
|
<Box display="flex" alignItems="center" pl={0.3}>
|
||||||
<ExpandMoreTwoToneIcon fontSize="small" />
|
<ExpandMoreTwoToneIcon fontSize="small" />
|
||||||
</Box>
|
</Box>
|
||||||
|
|
|
@ -17,6 +17,7 @@ import axiosConfig from 'src/Resources/axiosConfig';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
import { TokenContext } from 'src/contexts/tokenContext';
|
import { TokenContext } from 'src/contexts/tokenContext';
|
||||||
import { useNavigate } from 'react-router-dom';
|
import { useNavigate } from 'react-router-dom';
|
||||||
|
import routes from 'src/Resources/routes.json';
|
||||||
|
|
||||||
const UserBoxButton = styled(Button)(
|
const UserBoxButton = styled(Button)(
|
||||||
({ theme }) => `
|
({ theme }) => `
|
||||||
|
@ -68,7 +69,7 @@ function HeaderUserbox() {
|
||||||
removeToken();
|
removeToken();
|
||||||
removeUser();
|
removeUser();
|
||||||
localStorage.removeItem('theme');
|
localStorage.removeItem('theme');
|
||||||
navigate('/');
|
navigate(routes.login);
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@ import { useContext } from 'react';
|
||||||
import {
|
import {
|
||||||
alpha,
|
alpha,
|
||||||
Box,
|
Box,
|
||||||
|
darken,
|
||||||
Divider,
|
Divider,
|
||||||
IconButton,
|
IconButton,
|
||||||
lighten,
|
lighten,
|
||||||
|
@ -44,12 +45,17 @@ interface HeaderProps {
|
||||||
function Header(props: HeaderProps) {
|
function Header(props: HeaderProps) {
|
||||||
const { sidebarToggle, toggleSidebar } = useContext(SidebarContext);
|
const { sidebarToggle, toggleSidebar } = useContext(SidebarContext);
|
||||||
const theme = useTheme();
|
const theme = useTheme();
|
||||||
|
const isMobile = window.innerWidth <= 1280;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<HeaderWrapper
|
<HeaderWrapper
|
||||||
display="flex"
|
display="flex"
|
||||||
alignItems="center"
|
alignItems="center"
|
||||||
sx={{
|
sx={{
|
||||||
|
backgroundColor: isMobile
|
||||||
|
? darken(theme.colors.alpha.black[100], 0.5)
|
||||||
|
: '',
|
||||||
|
|
||||||
boxShadow:
|
boxShadow:
|
||||||
theme.palette.mode === 'dark'
|
theme.palette.mode === 'dark'
|
||||||
? `0 1px 0 ${alpha(
|
? `0 1px 0 ${alpha(
|
||||||
|
@ -65,6 +71,23 @@ function Header(props: HeaderProps) {
|
||||||
)}`
|
)}`
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
|
<Box
|
||||||
|
component="span"
|
||||||
|
sx={{
|
||||||
|
ml: 2,
|
||||||
|
display: { lg: 'none', xs: 'inline-block' }
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Tooltip arrow title="Toggle Menu">
|
||||||
|
<IconButton sx={{ color: 'white' }} onClick={toggleSidebar}>
|
||||||
|
{!sidebarToggle ? (
|
||||||
|
<MenuTwoToneIcon fontSize="small" />
|
||||||
|
) : (
|
||||||
|
<CloseTwoToneIcon fontSize="small" />
|
||||||
|
)}
|
||||||
|
</IconButton>
|
||||||
|
</Tooltip>
|
||||||
|
</Box>
|
||||||
<Stack
|
<Stack
|
||||||
direction="row"
|
direction="row"
|
||||||
divider={<Divider orientation="vertical" flexItem />}
|
divider={<Divider orientation="vertical" flexItem />}
|
||||||
|
@ -79,23 +102,6 @@ function Header(props: HeaderProps) {
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<HeaderUserbox />
|
<HeaderUserbox />
|
||||||
<Box
|
|
||||||
component="span"
|
|
||||||
sx={{
|
|
||||||
ml: 2,
|
|
||||||
display: { lg: 'none', xs: 'inline-block' }
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Tooltip arrow title="Toggle Menu">
|
|
||||||
<IconButton color="primary" onClick={toggleSidebar}>
|
|
||||||
{!sidebarToggle ? (
|
|
||||||
<MenuTwoToneIcon fontSize="small" />
|
|
||||||
) : (
|
|
||||||
<CloseTwoToneIcon fontSize="small" />
|
|
||||||
)}
|
|
||||||
</IconButton>
|
|
||||||
</Tooltip>
|
|
||||||
</Box>
|
|
||||||
</Box>
|
</Box>
|
||||||
</HeaderWrapper>
|
</HeaderWrapper>
|
||||||
);
|
);
|
||||||
|
|
|
@ -14,6 +14,7 @@ import { SidebarContext } from 'src/contexts/SidebarContext';
|
||||||
import BrightnessLowTwoToneIcon from '@mui/icons-material/BrightnessLowTwoTone';
|
import BrightnessLowTwoToneIcon from '@mui/icons-material/BrightnessLowTwoTone';
|
||||||
import TableChartTwoToneIcon from '@mui/icons-material/TableChartTwoTone';
|
import TableChartTwoToneIcon from '@mui/icons-material/TableChartTwoTone';
|
||||||
import { FormattedMessage } from 'react-intl';
|
import { FormattedMessage } from 'react-intl';
|
||||||
|
import { UserContext } from '../../../../contexts/userContext';
|
||||||
|
|
||||||
const MenuWrapper = styled(Box)(
|
const MenuWrapper = styled(Box)(
|
||||||
({ theme }) => `
|
({ theme }) => `
|
||||||
|
@ -159,6 +160,8 @@ const SubMenuWrapper = styled(Box)(
|
||||||
|
|
||||||
function SidebarMenu() {
|
function SidebarMenu() {
|
||||||
const { closeSidebar } = useContext(SidebarContext);
|
const { closeSidebar } = useContext(SidebarContext);
|
||||||
|
const context = useContext(UserContext);
|
||||||
|
const { currentUser, setUser } = context;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
|
@ -188,30 +191,33 @@ function SidebarMenu() {
|
||||||
</List>
|
</List>
|
||||||
</SubMenuWrapper>
|
</SubMenuWrapper>
|
||||||
</List>
|
</List>
|
||||||
<List
|
|
||||||
component="div"
|
{currentUser.hasWriteAccess && (
|
||||||
subheader={
|
<List
|
||||||
<ListSubheader component="div" disableSticky>
|
component="div"
|
||||||
Management
|
subheader={
|
||||||
</ListSubheader>
|
<ListSubheader component="div" disableSticky>
|
||||||
}
|
Management
|
||||||
>
|
</ListSubheader>
|
||||||
<SubMenuWrapper>
|
}
|
||||||
<List component="div">
|
>
|
||||||
<ListItem component="div">
|
<SubMenuWrapper>
|
||||||
<Button
|
<List component="div">
|
||||||
disableRipple
|
<ListItem component="div">
|
||||||
component={RouterLink}
|
<Button
|
||||||
onClick={closeSidebar}
|
disableRipple
|
||||||
to="/users"
|
component={RouterLink}
|
||||||
startIcon={<TableChartTwoToneIcon />}
|
onClick={closeSidebar}
|
||||||
>
|
to="/users"
|
||||||
<FormattedMessage id="users" defaultMessage="Users" />
|
startIcon={<TableChartTwoToneIcon />}
|
||||||
</Button>
|
>
|
||||||
</ListItem>
|
<FormattedMessage id="users" defaultMessage="Users" />
|
||||||
</List>
|
</Button>
|
||||||
</SubMenuWrapper>
|
</ListItem>
|
||||||
</List>
|
</List>
|
||||||
|
</SubMenuWrapper>
|
||||||
|
</List>
|
||||||
|
)}
|
||||||
</MenuWrapper>
|
</MenuWrapper>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
|
|
|
@ -1,13 +1,13 @@
|
||||||
import { useContext } from 'react';
|
import { useContext } from 'react';
|
||||||
import Scrollbar from 'src/components/Scrollbar';
|
import Scrollbar from 'src/components/Scrollbar';
|
||||||
import { SidebarContext } from 'src/contexts/SidebarContext';
|
import { SidebarContext } from 'src/contexts/SidebarContext';
|
||||||
import innovenergyLogo from 'src/Resources/innoveng_logo_on_orange.png';
|
import innovenergyLogo from 'src/Resources/images/innovenergy-Logo_Speichern-mit-Salz_R_color.svg';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
alpha,
|
alpha,
|
||||||
Box,
|
Box,
|
||||||
darken,
|
darken,
|
||||||
Divider,
|
Divider,
|
||||||
|
Drawer,
|
||||||
lighten,
|
lighten,
|
||||||
styled,
|
styled,
|
||||||
useTheme
|
useTheme
|
||||||
|
@ -18,7 +18,7 @@ import SidebarMenu from './SidebarMenu';
|
||||||
const SidebarWrapper = styled(Box)(
|
const SidebarWrapper = styled(Box)(
|
||||||
({ theme }) => `
|
({ theme }) => `
|
||||||
width: ${theme.sidebar.width};
|
width: ${theme.sidebar.width};
|
||||||
min-width: "${theme.sidebar.width}";
|
min-width: ${theme.sidebar.width};
|
||||||
color: ${theme.colors.alpha.trueWhite[70]};
|
color: ${theme.colors.alpha.trueWhite[70]};
|
||||||
position: relative;
|
position: relative;
|
||||||
z-index: 7;
|
z-index: 7;
|
||||||
|
@ -63,8 +63,7 @@ function Sidebar() {
|
||||||
src={innovenergyLogo}
|
src={innovenergyLogo}
|
||||||
alt="innovenergy logo"
|
alt="innovenergy logo"
|
||||||
style={{
|
style={{
|
||||||
maxWidth: '150px', // Maximum width for the image
|
width: '150px' // Width of the image
|
||||||
maxHeight: '150px' // Maximum height for the image
|
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Box>
|
</Box>
|
||||||
|
@ -78,12 +77,53 @@ function Sidebar() {
|
||||||
/>
|
/>
|
||||||
<SidebarMenu />
|
<SidebarMenu />
|
||||||
</Scrollbar>
|
</Scrollbar>
|
||||||
<Divider
|
|
||||||
sx={{
|
|
||||||
background: theme.colors.alpha.trueWhite[10]
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</SidebarWrapper>
|
</SidebarWrapper>
|
||||||
|
<Drawer
|
||||||
|
sx={{
|
||||||
|
boxShadow: `${theme.sidebar.boxShadow}`
|
||||||
|
}}
|
||||||
|
anchor={theme.direction === 'rtl' ? 'right' : 'left'}
|
||||||
|
open={sidebarToggle}
|
||||||
|
onClose={closeSidebar}
|
||||||
|
variant="temporary"
|
||||||
|
elevation={9}
|
||||||
|
>
|
||||||
|
<SidebarWrapper
|
||||||
|
sx={{
|
||||||
|
background:
|
||||||
|
theme.palette.mode === 'dark'
|
||||||
|
? theme.colors.alpha.white[100]
|
||||||
|
: darken(theme.colors.alpha.black[100], 0.5)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<Scrollbar>
|
||||||
|
<Box mt={3}>
|
||||||
|
<Box
|
||||||
|
mx={2}
|
||||||
|
sx={{
|
||||||
|
width: 52
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<img
|
||||||
|
src={innovenergyLogo}
|
||||||
|
alt="innovenergy logo"
|
||||||
|
style={{
|
||||||
|
width: '150px' // Width of the image
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</Box>
|
||||||
|
</Box>
|
||||||
|
<Divider
|
||||||
|
sx={{
|
||||||
|
mt: theme.spacing(3),
|
||||||
|
mx: theme.spacing(2),
|
||||||
|
background: theme.colors.alpha.trueWhite[10]
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<SidebarMenu />
|
||||||
|
</Scrollbar>
|
||||||
|
</SidebarWrapper>
|
||||||
|
</Drawer>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue