Merge branch 'marios'
# Conflicts: # csharp/App/S3Explorer/Program.cs
This commit is contained in:
commit
731587ac77
|
@ -9,10 +9,10 @@
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<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="MailKit" Version="3.6.0" />
|
|
||||||
<PackageReference Include="Microsoft.AspNet.Identity.Core" Version="2.2.3" />
|
<PackageReference Include="Microsoft.AspNet.Identity.Core" Version="2.2.3" />
|
||||||
<PackageReference Include="Microsoft.AspNet.Identity.Owin" Version="2.2.3" />
|
<PackageReference Include="Microsoft.AspNet.Identity.Owin" Version="2.2.3" />
|
||||||
<PackageReference Include="Microsoft.AspNet.WebApi.Core" Version="5.2.9" />
|
<PackageReference Include="Microsoft.AspNet.WebApi.Core" Version="5.2.9" />
|
||||||
|
<PackageReference Include="Microsoft.AspNetCore.Authentication.Certificate" Version="6.0.21" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
|
<PackageReference Include="Microsoft.AspNetCore.Identity" Version="2.2.0" />
|
||||||
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
<PackageReference Include="Microsoft.AspNetCore.Mvc.Core" Version="2.2.5" />
|
||||||
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
<PackageReference Include="Microsoft.Extensions.DependencyInjection" Version="7.0.0" />
|
||||||
|
@ -33,12 +33,207 @@
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ProjectReference Include="../../Lib/Utils/Utils.csproj" />
|
<ProjectReference Include="../../Lib/Utils/Utils.csproj" />
|
||||||
|
<ProjectReference Include="../../Lib/Mailer/Mailer.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>
|
||||||
|
|
|
@ -197,7 +197,10 @@ public class Controller : ControllerBase
|
||||||
if (user == null)
|
if (user == null)
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
|
|
||||||
return user.DescendantUsers().Select(u => u.HidePassword()).ToList();
|
return user
|
||||||
|
.DescendantUsers()
|
||||||
|
.Select(u => u.HidePassword())
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -233,8 +236,6 @@ public class Controller : ControllerBase
|
||||||
public ActionResult<IEnumerable<Object>> GetAllFoldersAndInstallations(Token authToken)
|
public ActionResult<IEnumerable<Object>> GetAllFoldersAndInstallations(Token authToken)
|
||||||
{
|
{
|
||||||
var user = Db.GetSession(authToken)?.User;
|
var user = Db.GetSession(authToken)?.User;
|
||||||
|
|
||||||
"GetAllFoldersAndInstallations".WriteLine();
|
|
||||||
|
|
||||||
if (user is null)
|
if (user is null)
|
||||||
return Unauthorized();
|
return Unauthorized();
|
||||||
|
@ -252,7 +253,7 @@ public class Controller : ControllerBase
|
||||||
|
|
||||||
|
|
||||||
[HttpPost(nameof(CreateUser))]
|
[HttpPost(nameof(CreateUser))]
|
||||||
public ActionResult<User> CreateUser(User newUser, Token authToken)
|
public ActionResult<User> CreateUser([FromBody] User newUser, Token authToken)
|
||||||
{
|
{
|
||||||
return Db.GetSession(authToken).Create(newUser)
|
return Db.GetSession(authToken).Create(newUser)
|
||||||
? newUser.HidePassword()
|
? newUser.HidePassword()
|
||||||
|
@ -260,7 +261,7 @@ public class Controller : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost(nameof(CreateInstallation))]
|
[HttpPost(nameof(CreateInstallation))]
|
||||||
public async Task<ActionResult<Installation>> CreateInstallation([FromBody]Installation installation, Token authToken)
|
public async Task<ActionResult<Installation>> CreateInstallation([FromBody] Installation installation, Token authToken)
|
||||||
{
|
{
|
||||||
var session = Db.GetSession(authToken);
|
var session = Db.GetSession(authToken);
|
||||||
|
|
||||||
|
@ -271,7 +272,7 @@ public class Controller : ControllerBase
|
||||||
}
|
}
|
||||||
|
|
||||||
[HttpPost(nameof(CreateFolder))]
|
[HttpPost(nameof(CreateFolder))]
|
||||||
public ActionResult<Folder> CreateFolder(Folder folder, Token authToken)
|
public ActionResult<Folder> CreateFolder([FromBody] Folder folder, Token authToken)
|
||||||
{
|
{
|
||||||
var session = Db.GetSession(authToken);
|
var session = Db.GetSession(authToken);
|
||||||
|
|
||||||
|
@ -331,7 +332,7 @@ public class Controller : ControllerBase
|
||||||
var session = Db.GetSession(authToken);
|
var session = Db.GetSession(authToken);
|
||||||
|
|
||||||
// TODO: automatic BadRequest when properties are null during deserialization
|
// TODO: automatic BadRequest when properties are null during deserialization
|
||||||
var installation = Db.GetFolderById(installationAccess.InstallationId);
|
var installation = Db.GetInstallationById(installationAccess.InstallationId);
|
||||||
var user = Db.GetUserById(installationAccess.UserId);
|
var user = Db.GetUserById(installationAccess.UserId);
|
||||||
|
|
||||||
return session.RevokeUserAccessTo(user, installation)
|
return session.RevokeUserAccessTo(user, installation)
|
||||||
|
@ -342,7 +343,7 @@ public class Controller : ControllerBase
|
||||||
|
|
||||||
|
|
||||||
[HttpPut(nameof(UpdateUser))]
|
[HttpPut(nameof(UpdateUser))]
|
||||||
public ActionResult<User> UpdateUser(User updatedUser, Token authToken)
|
public ActionResult<User> UpdateUser([FromBody] User updatedUser, Token authToken)
|
||||||
{
|
{
|
||||||
var session = Db.GetSession(authToken);
|
var session = Db.GetSession(authToken);
|
||||||
|
|
||||||
|
@ -366,7 +367,7 @@ public class Controller : ControllerBase
|
||||||
|
|
||||||
|
|
||||||
[HttpPut(nameof(UpdateInstallation))]
|
[HttpPut(nameof(UpdateInstallation))]
|
||||||
public ActionResult<Installation> UpdateInstallation(Installation installation, Token authToken)
|
public ActionResult<Installation> UpdateInstallation([FromBody] Installation installation, Token authToken)
|
||||||
{
|
{
|
||||||
var session = Db.GetSession(authToken);
|
var session = Db.GetSession(authToken);
|
||||||
|
|
||||||
|
@ -378,7 +379,7 @@ public class Controller : ControllerBase
|
||||||
|
|
||||||
|
|
||||||
[HttpPut(nameof(UpdateFolder))]
|
[HttpPut(nameof(UpdateFolder))]
|
||||||
public ActionResult<Folder> UpdateFolder(Folder folder, Token authToken)
|
public ActionResult<Folder> UpdateFolder([FromBody] Folder folder, Token authToken)
|
||||||
{
|
{
|
||||||
var session = Db.GetSession(authToken);
|
var session = Db.GetSession(authToken);
|
||||||
|
|
||||||
|
@ -441,6 +442,36 @@ public class Controller : ControllerBase
|
||||||
: Unauthorized();
|
: Unauthorized();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[HttpPost(nameof(ResetPasswordRequest))]
|
||||||
|
public ActionResult<IEnumerable<Object>> ResetPasswordRequest(String username)
|
||||||
|
{
|
||||||
|
var user = Db.GetUserByEmail(username);
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
var session = new Session(user.HidePassword().HideParentIfUserHasNoAccessToParent(user));
|
||||||
|
|
||||||
|
return Db.Create(session) && Db.SendPasswordResetEmail(user, session.Token)
|
||||||
|
? Ok()
|
||||||
|
: Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet(nameof(ResetPassword))]
|
||||||
|
public ActionResult<IEnumerable<Object>> ResetPassword(Token token)
|
||||||
|
{
|
||||||
|
var user = Db.GetSession(token)?.User;
|
||||||
|
|
||||||
|
if (user is null)
|
||||||
|
return Unauthorized();
|
||||||
|
|
||||||
|
//todo dont hardcode url
|
||||||
|
return Db.DeleteUserPassword(user)
|
||||||
|
? RedirectToRoute("https://monitor.innov.energy")
|
||||||
|
: Unauthorized();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -9,8 +9,9 @@ public class Installation : TreeNode
|
||||||
public String Country { get; set; } = "";
|
public String Country { get; set; } = "";
|
||||||
|
|
||||||
// TODO: make relation
|
// TODO: make relation
|
||||||
[Ignore] public IReadOnlyList<String> OrderNumbers { get; set; } = Array.Empty<String>();
|
//public IReadOnlyList<String> OrderNumbers { get; set; } = Array.Empty<String>();
|
||||||
|
public String OrderNumbers { get; set; } = "";
|
||||||
|
|
||||||
public Double Lat { get; set; }
|
public Double Lat { get; set; }
|
||||||
public Double Long { get; set; }
|
public Double Long { get; set; }
|
||||||
|
|
||||||
|
|
|
@ -52,6 +52,11 @@ public static class FolderMethods
|
||||||
.Skip(1); // skip self
|
.Skip(1); // skip self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static IEnumerable<Folder> DescendantFoldersAndSelf(this Folder parent)
|
||||||
|
{
|
||||||
|
return parent
|
||||||
|
.TraverseDepthFirstPreOrder(ChildFolders);
|
||||||
|
}
|
||||||
public static Boolean IsDescendantOf(this Folder folder, Folder ancestor)
|
public static Boolean IsDescendantOf(this Folder folder, Folder ancestor)
|
||||||
{
|
{
|
||||||
return folder
|
return folder
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
using InnovEnergy.App.Backend.Database;
|
using InnovEnergy.App.Backend.Database;
|
||||||
|
using InnovEnergy.App.Backend.Relations;
|
||||||
using InnovEnergy.App.Backend.S3;
|
using InnovEnergy.App.Backend.S3;
|
||||||
using InnovEnergy.Lib.Utils;
|
using InnovEnergy.Lib.Utils;
|
||||||
|
|
||||||
|
@ -119,12 +120,28 @@ public static class InstallationMethods
|
||||||
return Db.Installations.Any(i => i.Id == installation.Id);
|
return Db.Installations.Any(i => i.Id == installation.Id);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IReadOnlyList<String> GetOrderNumbers(this Installation installation)
|
public static Boolean SetOrderNumbers(this Installation installation)
|
||||||
{
|
{
|
||||||
return Db.OrderNumber2Installation
|
foreach (var orderNumber in installation.OrderNumbers.Split(','))
|
||||||
|
{
|
||||||
|
|
||||||
|
var o2I = new OrderNumber2Installation
|
||||||
|
{
|
||||||
|
OrderNumber = orderNumber,
|
||||||
|
InstallationId = installation.Id
|
||||||
|
};
|
||||||
|
Db.Create(o2I);
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static String GetOrderNumbers(this Installation installation)
|
||||||
|
{
|
||||||
|
return string.Join(", ", Db.OrderNumber2Installation
|
||||||
.Where(i => i.InstallationId == installation.Id)
|
.Where(i => i.InstallationId == installation.Id)
|
||||||
.Select(i => i.OrderNumber)
|
.Select(i => i.OrderNumber)
|
||||||
.ToReadOnlyList<String>();
|
.ToReadOnlyList());
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Installation FillOrderNumbers(this Installation installation)
|
public static Installation FillOrderNumbers(this Installation installation)
|
||||||
|
|
|
@ -83,18 +83,19 @@ public static class SessionMethods
|
||||||
var user = session?.User;
|
var user = session?.User;
|
||||||
|
|
||||||
return user is not null
|
return user is not null
|
||||||
&& installation is not null
|
&& installation is not null
|
||||||
&& user.HasWriteAccess
|
&& user.HasWriteAccess
|
||||||
&& user.HasAccessToParentOf(installation)
|
&& user.HasAccessToParentOf(installation)
|
||||||
&& Db.Create(installation) // TODO: these two in a transaction
|
&& Db.Create(installation) // TODO: these two in a transaction
|
||||||
&& Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id })
|
&& installation.SetOrderNumbers()
|
||||||
&& await installation.CreateBucket()
|
&& Db.Create(new InstallationAccess { UserId = user.Id, InstallationId = installation.Id })
|
||||||
&& await installation.RenewS3Credentials(); // generation of access _after_ generation of
|
&& await installation.CreateBucket()
|
||||||
// bucket to prevent "zombie" access-rights.
|
&& await installation.RenewS3Credentials(); // generation of access _after_ generation of
|
||||||
// This might fuck us over if the creation of access rights fails,
|
// bucket to prevent "zombie" access-rights.
|
||||||
// as bucket-names are unique and bound to the installation id... -K
|
// This might fuck us over if the creation of access rights fails,
|
||||||
|
// as bucket-names are unique and bound to the installation id... -K
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Boolean Update(this Session? session, Installation? installation)
|
public static Boolean Update(this Session? session, Installation? installation)
|
||||||
{
|
{
|
||||||
var user = session?.User;
|
var user = session?.User;
|
||||||
|
@ -104,7 +105,7 @@ public static class SessionMethods
|
||||||
|
|
||||||
if (!Equals(originalOrderNumbers, installation?.OrderNumbers))
|
if (!Equals(originalOrderNumbers, installation?.OrderNumbers))
|
||||||
{
|
{
|
||||||
foreach (var orderNumber in installation!.OrderNumbers)
|
foreach (var orderNumber in installation!.OrderNumbers.Split(','))
|
||||||
{
|
{
|
||||||
if (originalOrderNumbers.Contains(orderNumber)) continue;
|
if (originalOrderNumbers.Contains(orderNumber)) continue;
|
||||||
var o2I = new OrderNumber2Installation
|
var o2I = new OrderNumber2Installation
|
||||||
|
@ -115,7 +116,7 @@ public static class SessionMethods
|
||||||
Db.Create(o2I);
|
Db.Create(o2I);
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var orderNumberOld in originalOrderNumbers)
|
foreach (var orderNumberOld in originalOrderNumbers.Split(','))
|
||||||
{
|
{
|
||||||
if (!installation!.OrderNumbers.Contains(orderNumberOld))
|
if (!installation!.OrderNumbers.Contains(orderNumberOld))
|
||||||
{
|
{
|
||||||
|
@ -138,13 +139,13 @@ public static class SessionMethods
|
||||||
public static async Task<Boolean> Delete(this Session? session, Installation? installation)
|
public static async Task<Boolean> Delete(this Session? session, Installation? installation)
|
||||||
{
|
{
|
||||||
var user = session?.User;
|
var user = session?.User;
|
||||||
|
|
||||||
return user is not null
|
return user is not null
|
||||||
&& installation is not null
|
&& installation is not null
|
||||||
&& user.HasWriteAccess
|
&& user.HasWriteAccess
|
||||||
&& user.HasAccessTo(installation)
|
&& user.HasAccessTo(installation)
|
||||||
&& Db.Delete(installation)
|
&& Db.Delete(installation)
|
||||||
&& await installation.DeleteBucket();
|
&& await installation.DeleteBucket();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Boolean Create(this Session? session, User newUser)
|
public static Boolean Create(this Session? session, User newUser)
|
||||||
|
|
|
@ -5,7 +5,7 @@ namespace InnovEnergy.App.Backend.DataTypes;
|
||||||
public abstract partial class TreeNode
|
public abstract partial class TreeNode
|
||||||
{
|
{
|
||||||
[PrimaryKey, AutoIncrement]
|
[PrimaryKey, AutoIncrement]
|
||||||
public virtual Int64 Id { get; set; }
|
public virtual Int64 Id { get; set; }
|
||||||
public virtual String Name { get; set; } = ""; // overridden by User (unique)
|
public virtual String Name { get; set; } = ""; // overridden by User (unique)
|
||||||
public String Information { get; set; } = ""; // unstructured random info
|
public String Information { get; set; } = ""; // unstructured random info
|
||||||
|
|
||||||
|
|
|
@ -1,14 +1,10 @@
|
||||||
using System.Data.SQLite;
|
|
||||||
using System.Reactive.Concurrency;
|
using System.Reactive.Concurrency;
|
||||||
using System.Reactive.Linq;
|
using System.Reactive.Linq;
|
||||||
using System.Runtime.InteropServices;
|
|
||||||
using CliWrap;
|
using CliWrap;
|
||||||
using CliWrap.Buffered;
|
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.Utils;
|
|
||||||
using Microsoft.Identity.Client;
|
|
||||||
using SQLite;
|
using SQLite;
|
||||||
using SQLiteConnection = SQLite.SQLiteConnection;
|
using SQLiteConnection = SQLite.SQLiteConnection;
|
||||||
|
|
||||||
|
@ -27,7 +23,8 @@ public static partial class Db
|
||||||
.Last().Name;
|
.Last().Name;
|
||||||
|
|
||||||
var fileConnection = new SQLiteConnection("DbBackups/"+latestDb);
|
var fileConnection = new SQLiteConnection("DbBackups/"+latestDb);
|
||||||
|
|
||||||
|
Console.Out.Write(latestDb);
|
||||||
var memoryConnection = new SQLiteConnection(":memory:");
|
var memoryConnection = new SQLiteConnection(":memory:");
|
||||||
|
|
||||||
// fileConnection.Backup(memoryConnection.DatabasePath);
|
// fileConnection.Backup(memoryConnection.DatabasePath);
|
||||||
|
@ -74,7 +71,7 @@ public static partial class Db
|
||||||
public static void BackupDatabase()
|
public static void BackupDatabase()
|
||||||
{
|
{
|
||||||
var filename = "db-" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + ".sqlite";
|
var filename = "db-" + DateTimeOffset.UtcNow.ToUnixTimeSeconds() + ".sqlite";
|
||||||
Connection.Backup("DbBackups/"+filename);
|
Connection.Backup("DbBackups/" + filename);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static TableQuery<Session> Sessions => Connection.Table<Session>();
|
public static TableQuery<Session> Sessions => Connection.Table<Session>();
|
||||||
|
@ -166,5 +163,16 @@ public static partial class Db
|
||||||
await installation.RenewS3Credentials();
|
await installation.RenewS3Credentials();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Boolean SendPasswordResetEmail(User user, String sessionToken)
|
||||||
|
{
|
||||||
|
return Email.Email.SendPasswordResetMessage(user, sessionToken);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Boolean DeleteUserPassword(User user)
|
||||||
|
{
|
||||||
|
user.Password = "";
|
||||||
|
user.MustResetPassword = true;
|
||||||
|
return Update(user);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -10,12 +10,18 @@ public static partial class Db
|
||||||
{
|
{
|
||||||
public static Boolean Delete(Folder folder)
|
public static Boolean Delete(Folder folder)
|
||||||
{
|
{
|
||||||
return RunTransaction(DeleteFolderAndAllItsDependencies);
|
var deleteSuccess= RunTransaction(DeleteFolderAndAllItsDependencies);
|
||||||
|
if (deleteSuccess)
|
||||||
|
{
|
||||||
|
BackupDatabase();
|
||||||
|
}
|
||||||
|
|
||||||
|
return deleteSuccess;
|
||||||
|
|
||||||
Boolean DeleteFolderAndAllItsDependencies()
|
Boolean DeleteFolderAndAllItsDependencies()
|
||||||
{
|
{
|
||||||
return folder
|
return folder
|
||||||
.DescendantFolders()
|
.DescendantFoldersAndSelf()
|
||||||
.All(DeleteDescendantFolderAndItsDependencies);
|
.All(DeleteDescendantFolderAndItsDependencies);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,10 +30,8 @@ public static partial class Db
|
||||||
FolderAccess .Delete(r => r.FolderId == f.Id);
|
FolderAccess .Delete(r => r.FolderId == f.Id);
|
||||||
Installations.Delete(r => r.ParentId == f.Id);
|
Installations.Delete(r => r.ParentId == f.Id);
|
||||||
var delete = Folders.Delete(r => r.Id == f.Id);
|
var delete = Folders.Delete(r => r.Id == f.Id);
|
||||||
var deleteSuccess = delete > 0;
|
|
||||||
if (deleteSuccess)
|
return delete>0;
|
||||||
BackupDatabase();
|
|
||||||
return deleteSuccess;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +46,7 @@ public static partial class Db
|
||||||
Boolean DeleteInstallationAndItsDependencies()
|
Boolean DeleteInstallationAndItsDependencies()
|
||||||
{
|
{
|
||||||
InstallationAccess.Delete(i => i.InstallationId == installation.Id);
|
InstallationAccess.Delete(i => i.InstallationId == installation.Id);
|
||||||
|
OrderNumber2Installation.Delete(i => i.InstallationId == installation.Id);
|
||||||
return Installations.Delete(i => i.Id == installation.Id) > 0;
|
return Installations.Delete(i => i.Id == installation.Id) > 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,54 @@
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,43 +0,0 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
|
||||||
using InnovEnergy.App.Backend.DataTypes;
|
|
||||||
using MailKit.Net.Smtp;
|
|
||||||
using MimeKit;
|
|
||||||
using JsonSerializer = System.Text.Json.JsonSerializer;
|
|
||||||
|
|
||||||
namespace InnovEnergy.App.Backend.Mailer;
|
|
||||||
public static class Mailer
|
|
||||||
{
|
|
||||||
[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<SmptConfig>(File.OpenRead("./Resources/smtpConfig.json"))!;
|
|
||||||
var email = new MimeMessage();
|
|
||||||
|
|
||||||
try
|
|
||||||
{
|
|
||||||
|
|
||||||
email.From.Add(new MailboxAddress("InnovEnergy", "noreply@innov.energy"));
|
|
||||||
email.To.Add(new MailboxAddress(emailRecipientUser.Name, emailRecipientUser.Email));
|
|
||||||
|
|
||||||
email.Subject = "Create a new password for your Innovenergy-Account";
|
|
||||||
email.Body = new TextPart(MimeKit.Text.TextFormat.Plain) {
|
|
||||||
Text = "Dear " + emailRecipientUser.Name + "\n Please create a new password for your Innovenergy-account." +
|
|
||||||
"\n To do this just login at https://HEEEEELP"
|
|
||||||
};
|
|
||||||
|
|
||||||
using var smtp = new SmtpClient();
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -3,6 +3,8 @@ 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;
|
||||||
|
|
||||||
namespace InnovEnergy.App.Backend;
|
namespace InnovEnergy.App.Backend;
|
||||||
|
|
||||||
|
@ -12,8 +14,8 @@ public static class Program
|
||||||
{
|
{
|
||||||
//Db.CreateFakeRelations();
|
//Db.CreateFakeRelations();
|
||||||
Db.Init();
|
Db.Init();
|
||||||
|
|
||||||
var builder = WebApplication.CreateBuilder(args);
|
var builder = WebApplication.CreateBuilder(args);
|
||||||
|
|
||||||
builder.Services.AddControllers();
|
builder.Services.AddControllers();
|
||||||
builder.Services.AddProblemDetails(setup =>
|
builder.Services.AddProblemDetails(setup =>
|
||||||
{
|
{
|
||||||
|
@ -21,7 +23,7 @@ public static class Program
|
||||||
setup.IncludeExceptionDetails = (ctx, env) => builder.Environment.IsDevelopment() || builder.Environment.IsStaging();
|
setup.IncludeExceptionDetails = (ctx, env) => 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
|
||||||
{
|
{
|
||||||
Detail = exception.Detail,
|
Detail = exception.Detail,
|
||||||
Status = exception.Status,
|
Status = exception.Status,
|
||||||
|
@ -38,6 +40,16 @@ public static class Program
|
||||||
});
|
});
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
|
app.Use(async (context, next) =>
|
||||||
|
{
|
||||||
|
var x = 2;
|
||||||
|
|
||||||
|
context.Request.WriteLine();
|
||||||
|
|
||||||
|
await next(context);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
app.UseForwardedHeaders(new ForwardedHeadersOptions
|
||||||
{
|
{
|
||||||
|
@ -51,12 +63,11 @@ public static class Program
|
||||||
}
|
}
|
||||||
|
|
||||||
app.UseCors(p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()) ;
|
app.UseCors(p => p.AllowAnyOrigin().AllowAnyHeader().AllowAnyMethod()) ;
|
||||||
app.UseHttpsRedirection();
|
//app.UseHttpsRedirection();
|
||||||
app.MapControllers();
|
app.MapControllers();
|
||||||
app.UseProblemDetails();
|
app.UseProblemDetails();
|
||||||
|
|
||||||
app.Run();
|
|
||||||
|
|
||||||
|
app.Run();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static OpenApiInfo OpenApiInfo { get; } = new OpenApiInfo
|
private static OpenApiInfo OpenApiInfo { get; } = new OpenApiInfo
|
||||||
|
|
|
@ -36,7 +36,7 @@ public class Session : Relation<String, Int64>
|
||||||
{
|
{
|
||||||
var token = new Byte[24];
|
var token = new Byte[24];
|
||||||
Random.Shared.NextBytes(token);
|
Random.Shared.NextBytes(token);
|
||||||
return Convert.ToBase64String(token);
|
return Convert.ToBase64String(token).Replace("/","");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -1,12 +1,11 @@
|
||||||
using InnovEnergy.App.Backend.S3;
|
using System.ComponentModel;
|
||||||
using InnovEnergy.Lib.Time.Unix;
|
using InnovEnergy.App.Backend.S3;
|
||||||
using InnovEnergy.Lib.Utils;
|
using InnovEnergy.Lib.Utils;
|
||||||
|
|
||||||
namespace S3Explorer;
|
namespace S3Explorer;
|
||||||
|
|
||||||
public static class Program
|
public static class Program
|
||||||
{
|
{
|
||||||
private const String BucketSalt = "-3e5b3069-214a-43ee-8d85-57d72000c19d";
|
|
||||||
|
|
||||||
public static async Task<Int32> Main(String[] args)
|
public static async Task<Int32> Main(String[] args)
|
||||||
{
|
{
|
||||||
|
@ -19,103 +18,78 @@ public static class Program
|
||||||
}
|
}
|
||||||
|
|
||||||
// Help message
|
// Help message
|
||||||
if (args.Length < 1 || args.Contains("-h"))
|
if (args.Length < 4 || args.Contains("-h"))
|
||||||
{
|
{
|
||||||
Console.WriteLine("Usage: S3Explorer installation-id [from-unix-time] [to-unix-time] [nb-data-points]");
|
Console.WriteLine("Usage: S3Explorer [BucketId] [from:Unix-time] [to:Unix-time] [#Data-points]");
|
||||||
Console.WriteLine("-h Shows this message.");
|
Console.WriteLine("-h Shows this message.");
|
||||||
Console.WriteLine("-s 🐍");
|
Console.WriteLine("-s 🐍");
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parsing Arguments
|
// Parsing Arguments
|
||||||
var bucketName = args[0] + BucketSalt;
|
var bucketName = args[0] + "-3e5b3069-214a-43ee-8d85-57d72000c19d";
|
||||||
var now = UnixTime.Now;
|
var startTime = Int64.Parse(args[1]);
|
||||||
|
var endTime = Int64.Parse(args[2]);
|
||||||
|
var numberOfDataPoints = Int64.Parse(args[3]);
|
||||||
|
|
||||||
var startTime = Int64.Parse(args.ElementAtOr(1, (now - UnixTimeSpan.FromSeconds(20)).ToString()));
|
var timeBetweenDataPoints = TimeBetweenDataPoints(startTime, endTime, numberOfDataPoints);
|
||||||
var endTime = Int64.Parse(args.ElementAtOr(2, now.ToString()));
|
|
||||||
var nDataPoints = Int64.Parse(args.ElementAtOr(3, "10"));
|
|
||||||
|
|
||||||
var timestampList = GetDataTimestamps(startTime, endTime, nDataPoints);
|
|
||||||
|
|
||||||
await PrintFiles(bucketName, timestampList);
|
// Building a List of the timestamps we want to grab the files for.
|
||||||
|
var timestampList = new List<String> { };
|
||||||
|
for (var i = startTime; i <= endTime; i += timeBetweenDataPoints)
|
||||||
|
{
|
||||||
|
//Rounding to even numbers only (we only save every second second)
|
||||||
|
timestampList.Add((i/2 *2).ToString());
|
||||||
|
}
|
||||||
|
|
||||||
|
await PrintFiles(bucketName,timestampList);
|
||||||
|
|
||||||
// Success
|
// Success
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static IEnumerable<Int64> GetDataTimestamps(Int64 startTime, Int64 endTime, Int64 nDataPoints)
|
private static async Task PrintFiles(String bucketName, List<String> timestampList)
|
||||||
{
|
{
|
||||||
// Calculating temporal distance of data files from the number of requested points. (rounding for int division)
|
var newestDataFilename = timestampList.Last();
|
||||||
var timeSpan = endTime - startTime;
|
var csvFileText = await GetFileText(bucketName, newestDataFilename);
|
||||||
var timeBetweenDataPoints = (Double)(timeSpan / nDataPoints);
|
|
||||||
timeBetweenDataPoints = Math.Max(2, timeBetweenDataPoints);
|
|
||||||
// We only upload data every second second so sampling more is impossible.
|
|
||||||
// If this ever changes we might have to change this as well.
|
|
||||||
|
|
||||||
// Building a List of the timestamps we want to grab the files for.
|
// Building Header-Row from the newest data
|
||||||
for (Double i = startTime; i <= endTime; i += timeBetweenDataPoints)
|
csvFileText
|
||||||
{
|
.Select(l => l.Split(";"))
|
||||||
//Rounding to even numbers only (we only save every second second)
|
.Select(l => l[0])
|
||||||
var integer = (Int64) Math.Round(i);
|
.Prepend("Timestamp")
|
||||||
yield return integer/2 * 2;
|
.JoinWith(";")
|
||||||
}
|
.WriteLine();
|
||||||
}
|
|
||||||
|
|
||||||
private static async Task PrintFiles(String bucketName, IEnumerable<Int64> timestampList)
|
|
||||||
{
|
|
||||||
|
|
||||||
var columns = new Dictionary<String, List<String?>>
|
|
||||||
{
|
|
||||||
["timestamp"] = new()
|
|
||||||
};
|
|
||||||
var index = 0;
|
|
||||||
|
|
||||||
foreach (var timestamp in timestampList)
|
foreach (var timestamp in timestampList)
|
||||||
{
|
{
|
||||||
var csvFileText = await GetFileText(bucketName, timestamp);
|
csvFileText = await GetFileText(bucketName, timestamp);
|
||||||
|
|
||||||
columns["timestamp"].Add(timestamp.ToString());
|
|
||||||
|
|
||||||
var dict = csvFileText is null
|
// Writing Data below data-keys in a timestamped row
|
||||||
? new Dictionary<String, String>()
|
csvFileText.Select(l => l.Split(";"))
|
||||||
: csvFileText
|
.Select(l => l[1])
|
||||||
.Select(l => l.Split(";"))
|
.Prepend(timestamp)
|
||||||
.ToDictionary(kv => kv[0], kv => kv[1]);
|
.JoinWith(";")
|
||||||
|
.WriteLine();
|
||||||
foreach (var key in dict.Keys)
|
|
||||||
{
|
|
||||||
// if a key is not yet present in columns we need to backfill it with nulls
|
|
||||||
if (!columns.ContainsKey(key))
|
|
||||||
columns[key] = Enumerable.Repeat<String?>(null, index).ToList();
|
|
||||||
|
|
||||||
columns[key].Add(dict[key]);
|
|
||||||
}
|
|
||||||
|
|
||||||
// if a key in columns is not present in this record (dict) (except the timestamp) we need to set it to null
|
|
||||||
foreach (var key in columns.Keys.Where(key => !dict.ContainsKey(key) && key != "timestamp"))
|
|
||||||
{
|
|
||||||
columns[key].Add(null);
|
|
||||||
}
|
|
||||||
index++;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var headerKeys = columns
|
|
||||||
.Keys
|
|
||||||
.OrderBy(k => k)
|
|
||||||
.Where(k => k != "timestamp")
|
|
||||||
.Prepend("timestamp")
|
|
||||||
.ToList();
|
|
||||||
|
|
||||||
String.Join(';', headerKeys).WriteLine();
|
|
||||||
|
|
||||||
Enumerable.Range(0, index)
|
|
||||||
.Select(i => headerKeys.Select(hk => columns[hk][i]).JoinWith(";"))
|
|
||||||
.ForEach(Console.WriteLine);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// This Method extracts the Text from a given csv file on the s3 bucket
|
private static Int64 TimeBetweenDataPoints(Int64 startTime, Int64 endTime, Int64 numberOfDataPoints)
|
||||||
private static async Task<IReadOnlyList<String>?> GetFileText(String bucketName, Int64 timestamp)
|
|
||||||
{
|
{
|
||||||
return await S3Access.Admin.GetFileLines(bucketName, $"{timestamp}.csv");
|
// Calculating temporal distance of data files from the number of requested points.
|
||||||
|
var timeSpan = endTime - startTime;
|
||||||
|
var timeBetweenDataPoints = timeSpan / numberOfDataPoints;
|
||||||
|
|
||||||
|
// We only upload data every second second so sampling more is impossible.
|
||||||
|
// If this ever changes we might have to change this as well.
|
||||||
|
timeBetweenDataPoints = Math.Max(timeBetweenDataPoints, 2);
|
||||||
|
return timeBetweenDataPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
// This Method extracts the Text from a given csv file on the s3 bucket
|
||||||
|
private static async Task<String[]> GetFileText(String bucketName, String filename)
|
||||||
|
{
|
||||||
|
return await S3Access.Admin.GetFileText(bucketName, filename + ".csv");
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -188,7 +188,7 @@ th { /* header cell */
|
||||||
|
|
||||||
await SendNewBatteryFirmware(installationIp);
|
await SendNewBatteryFirmware(installationIp);
|
||||||
var batteryTtyName = split[1].Split(".").Last();
|
var batteryTtyName = split[1].Split(".").Last();
|
||||||
var localCommand = $"/opt/innovenergy/scripts/upload-bms-firmware {batteryTtyName} 2 /opt/innovenergy/{FirmwareVersion}.bin";
|
var localCommand = $"/opt/innovenergy/scripts/upload-bms-firmware {batteryTtyName} 2 /opt/innovenergy/bms-firmware/{FirmwareVersion}.bin";
|
||||||
var installation = Db.Installations.First(installation => installation.Ip == installationIp);
|
var installation = Db.Installations.First(installation => installation.Ip == installationIp);
|
||||||
installation.BatteryUpdateStatus = "Running";
|
installation.BatteryUpdateStatus = "Running";
|
||||||
Db.Update(installation: installation);
|
Db.Update(installation: installation);
|
||||||
|
|
Binary file not shown.
|
@ -3,7 +3,7 @@ from flask import Flask
|
||||||
from json2html import json2html
|
from json2html import json2html
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
serverUrl = "https://127.0.0.1:7087/api" #todo change me
|
serverUrl = "https://127.0.0.1:8000/api" #todo change me
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def hello():
|
def hello():
|
||||||
|
|
|
@ -81,6 +81,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "S3Explorer", "App\S3Explore
|
||||||
EndProject
|
EndProject
|
||||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VrmGrabber", "App\VrmGrabber\VrmGrabber.csproj", "{88633C71-D701-49B3-A6DE-9D7CED9046E3}"
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VrmGrabber", "App\VrmGrabber\VrmGrabber.csproj", "{88633C71-D701-49B3-A6DE-9D7CED9046E3}"
|
||||||
EndProject
|
EndProject
|
||||||
|
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Mailer", "Lib\Mailer\Mailer.csproj", "{73B97F6E-2BDC-40DA-84A7-7FB0264387D6}"
|
||||||
|
EndProject
|
||||||
|
|
||||||
Global
|
Global
|
||||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||||
|
@ -208,6 +210,10 @@ Global
|
||||||
{88633C71-D701-49B3-A6DE-9D7CED9046E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{88633C71-D701-49B3-A6DE-9D7CED9046E3}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
{88633C71-D701-49B3-A6DE-9D7CED9046E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{88633C71-D701-49B3-A6DE-9D7CED9046E3}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{88633C71-D701-49B3-A6DE-9D7CED9046E3}.Release|Any CPU.Build.0 = Release|Any CPU
|
{88633C71-D701-49B3-A6DE-9D7CED9046E3}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
|
{73B97F6E-2BDC-40DA-84A7-7FB0264387D6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
{73B97F6E-2BDC-40DA-84A7-7FB0264387D6}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{73B97F6E-2BDC-40DA-84A7-7FB0264387D6}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
|
{73B97F6E-2BDC-40DA-84A7-7FB0264387D6}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
GlobalSection(NestedProjects) = preSolution
|
GlobalSection(NestedProjects) = preSolution
|
||||||
{CF4834CB-91B7-4172-AC13-ECDA8613CD17} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
{CF4834CB-91B7-4172-AC13-ECDA8613CD17} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
||||||
|
@ -244,5 +250,6 @@ Global
|
||||||
{1391165D-51F1-45B4-8B7F-042A20AA0277} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
|
{1391165D-51F1-45B4-8B7F-042A20AA0277} = {4931A385-24DC-4E78-BFF4-356F8D6D5183}
|
||||||
{EB56EF94-D8A7-4111-A8E7-A87EF13596DA} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
{EB56EF94-D8A7-4111-A8E7-A87EF13596DA} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
||||||
{88633C71-D701-49B3-A6DE-9D7CED9046E3} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
{88633C71-D701-49B3-A6DE-9D7CED9046E3} = {145597B4-3E30-45E6-9F72-4DD43194539A}
|
||||||
|
{73B97F6E-2BDC-40DA-84A7-7FB0264387D6} = {AD5B98A8-AB7F-4DA2-B66D-5B4E63E7D854}
|
||||||
EndGlobalSection
|
EndGlobalSection
|
||||||
EndGlobal
|
EndGlobal
|
||||||
|
|
|
@ -0,0 +1,55 @@
|
||||||
|
using MailKit.Net.Smtp;
|
||||||
|
using MimeKit;
|
||||||
|
|
||||||
|
namespace InnovEnergy.Lib.Mailer;
|
||||||
|
|
||||||
|
|
||||||
|
public class Mailer
|
||||||
|
{
|
||||||
|
private static MimeMessage Email = new();
|
||||||
|
|
||||||
|
public MimeMessage To(String name, String emailAddress)
|
||||||
|
{
|
||||||
|
Email.To.Add(new MailboxAddress(name, emailAddress));
|
||||||
|
return Email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MimeMessage From(String name, String emailAddress)
|
||||||
|
{
|
||||||
|
Email.From.Add(new MailboxAddress(name, emailAddress));
|
||||||
|
return Email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MimeMessage Subject(String subjectText)
|
||||||
|
{
|
||||||
|
Email.Subject = subjectText;
|
||||||
|
return Email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public MimeMessage Body(String bodyText)
|
||||||
|
{
|
||||||
|
Email.Body = new TextPart(MimeKit.Text.TextFormat.Plain)
|
||||||
|
{
|
||||||
|
Text = bodyText
|
||||||
|
};
|
||||||
|
return Email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Boolean SendEmailUsingSmtpConfig(SmtpConfig config)
|
||||||
|
{
|
||||||
|
try{
|
||||||
|
using var smtp = new SmtpClient();
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,10 @@
|
||||||
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
<Import Project="../InnovEnergy.Lib.props" />
|
||||||
|
<PropertyGroup>
|
||||||
|
<RootNamespace>InnovEnergy.Lib.Mailer</RootNamespace>
|
||||||
|
</PropertyGroup>
|
||||||
|
|
||||||
|
<ItemGroup>
|
||||||
|
<PackageReference Include="MailKit" Version="4.2.0" />
|
||||||
|
</ItemGroup>
|
||||||
|
</Project>
|
|
@ -1,13 +1,12 @@
|
||||||
using System.Diagnostics.CodeAnalysis;
|
using System.Diagnostics.CodeAnalysis;
|
||||||
|
namespace InnovEnergy.Lib.Mailer;
|
||||||
|
|
||||||
namespace InnovEnergy.App.Backend.Mailer;
|
|
||||||
|
|
||||||
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
[SuppressMessage("ReSharper", "ClassNeverInstantiated.Global")]
|
||||||
public class SmptConfig
|
public class SmtpConfig
|
||||||
{
|
{
|
||||||
public String Url { get; init; } = null!;
|
public String Url { get; init; } = null!;
|
||||||
public String Username { get; init; } = null!;
|
public String Username { get; init; } = null!;
|
||||||
public String Password { get; init; } = null!;
|
public String Password { get; init; } = null!;
|
||||||
public Int32 Port { get; init; } = 587;
|
public Int32 Port { get; init; } = 587;
|
||||||
|
|
||||||
}
|
}
|
|
@ -35,6 +35,7 @@
|
||||||
"yup": "^1.1.0"
|
"yup": "^1.1.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
|
||||||
"start": "react-scripts start",
|
"start": "react-scripts start",
|
||||||
"build": "react-scripts build",
|
"build": "react-scripts build",
|
||||||
"test": "react-scripts test",
|
"test": "react-scripts test",
|
||||||
|
|
|
@ -1,11 +1,11 @@
|
||||||
import useToken from "./hooks/useToken";
|
import useToken from "./hooks/useToken";
|
||||||
import Login from "./Login";
|
import Login from "./Login";
|
||||||
import { BrowserRouter, Navigate, Route, Routes } from "react-router-dom";
|
import {BrowserRouter, Navigate, Route, Routes} from "react-router-dom";
|
||||||
|
|
||||||
import { Container, Grid, Box } from "@mui/material";
|
import {Box, Container, Grid} from "@mui/material";
|
||||||
import routes from "./routes.json";
|
import routes from "./routes.json";
|
||||||
import { IntlProvider } from "react-intl";
|
import {IntlProvider} from "react-intl";
|
||||||
import { useContext, useState } from "react";
|
import {useContext, useState} from "react";
|
||||||
import en from "./lang/en.json";
|
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";
|
||||||
|
@ -14,10 +14,10 @@ import LogoutButton from "./components/Layout/LogoutButton";
|
||||||
import Users from "./components/Users/Users";
|
import Users from "./components/Users/Users";
|
||||||
import NavigationTabs from "./components/Layout/NavigationTabs";
|
import NavigationTabs from "./components/Layout/NavigationTabs";
|
||||||
import InstallationPage from "./components/Installations/InstallationPage";
|
import InstallationPage from "./components/Installations/InstallationPage";
|
||||||
import { UserContext } from "./components/Context/UserContextProvider";
|
import {UserContext} from "./components/Context/UserContextProvider";
|
||||||
import ResetPassword from "./ResetPassword";
|
import ResetPassword from "./ResetPassword";
|
||||||
import innovenergyLogo from "./resources/innoveng_logo_on_orange.png";
|
import innovenergyLogo from "./resources/innoveng_logo_on_orange.png";
|
||||||
import { colors } from "./index";
|
import {colors} from "./index";
|
||||||
|
|
||||||
const App = () => {
|
const App = () => {
|
||||||
const { token, setToken, removeToken } = useToken();
|
const { token, setToken, removeToken } = useToken();
|
||||||
|
@ -51,8 +51,10 @@ const App = () => {
|
||||||
>
|
>
|
||||||
<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} >
|
||||||
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
<a href="https://www.innov.energy/de/" >
|
||||||
|
<img src={innovenergyLogo} alt="innovenergy logo" height="100" />
|
||||||
|
</a>
|
||||||
</Grid>
|
</Grid>
|
||||||
<Grid
|
<Grid
|
||||||
item
|
item
|
||||||
|
|
|
@ -11,7 +11,7 @@ interface I_S3CredentialsContextProviderProps {
|
||||||
saveS3Credentials: (credentials: I_S3Credentials, id: string) => void;
|
saveS3Credentials: (credentials: I_S3Credentials, id: string) => void;
|
||||||
fetchData: (timestamp: UnixTime) => Promise<FetchResult<DataRecord>>;
|
fetchData: (timestamp: UnixTime) => Promise<FetchResult<DataRecord>>;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const S3CredentialsContext =
|
export const S3CredentialsContext =
|
||||||
createContext<I_S3CredentialsContextProviderProps>({
|
createContext<I_S3CredentialsContextProviderProps>({
|
||||||
s3Credentials: {} as I_S3Credentials,
|
s3Credentials: {} as I_S3Credentials,
|
||||||
|
@ -27,7 +27,7 @@ const S3CredentialsContextProvider = ({
|
||||||
const [s3Credentials, setS3Credentials] = useState<I_S3Credentials>();
|
const [s3Credentials, setS3Credentials] = useState<I_S3Credentials>();
|
||||||
|
|
||||||
const saveS3Credentials = (credentials: I_S3Credentials, id: string) => {
|
const saveS3Credentials = (credentials: I_S3Credentials, id: string) => {
|
||||||
const s3Bucket = id + "-3e5b3069-214a-43ee-8d85-57d72000c10d";
|
const s3Bucket = id + "-3e5b3069-214a-43ee-8d85-57d72000c19d";
|
||||||
setS3Credentials({ s3Bucket, ...credentials });
|
setS3Credentials({ s3Bucket, ...credentials });
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
import { createContext, ReactNode, useState } from "react";
|
import { createContext, ReactNode, useState } from "react";
|
||||||
import { I_User } from "../../util/user.util";
|
import { I_User } from "../../util/user.util";
|
||||||
|
|
||||||
|
|
||||||
interface I_InstallationContextProviderProps {
|
interface I_InstallationContextProviderProps {
|
||||||
currentUser?: I_User;
|
currentUser?: I_User;
|
||||||
setCurrentUser: (value: I_User) => void;
|
setCurrentUser: (value: I_User) => void;
|
||||||
|
|
|
@ -33,7 +33,7 @@ const InstallationForm = (props: I_InstallationFormProps) => {
|
||||||
|
|
||||||
const readOnly = !getCurrentUser().hasWriteAccess;
|
const readOnly = !getCurrentUser().hasWriteAccess;
|
||||||
const intl = useIntl();
|
const intl = useIntl();
|
||||||
|
|
||||||
const validationSchema = Yup.object().shape({
|
const validationSchema = Yup.object().shape({
|
||||||
name: Yup.string().required(
|
name: Yup.string().required(
|
||||||
intl.formatMessage({
|
intl.formatMessage({
|
||||||
|
@ -53,6 +53,19 @@ const InstallationForm = (props: I_InstallationFormProps) => {
|
||||||
defaultMessage: "Location is required",
|
defaultMessage: "Location is required",
|
||||||
})
|
})
|
||||||
),
|
),
|
||||||
|
country: Yup.string().required(
|
||||||
|
intl.formatMessage({
|
||||||
|
id: "requiredCountry",
|
||||||
|
defaultMessage: "Country is required",
|
||||||
|
})
|
||||||
|
),
|
||||||
|
orderNumbers: Yup.string().required(
|
||||||
|
intl.formatMessage({
|
||||||
|
id: "requiredOrderNumber",
|
||||||
|
defaultMessage: "Order Number is required",
|
||||||
|
})
|
||||||
|
),
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const formik = useFormik({
|
const formik = useFormik({
|
||||||
|
@ -64,6 +77,12 @@ const InstallationForm = (props: I_InstallationFormProps) => {
|
||||||
orderNumbers: values.orderNumbers,
|
orderNumbers: values.orderNumbers,
|
||||||
},
|
},
|
||||||
onSubmit: (formikValues) => {
|
onSubmit: (formikValues) => {
|
||||||
|
/*const updatedValues = {
|
||||||
|
...formikValues,
|
||||||
|
|
||||||
|
orderNumbers: formikValues.orderNumbers.split(','),
|
||||||
|
};*/
|
||||||
|
|
||||||
handleSubmit(values, formikValues)
|
handleSubmit(values, formikValues)
|
||||||
.then(() => {
|
.then(() => {
|
||||||
setOpen(true);
|
setOpen(true);
|
||||||
|
@ -164,12 +183,22 @@ const InstallationForm = (props: I_InstallationFormProps) => {
|
||||||
additionalButtons.map((button) => button)}
|
additionalButtons.map((button) => button)}
|
||||||
{!readOnly && (
|
{!readOnly && (
|
||||||
<InnovenergyButton id="installation-form-submit-button" type="submit">
|
<InnovenergyButton id="installation-form-submit-button" type="submit">
|
||||||
|
|
||||||
|
|
||||||
<FormattedMessage
|
<FormattedMessage
|
||||||
id="applyChanges"
|
id="applyChanges"
|
||||||
defaultMessage="Apply changes"
|
defaultMessage="Apply changes"
|
||||||
/>
|
/>
|
||||||
</InnovenergyButton>
|
</InnovenergyButton>
|
||||||
)}
|
)}
|
||||||
|
{!readOnly && (
|
||||||
|
<InnovenergyButton id="installation-form-submit-button" type="submit" sx={{ marginLeft: 1}}>
|
||||||
|
<FormattedMessage
|
||||||
|
id="deleteInstallation"
|
||||||
|
defaultMessage="Delete installation"
|
||||||
|
/>
|
||||||
|
</InnovenergyButton>
|
||||||
|
)}
|
||||||
</Grid>
|
</Grid>
|
||||||
<Snackbar
|
<Snackbar
|
||||||
open={open}
|
open={open}
|
||||||
|
|
|
@ -66,4 +66,4 @@ const Installations = () => {
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Installations;
|
export default Installations;
|
|
@ -16,12 +16,7 @@ const NavigationTabs = () => {
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<InnovenergyTabs
|
<InnovenergyTabs
|
||||||
sx={{
|
|
||||||
paddingTop: 0,
|
|
||||||
"&.MuiTabs-root": {
|
|
||||||
borderBottom: "none",
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
id="navigation-buttons-tabs"
|
id="navigation-buttons-tabs"
|
||||||
value={routeMatch?.pattern?.path}
|
value={routeMatch?.pattern?.path}
|
||||||
>
|
>
|
||||||
|
|
|
@ -2,6 +2,7 @@
|
||||||
"liveView": "Live view",
|
"liveView": "Live view",
|
||||||
"allInstallations": "All installations",
|
"allInstallations": "All installations",
|
||||||
"applyChanges": "Apply changes",
|
"applyChanges": "Apply changes",
|
||||||
|
"deleteInstallation": "Delete Installation",
|
||||||
"country": "Country",
|
"country": "Country",
|
||||||
"customerName": "Customer name",
|
"customerName": "Customer name",
|
||||||
"english": "English",
|
"english": "English",
|
||||||
|
@ -44,6 +45,7 @@
|
||||||
"requiredLocation": "Location is required",
|
"requiredLocation": "Location is required",
|
||||||
"requiredName": "Name is required",
|
"requiredName": "Name is required",
|
||||||
"requiredRegion": "Region is required",
|
"requiredRegion": "Region is required",
|
||||||
|
"requiredOrderNumber": "Required Order Number",
|
||||||
"submit": "Submit",
|
"submit": "Submit",
|
||||||
"user": "User",
|
"user": "User",
|
||||||
"userTabs": "user tabs"
|
"userTabs": "user tabs"
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
import axios from 'axios';
|
||||||
|
|
||||||
|
export const axiosConfigWithoutToken = axios.create({
|
||||||
|
baseURL: 'https://localhost:7087/api'
|
||||||
|
});
|
||||||
|
|
||||||
|
const axiosConfig = axios.create({
|
||||||
|
baseURL: 'https://localhost:7087/api'
|
||||||
|
});
|
||||||
|
|
||||||
|
axiosConfig.defaults.params = {};
|
||||||
|
axiosConfig.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
const tokenString = localStorage.getItem('token');
|
||||||
|
const token = tokenString !== null ? tokenString : '';
|
||||||
|
if (token) {
|
||||||
|
config.params['authToken'] = token;
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
return Promise.reject(error);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
export default axiosConfig;
|
Binary file not shown.
After Width: | Height: | Size: 15 KiB |
|
@ -0,0 +1,126 @@
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import {
|
||||||
|
Card,
|
||||||
|
Divider,
|
||||||
|
Grid,
|
||||||
|
Table,
|
||||||
|
TableBody,
|
||||||
|
TableCell,
|
||||||
|
TableContainer,
|
||||||
|
TableHead,
|
||||||
|
TableRow,
|
||||||
|
Typography,
|
||||||
|
useTheme
|
||||||
|
} from '@mui/material';
|
||||||
|
import { InnovEnergyUser } from 'src/interfaces/UserTypes';
|
||||||
|
import User from './User';
|
||||||
|
|
||||||
|
interface FlatUsersViewProps {
|
||||||
|
users: InnovEnergyUser[];
|
||||||
|
fetchDataAgain: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const FlatUsersView = (props: FlatUsersViewProps) => {
|
||||||
|
const [selectedUser, setSelectedUser] = useState<number>(-1);
|
||||||
|
const selectedBulkActions = selectedUser !== -1;
|
||||||
|
|
||||||
|
const handleSelectOneUser = (installationID: number): void => {
|
||||||
|
if (selectedUser != installationID) {
|
||||||
|
setSelectedUser(installationID);
|
||||||
|
} else {
|
||||||
|
setSelectedUser(-1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const theme = useTheme();
|
||||||
|
const [isRowHovered, setHoveredRow] = useState(-1);
|
||||||
|
|
||||||
|
const handleRowMouseEnter = (id: number) => {
|
||||||
|
setHoveredRow(id);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleRowMouseLeave = () => {
|
||||||
|
setHoveredRow(-1);
|
||||||
|
};
|
||||||
|
const findUser = (id: number) => {
|
||||||
|
return props.users.find((user) => user.id === id);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Grid container spacing={1} sx={{ marginTop: '1px' }}>
|
||||||
|
<Grid item xs={12} md={3}>
|
||||||
|
<Card>
|
||||||
|
<Divider />
|
||||||
|
<TableContainer>
|
||||||
|
<Table>
|
||||||
|
<TableHead>
|
||||||
|
<TableRow>
|
||||||
|
<TableCell padding="checkbox"></TableCell>
|
||||||
|
<TableCell>Username</TableCell>
|
||||||
|
<TableCell>Email</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
</TableHead>
|
||||||
|
<TableBody>
|
||||||
|
{props.users.map((user) => {
|
||||||
|
const isInstallationSelected = user.id === selectedUser;
|
||||||
|
const rowStyles =
|
||||||
|
isRowHovered === user.id
|
||||||
|
? {
|
||||||
|
cursor: 'pointer',
|
||||||
|
backgroundColor: theme.colors.primary.lighter // Set your desired hover background color here
|
||||||
|
}
|
||||||
|
: {};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<TableRow
|
||||||
|
hover
|
||||||
|
key={user.id}
|
||||||
|
selected={isInstallationSelected}
|
||||||
|
onClick={() => handleSelectOneUser(user.id)}
|
||||||
|
style={rowStyles}
|
||||||
|
onMouseEnter={() => handleRowMouseEnter(user.id)}
|
||||||
|
onMouseLeave={() => handleRowMouseLeave()}
|
||||||
|
>
|
||||||
|
<TableCell padding="checkbox"></TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
fontWeight="bold"
|
||||||
|
color="text.primary"
|
||||||
|
gutterBottom
|
||||||
|
noWrap
|
||||||
|
>
|
||||||
|
{user.name}
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
<TableCell>
|
||||||
|
<Typography
|
||||||
|
variant="body1"
|
||||||
|
fontWeight="bold"
|
||||||
|
color="text.primary"
|
||||||
|
gutterBottom
|
||||||
|
noWrap
|
||||||
|
>
|
||||||
|
{user.email}
|
||||||
|
</Typography>
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</TableContainer>
|
||||||
|
</Card>
|
||||||
|
</Grid>
|
||||||
|
|
||||||
|
{selectedBulkActions && (
|
||||||
|
<User
|
||||||
|
current_user={findUser(selectedUser)}
|
||||||
|
fetchDataAgain={props.fetchDataAgain}
|
||||||
|
></User>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default FlatUsersView;
|
|
@ -0,0 +1,90 @@
|
||||||
|
import React, { useContext, useEffect, useState } from 'react';
|
||||||
|
import {
|
||||||
|
FormControl,
|
||||||
|
Grid,
|
||||||
|
InputAdornment,
|
||||||
|
TextField,
|
||||||
|
useTheme
|
||||||
|
} from '@mui/material';
|
||||||
|
import SearchTwoToneIcon from '@mui/icons-material/SearchTwoTone';
|
||||||
|
import FlatUsersView from './FlatUsersView';
|
||||||
|
import { UsersContext } from '../../../contexts/UsersContextProvider';
|
||||||
|
import Button from '@mui/material/Button';
|
||||||
|
import UserForm from './userForm';
|
||||||
|
import { UserContext } from '../../../contexts/userContext';
|
||||||
|
|
||||||
|
function UsersSearch() {
|
||||||
|
const theme = useTheme();
|
||||||
|
const [searchTerm, setSearchTerm] = useState('');
|
||||||
|
const { availableUsers, fetchAvailableUsers } = useContext(UsersContext);
|
||||||
|
const [filteredData, setFilteredData] = useState(availableUsers);
|
||||||
|
const [openModal, setOpenModal] = useState(false);
|
||||||
|
const context = useContext(UserContext);
|
||||||
|
const { currentUser, setUser } = context;
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
fetchAvailableUsers();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const fetchDataAgain = () => {
|
||||||
|
fetchAvailableUsers();
|
||||||
|
};
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const filtered = availableUsers.filter((item) =>
|
||||||
|
item.name.toLowerCase().includes(searchTerm.toLowerCase())
|
||||||
|
);
|
||||||
|
setFilteredData(filtered);
|
||||||
|
}, [searchTerm, availableUsers]);
|
||||||
|
|
||||||
|
const handleSubmit = () => {
|
||||||
|
setOpenModal(true);
|
||||||
|
};
|
||||||
|
const handleUserFormSubmit = () => {
|
||||||
|
setOpenModal(false);
|
||||||
|
fetchAvailableUsers();
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleUserFormCancel = () => {
|
||||||
|
setOpenModal(false);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Grid container spacing={1}>
|
||||||
|
<Grid item xs={12} md={3}>
|
||||||
|
{currentUser.hasWriteAccess && (
|
||||||
|
<Button variant="contained" onClick={handleSubmit}>
|
||||||
|
Create user
|
||||||
|
</Button>
|
||||||
|
)}
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
{openModal && (
|
||||||
|
<UserForm cancel={handleUserFormCancel} submit={handleUserFormSubmit} />
|
||||||
|
)}
|
||||||
|
<Grid container spacing={1} sx={{ marginTop: '1px' }}>
|
||||||
|
<Grid item xs={12} md={3}>
|
||||||
|
<FormControl variant="outlined" fullWidth>
|
||||||
|
<TextField
|
||||||
|
placeholder="Search"
|
||||||
|
value={searchTerm}
|
||||||
|
onChange={(e) => setSearchTerm(e.target.value)}
|
||||||
|
fullWidth
|
||||||
|
InputProps={{
|
||||||
|
startAdornment: (
|
||||||
|
<InputAdornment position="start">
|
||||||
|
<SearchTwoToneIcon />
|
||||||
|
</InputAdornment>
|
||||||
|
)
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
</FormControl>
|
||||||
|
</Grid>
|
||||||
|
</Grid>
|
||||||
|
<FlatUsersView users={filteredData} fetchDataAgain={fetchDataAgain} />
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default UsersSearch;
|
|
@ -0,0 +1,25 @@
|
||||||
|
import Footer from 'src/components/Footer';
|
||||||
|
import { Box, Container, Grid, useTheme } from '@mui/material';
|
||||||
|
import UsersSearch from './UsersSearch';
|
||||||
|
import UsersContextProvider from 'src/contexts/UsersContextProvider';
|
||||||
|
|
||||||
|
function Users() {
|
||||||
|
const theme = useTheme();
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<UsersContextProvider>
|
||||||
|
<Container maxWidth="xl" sx={{ marginTop: '20px' }}>
|
||||||
|
<Grid item xs={12}>
|
||||||
|
<Box p={4}>
|
||||||
|
<UsersSearch />
|
||||||
|
</Box>
|
||||||
|
</Grid>
|
||||||
|
</Container>
|
||||||
|
<Footer />
|
||||||
|
</UsersContextProvider>
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default Users;
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"information": "Information",
|
||||||
|
"addNewChild": "Neues Kind hinzufügen",
|
||||||
|
"addNewDialogButton": "Neue Dialogschaltfläche hinzufügen",
|
||||||
|
"addUser": "Nutzer erstellen",
|
||||||
|
"alarms": "Alarme",
|
||||||
|
"applyChanges": "Änderungen speichern",
|
||||||
|
"country": "Land",
|
||||||
|
"createNewFolder": "Neuen Ordner erstellen",
|
||||||
|
"createNewUser": "Neuen Nutzer erstellen",
|
||||||
|
"customerName": "Kundenname",
|
||||||
|
"email": "Email",
|
||||||
|
"english": "Englisch",
|
||||||
|
"error": "Fehler",
|
||||||
|
"folder": "Ordner",
|
||||||
|
"german": "Deutsch",
|
||||||
|
"groupTabs": "Gruppen",
|
||||||
|
"groupTree": "Gruppenbaum",
|
||||||
|
"information": "Information",
|
||||||
|
"inheritedAccess": "Vererbter Zugriff von",
|
||||||
|
"installation": "Installation",
|
||||||
|
"installationTabs": "Installationen",
|
||||||
|
"installations": "Installationen",
|
||||||
|
"lastWeek": "Letzte Woche",
|
||||||
|
"location": "Standort",
|
||||||
|
"log": "Logbuch",
|
||||||
|
"logout": "Abmelden",
|
||||||
|
"makeASelection": "Bitte wählen Sie links eine Auswahl",
|
||||||
|
"manageAccess": "Zugriff verwalten",
|
||||||
|
"move": "Verschieben",
|
||||||
|
"moveTo": "Verschieben zu",
|
||||||
|
"moveTree": "Baum verschieben",
|
||||||
|
"name": "Name",
|
||||||
|
"navigationTabs": "Navigation",
|
||||||
|
"orderNumbers": "Bestellnummer",
|
||||||
|
"region": "Region",
|
||||||
|
"requiredLocation": "Standort ist erforderlich",
|
||||||
|
"requiredName": "Name ist erforderlich",
|
||||||
|
"requiredRegion": "Region ist erforderlich",
|
||||||
|
"search": "Suche",
|
||||||
|
"submit": "Senden",
|
||||||
|
"updateFolderErrorMessage": "Fehler, Ordner kann nicht aktualisiert werden",
|
||||||
|
"updatedSuccessfully": "Erfolgreich aktualisiert",
|
||||||
|
"user": "Nutzer",
|
||||||
|
"userTabs": "Nutzer",
|
||||||
|
"users": "Nutzer"
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
{
|
||||||
|
"liveView": "Live view",
|
||||||
|
"allInstallations": "All installations",
|
||||||
|
"applyChanges": "Apply changes",
|
||||||
|
"deleteInstallation": "Delete Installation",
|
||||||
|
"country": "Country",
|
||||||
|
"customerName": "Customer name",
|
||||||
|
"english": "English",
|
||||||
|
"german": "German",
|
||||||
|
"installation": "Installation",
|
||||||
|
"location": "Location",
|
||||||
|
"log": "Log",
|
||||||
|
"orderNumbers": "Order numbers",
|
||||||
|
"region": "Region",
|
||||||
|
"search": "Search",
|
||||||
|
"users": "Users",
|
||||||
|
"logout": "Logout",
|
||||||
|
"updatedSuccessfully": "Updated successfully",
|
||||||
|
"groups": "Groups",
|
||||||
|
"group": "Group",
|
||||||
|
"folder": "Folder",
|
||||||
|
"updateFolderErrorMessage": "Couldn't update folder, an error occured",
|
||||||
|
"Information": "Information",
|
||||||
|
"addNewChild": "Add new child",
|
||||||
|
"addNewDialogButton": "Add new dialog button",
|
||||||
|
"addUser": "Create user",
|
||||||
|
"createNewFolder": "Create new folder",
|
||||||
|
"createNewUser": "Create new user",
|
||||||
|
"email": "Email",
|
||||||
|
"error": "",
|
||||||
|
"groupTabs": "Group tabs",
|
||||||
|
"groupTree": "Group tree",
|
||||||
|
"information": "Information",
|
||||||
|
"inheritedAccess": "Inherited access from",
|
||||||
|
"installationTabs": "Installation tabs",
|
||||||
|
"installations": "Installations",
|
||||||
|
"lastWeek": "Last week",
|
||||||
|
"makeASelection": "Please make a selection on the left",
|
||||||
|
"manageAccess": "Manage access",
|
||||||
|
"move": "Move",
|
||||||
|
"moveTo": "Move to",
|
||||||
|
"moveTree": "Move tree",
|
||||||
|
"name": "Name",
|
||||||
|
"navigationTabs": "Navigation tabs",
|
||||||
|
"requiredLocation": "Location is required",
|
||||||
|
"requiredName": "Name is required",
|
||||||
|
"requiredRegion": "Region is required",
|
||||||
|
"requiredOrderNumber": "Required Order Number",
|
||||||
|
"submit": "Submit",
|
||||||
|
"user": "User",
|
||||||
|
"userTabs": "user tabs"
|
||||||
|
}
|
|
@ -0,0 +1,47 @@
|
||||||
|
{
|
||||||
|
"information": "Informations",
|
||||||
|
"addNewChild": "Ajouter un nouvel enfant",
|
||||||
|
"addNewDialogButton": "Ajouter un nouveau bouton de dialogue",
|
||||||
|
"addUser": "Créer un utilisateur",
|
||||||
|
"alarms": "Alarmes",
|
||||||
|
"applyChanges": "Appliquer les modifications",
|
||||||
|
"country": "Pays",
|
||||||
|
"createNewFolder": "Créer un nouveau dossier",
|
||||||
|
"createNewUser": "Créer un nouvel utilisateur",
|
||||||
|
"customerName": "Nom du client",
|
||||||
|
"email": "E-mail",
|
||||||
|
"english": "Anglais",
|
||||||
|
"error": "Erreur",
|
||||||
|
"folder": "Dossier",
|
||||||
|
"german": "Allemand",
|
||||||
|
"groupTabs": "Onglets de groupe",
|
||||||
|
"groupTree": "Arbre de groupe",
|
||||||
|
"information": "Informations",
|
||||||
|
"inheritedAccess": "Accès hérité de",
|
||||||
|
"installation": "Installation",
|
||||||
|
"installationTabs": "Onglets d'installation",
|
||||||
|
"installations": "Installations",
|
||||||
|
"lastWeek": "La semaine dernière",
|
||||||
|
"location": "Localisation",
|
||||||
|
"log": "Journal",
|
||||||
|
"logout": "Déconnexion",
|
||||||
|
"makeASelection": "Veuillez faire une sélection à gauche",
|
||||||
|
"manageAccess": "Gérer l'accès",
|
||||||
|
"move": "Déplacer",
|
||||||
|
"moveTo": "Déplacer à",
|
||||||
|
"moveTree": "Déplacer l'arbre",
|
||||||
|
"name": "Nom",
|
||||||
|
"navigationTabs": "Onglets de navigation",
|
||||||
|
"orderNumbers": "Numéro de commande",
|
||||||
|
"region": "Région",
|
||||||
|
"requiredLocation": "L'emplacement est requis",
|
||||||
|
"requiredName": "Le nom est obligatoire",
|
||||||
|
"requiredRegion": "La région est obligatoire",
|
||||||
|
"search": "Recherche",
|
||||||
|
"submit": "Soumettre",
|
||||||
|
"updateFolderErrorMessage": "Une erreur s'est produite, impossible de mettre à jour le dossier.",
|
||||||
|
"updatedSuccessfully": "Mise à jour réussie",
|
||||||
|
"user": "Utilisateur",
|
||||||
|
"userTabs": "Onglets utilisateurs",
|
||||||
|
"users": "Utilisateurs"
|
||||||
|
}
|
Loading…
Reference in New Issue