Merge pull request #1 from Moonlight-Panel/ServiceManager
Service manager
This commit is contained in:
commit
faa4fc6e18
33 changed files with 1158 additions and 163 deletions
|
@ -39,6 +39,8 @@ public class DataContext : DbContext
|
|||
public DbSet<Revoke> Revokes { get; set; }
|
||||
public DbSet<NotificationClient> NotificationClients { get; set; }
|
||||
public DbSet<NotificationAction> NotificationActions { get; set; }
|
||||
public DbSet<AaPanel> AaPanels { get; set; }
|
||||
public DbSet<Website> Websites { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
{
|
||||
|
|
9
Moonlight/App/Database/Entities/AaPanel.cs
Normal file
9
Moonlight/App/Database/Entities/AaPanel.cs
Normal file
|
@ -0,0 +1,9 @@
|
|||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
public class AaPanel
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public string Url { get; set; } = "";
|
||||
public string Key { get; set; } = "";
|
||||
public string BaseDomain { get; set; } = "";
|
||||
}
|
|
@ -3,6 +3,8 @@
|
|||
public class Database
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int AaPanelId { get; set; }
|
||||
public int InternalAaPanelId { get; set; }
|
||||
public User Owner { get; set; }
|
||||
public AaPanel AaPanel { get; set; }
|
||||
public string Name { get; set; }
|
||||
}
|
13
Moonlight/App/Database/Entities/Website.cs
Normal file
13
Moonlight/App/Database/Entities/Website.cs
Normal file
|
@ -0,0 +1,13 @@
|
|||
namespace Moonlight.App.Database.Entities;
|
||||
|
||||
public class Website
|
||||
{
|
||||
public int Id { get; set; }
|
||||
public int InternalAaPanelId { get; set; }
|
||||
public AaPanel AaPanel { get; set; }
|
||||
public User Owner { get; set; }
|
||||
public string DomainName { get; set; }
|
||||
public string PhpVersion { get; set; }
|
||||
public string FtpUsername { get; set; }
|
||||
public string FtpPassword { get; set; }
|
||||
}
|
|
@ -18,5 +18,6 @@ public enum AuditLogType
|
|||
DisableTotp,
|
||||
AddDomainRecord,
|
||||
UpdateDomainRecord,
|
||||
DeleteDomainRecord
|
||||
DeleteDomainRecord,
|
||||
PasswordReset
|
||||
}
|
44
Moonlight/App/Repositories/AaPanelRepository.cs
Normal file
44
Moonlight/App/Repositories/AaPanelRepository.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Database.Entities;
|
||||
|
||||
namespace Moonlight.App.Repositories;
|
||||
|
||||
public class AaPanelRepository : IDisposable
|
||||
{
|
||||
private readonly DataContext DataContext;
|
||||
|
||||
public AaPanelRepository(DataContext dataContext)
|
||||
{
|
||||
DataContext = dataContext;
|
||||
}
|
||||
|
||||
public DbSet<AaPanel> Get()
|
||||
{
|
||||
return DataContext.AaPanels;
|
||||
}
|
||||
|
||||
public AaPanel Add(AaPanel aaPanel)
|
||||
{
|
||||
var x = DataContext.AaPanels.Add(aaPanel);
|
||||
DataContext.SaveChanges();
|
||||
return x.Entity;
|
||||
}
|
||||
|
||||
public void Update(AaPanel aaPanel)
|
||||
{
|
||||
DataContext.AaPanels.Update(aaPanel);
|
||||
DataContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void Delete(AaPanel aaPanel)
|
||||
{
|
||||
DataContext.AaPanels.Remove(aaPanel);
|
||||
DataContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DataContext.Dispose();
|
||||
}
|
||||
}
|
44
Moonlight/App/Repositories/WebsiteRepository.cs
Normal file
44
Moonlight/App/Repositories/WebsiteRepository.cs
Normal file
|
@ -0,0 +1,44 @@
|
|||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database;
|
||||
using Moonlight.App.Database.Entities;
|
||||
|
||||
namespace Moonlight.App.Repositories;
|
||||
|
||||
public class WebsiteRepository : IDisposable
|
||||
{
|
||||
private readonly DataContext DataContext;
|
||||
|
||||
public WebsiteRepository(DataContext dataContext)
|
||||
{
|
||||
DataContext = dataContext;
|
||||
}
|
||||
|
||||
public DbSet<Website> Get()
|
||||
{
|
||||
return DataContext.Websites;
|
||||
}
|
||||
|
||||
public Website Add(Website website)
|
||||
{
|
||||
var x = DataContext.Websites.Add(website);
|
||||
DataContext.SaveChanges();
|
||||
return x.Entity;
|
||||
}
|
||||
|
||||
public void Update(Website website)
|
||||
{
|
||||
DataContext.Websites.Update(website);
|
||||
DataContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void Delete(Website website)
|
||||
{
|
||||
DataContext.Websites.Remove(website);
|
||||
DataContext.SaveChanges();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
DataContext.Dispose();
|
||||
}
|
||||
}
|
76
Moonlight/App/Services/DatabaseService.cs
Normal file
76
Moonlight/App/Services/DatabaseService.cs
Normal file
|
@ -0,0 +1,76 @@
|
|||
using aaPanelSharp;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Repositories;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class DatabaseService
|
||||
{
|
||||
private readonly DatabaseRepository DatabaseRepository;
|
||||
private readonly AaPanelRepository AaPanelRepository;
|
||||
|
||||
public DatabaseService(DatabaseRepository databaseRepository, AaPanelRepository aaPanelRepository)
|
||||
{
|
||||
DatabaseRepository = databaseRepository;
|
||||
AaPanelRepository = aaPanelRepository;
|
||||
}
|
||||
|
||||
public Task<Database.Entities.Database> Create(string name, string password, User u, AaPanel? a = null)
|
||||
{
|
||||
if (DatabaseRepository.Get().Any(x => x.Name == name))
|
||||
throw new DisplayException("A database with this name has been already created");
|
||||
|
||||
var aaPanel = a ?? AaPanelRepository.Get().First();
|
||||
|
||||
var access = new aaPanel(a!.Url, a.Key);
|
||||
|
||||
if (access.CreateDatabase(name, name, password))
|
||||
{
|
||||
var aaDb = access.Databases.First(x => x.Name == name);
|
||||
|
||||
return Task.FromResult(DatabaseRepository.Add(new()
|
||||
{
|
||||
Name = name,
|
||||
AaPanel = aaPanel,
|
||||
Owner = u,
|
||||
InternalAaPanelId = aaDb.Id
|
||||
}));
|
||||
}
|
||||
else
|
||||
throw new DisplayException("An unknown error occured while creating the database");
|
||||
}
|
||||
|
||||
public Task ChangePassword(Database.Entities.Database database, string newPassword)
|
||||
{
|
||||
var access = CreateApiAccess(database);
|
||||
|
||||
access.Databases.First(x => x.Id == database.InternalAaPanelId).ChangePassword(newPassword);
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public Task<string> GetPassword(Database.Entities.Database database)
|
||||
{
|
||||
var access = CreateApiAccess(database);
|
||||
|
||||
return Task.FromResult(
|
||||
access.Databases
|
||||
.First(x => x.Id == database.InternalAaPanelId).Password
|
||||
);
|
||||
}
|
||||
|
||||
private aaPanel CreateApiAccess(Database.Entities.Database database)
|
||||
{
|
||||
if (database.AaPanel == null)
|
||||
{
|
||||
database = DatabaseRepository
|
||||
.Get()
|
||||
.Include(x => x.AaPanel)
|
||||
.First(x => x.Id == database.Id);
|
||||
}
|
||||
|
||||
return new aaPanel(database.AaPanel.Url, database.AaPanel.Key);
|
||||
}
|
||||
}
|
|
@ -52,7 +52,7 @@ public class MailService
|
|||
try
|
||||
{
|
||||
using var client = new SmtpClient();
|
||||
|
||||
|
||||
client.Host = Server;
|
||||
client.Port = Port;
|
||||
client.EnableSsl = true;
|
||||
|
@ -67,8 +67,6 @@ public class MailService
|
|||
Subject = $"Hey {user.FirstName}, there are news from moonlight",
|
||||
To = { new MailAddress(user.Email) }
|
||||
});
|
||||
|
||||
Logger.Debug("Send!");
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
|
46
Moonlight/App/Services/TrashMailDetectorService.cs
Normal file
46
Moonlight/App/Services/TrashMailDetectorService.cs
Normal file
|
@ -0,0 +1,46 @@
|
|||
using System.Net;
|
||||
using Logging.Net;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class TrashMailDetectorService
|
||||
{
|
||||
private string[] Domains;
|
||||
|
||||
public TrashMailDetectorService()
|
||||
{
|
||||
Logger.Info("Fetching trash mail list from github repository");
|
||||
|
||||
using var wc = new WebClient();
|
||||
|
||||
var lines = wc
|
||||
.DownloadString("https://raw.githubusercontent.com/Endelon-Hosting/TrashMailDomainDetector/main/trashmail_domains.md")
|
||||
.Replace("\r\n", "\n")
|
||||
.Split(new [] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
||||
|
||||
Domains = GetDomains(lines).ToArray();
|
||||
}
|
||||
|
||||
private IEnumerable<string> GetDomains(string[] lines)
|
||||
{
|
||||
foreach (var line in lines)
|
||||
{
|
||||
if (!string.IsNullOrWhiteSpace(line))
|
||||
{
|
||||
if (line.Contains("."))
|
||||
{
|
||||
var domain = line.Remove(0, line.IndexOf(".", StringComparison.Ordinal) + 1).Trim();
|
||||
if (domain.Contains("."))
|
||||
{
|
||||
yield return domain;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public bool IsTrashEmail(string mail)
|
||||
{
|
||||
return Domains.Contains(mail.Split('@')[1]);
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
using JWT.Builder;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Helpers;
|
||||
using Moonlight.App.Models.Misc;
|
||||
using Moonlight.App.Repositories;
|
||||
using Moonlight.App.Services.LogServices;
|
||||
|
@ -67,13 +68,13 @@ public class UserService
|
|||
LastName = lastname,
|
||||
State = "",
|
||||
Status = UserStatus.Unverified,
|
||||
CreatedAt = DateTime.Now,
|
||||
CreatedAt = DateTime.UtcNow,
|
||||
DiscordDiscriminator = "",
|
||||
DiscordId = -1,
|
||||
DiscordUsername = "",
|
||||
TotpEnabled = false,
|
||||
TotpSecret = "",
|
||||
UpdatedAt = DateTime.Now,
|
||||
UpdatedAt = DateTime.UtcNow,
|
||||
TokenValidTime = DateTime.Now.AddDays(-5)
|
||||
});
|
||||
|
||||
|
@ -142,20 +143,27 @@ public class UserService
|
|||
}
|
||||
}
|
||||
|
||||
public async Task ChangePassword(User user, string password)
|
||||
public async Task ChangePassword(User user, string password, bool isSystemAction = false)
|
||||
{
|
||||
user.Password = BCrypt.Net.BCrypt.HashPassword(password);
|
||||
user.TokenValidTime = DateTime.Now;
|
||||
UserRepository.Update(user);
|
||||
|
||||
await MailService.SendMail(user!, "passwordChange", values =>
|
||||
if (isSystemAction)
|
||||
{
|
||||
values.Add("Ip", IdentityService.GetIp());
|
||||
values.Add("Device", IdentityService.GetDevice());
|
||||
values.Add("Location", "In your walls");
|
||||
});
|
||||
await AuditLogService.LogSystem(AuditLogType.ChangePassword, user.Email);
|
||||
}
|
||||
else
|
||||
{
|
||||
await MailService.SendMail(user!, "passwordChange", values =>
|
||||
{
|
||||
values.Add("Ip", IdentityService.GetIp());
|
||||
values.Add("Device", IdentityService.GetDevice());
|
||||
values.Add("Location", "In your walls");
|
||||
});
|
||||
|
||||
await AuditLogService.Log(AuditLogType.ChangePassword, user.Email);
|
||||
await AuditLogService.Log(AuditLogType.ChangePassword, user.Email);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<User> SftpLogin(int id, string password)
|
||||
|
@ -197,4 +205,29 @@ public class UserService
|
|||
|
||||
return token;
|
||||
}
|
||||
|
||||
public async Task ResetPassword(string email)
|
||||
{
|
||||
email = email.ToLower();
|
||||
|
||||
var user = UserRepository
|
||||
.Get()
|
||||
.FirstOrDefault(x => x.Email == email);
|
||||
|
||||
if (user == null)
|
||||
throw new DisplayException("A user with this email can not be found");
|
||||
|
||||
var newPassword = StringHelper.GenerateString(16);
|
||||
await ChangePassword(user, newPassword, true);
|
||||
|
||||
await AuditLogService.Log(AuditLogType.PasswordReset);
|
||||
|
||||
await MailService.SendMail(user, "passwordReset", values =>
|
||||
{
|
||||
values.Add("Ip", IdentityService.GetIp());
|
||||
values.Add("Device", IdentityService.GetDevice());
|
||||
values.Add("Location", "In your walls");
|
||||
values.Add("Password", newPassword);
|
||||
});
|
||||
}
|
||||
}
|
39
Moonlight/App/Services/WebsiteService.cs
Normal file
39
Moonlight/App/Services/WebsiteService.cs
Normal file
|
@ -0,0 +1,39 @@
|
|||
using aaPanelSharp;
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Moonlight.App.Database.Entities;
|
||||
using Moonlight.App.Exceptions;
|
||||
using Moonlight.App.Repositories;
|
||||
|
||||
namespace Moonlight.App.Services;
|
||||
|
||||
public class WebsiteService
|
||||
{
|
||||
private readonly WebsiteRepository WebsiteRepository;
|
||||
|
||||
public WebsiteService(WebsiteRepository websiteRepository)
|
||||
{
|
||||
WebsiteRepository = websiteRepository;
|
||||
}
|
||||
|
||||
public Website Create(AaPanel aaPanel, User user, string name)
|
||||
{
|
||||
if (WebsiteRepository.Get().Any(x => x.DomainName == name))
|
||||
throw new DisplayException("A website with this domain has already been created");
|
||||
|
||||
var access = new aaPanel(aaPanel.Url, aaPanel.Key);
|
||||
return null;
|
||||
}
|
||||
|
||||
private aaPanel CreateApiAccess(Website website)
|
||||
{
|
||||
if (website.AaPanel == null)
|
||||
{
|
||||
website = WebsiteRepository
|
||||
.Get()
|
||||
.Include(x => x.AaPanel)
|
||||
.First(x => x.Id == website.Id);
|
||||
}
|
||||
|
||||
return new aaPanel(website.AaPanel.Url, website.AaPanel.Key);
|
||||
}
|
||||
}
|
|
@ -56,6 +56,8 @@
|
|||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\ICON-LICENSE" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\README.md" />
|
||||
<_ContentIncludedByDefault Remove="wwwroot\css\site.css" />
|
||||
<_ContentIncludedByDefault Remove="Shared\Components\Tables\Column.razor" />
|
||||
<_ContentIncludedByDefault Remove="Shared\Components\Tables\Table.razor" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -62,6 +62,8 @@ namespace Moonlight
|
|||
builder.Services.AddScoped<SubscriptionLimitRepository>();
|
||||
builder.Services.AddScoped<RevokeRepository>();
|
||||
builder.Services.AddScoped<NotificationRepository>();
|
||||
builder.Services.AddScoped<AaPanelRepository>();
|
||||
builder.Services.AddScoped<WebsiteRepository>();
|
||||
|
||||
builder.Services.AddScoped<AuditLogEntryRepository>();
|
||||
builder.Services.AddScoped<ErrorLogEntryRepository>();
|
||||
|
@ -100,6 +102,8 @@ namespace Moonlight
|
|||
builder.Services.AddScoped<ErrorLogService>();
|
||||
builder.Services.AddScoped<LogService>();
|
||||
builder.Services.AddScoped<MailService>();
|
||||
builder.Services.AddSingleton<TrashMailDetectorService>();
|
||||
builder.Services.AddScoped<WebsiteService>();
|
||||
|
||||
// Support
|
||||
builder.Services.AddSingleton<SupportServerService>();
|
||||
|
|
|
@ -72,7 +72,7 @@
|
|||
<div class="d-flex flex-stack flex-wrap gap-3 fs-base fw-semibold mb-8">
|
||||
<div></div>
|
||||
|
||||
<a href="/reset-password" class="link-primary">
|
||||
<a href="/passwordreset" class="link-primary">
|
||||
<TL>Forgot password?</TL>
|
||||
</a>
|
||||
</div>
|
||||
|
|
73
Moonlight/Shared/Components/Auth/PasswordReset.razor
Normal file
73
Moonlight/Shared/Components/Auth/PasswordReset.razor
Normal file
|
@ -0,0 +1,73 @@
|
|||
@page "/passwordreset"
|
||||
@using Moonlight.App.Services
|
||||
|
||||
@* This is just a "virtual" route/page. The handling for that is
|
||||
@* MainLayout doing for us. We need to put that here so the router
|
||||
@* does not return the 404 page
|
||||
*@
|
||||
|
||||
@inject UserService UserService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<div class="d-flex flex-center">
|
||||
<div class="card rounded-3 w-md-550px">
|
||||
<div class="card-body">
|
||||
<div class="d-flex flex-center flex-column-fluid pb-15 pb-lg-20">
|
||||
@if (Send)
|
||||
{
|
||||
<div class="text-center mb-11">
|
||||
<h1 class="text-dark fw-bolder mb-3">
|
||||
<TL>Passwort reset successfull. Check your mail</TL>
|
||||
</h1>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="form w-100 fv-plugins-bootstrap5 fv-plugins-framework" novalidate="novalidate">
|
||||
<div class="text-center mb-11">
|
||||
<h1 class="text-dark fw-bolder mb-3">
|
||||
<TL>Password reset</TL>
|
||||
</h1>
|
||||
<div class="text-gray-500 fw-semibold fs-6">
|
||||
<TL>Reset the password of your account</TL>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="fv-row mb-8 fv-plugins-icon-container">
|
||||
<input @bind="Email" type="email" placeholder="@(SmartTranslateService.Translate("Email"))" name="email" class="form-control bg-transparent">
|
||||
</div>
|
||||
|
||||
<div class="d-grid mb-10">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Reset password"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Working"))"
|
||||
CssClasses="btn-primary"
|
||||
OnClick="Submit">
|
||||
</WButton>
|
||||
</div>
|
||||
|
||||
<div class="text-gray-500 text-center fw-semibold fs-6">
|
||||
<TL>Wrong here?</TL>
|
||||
|
||||
<a href="/login" class="link-primary">
|
||||
<TL>Sign in</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
private string Email = "";
|
||||
private bool Send = false;
|
||||
|
||||
private async Task Submit()
|
||||
{
|
||||
await UserService.ResetPassword(Email);
|
||||
Send = true;
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
|
@ -34,6 +34,11 @@
|
|||
<TL>Resources</TL>
|
||||
</a>
|
||||
</li>
|
||||
<li class="nav-item mt-2">
|
||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 6 ? "active" : "")" href="/admin/system/discordbot">
|
||||
<TL>Discord bot</TL>
|
||||
</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -62,6 +62,7 @@
|
|||
<PageErrorBoundary>
|
||||
<SoftErrorBoundary>
|
||||
@if (uri.LocalPath != "/login" &&
|
||||
uri.LocalPath != "/passwordreset" &&
|
||||
uri.LocalPath != "/register")
|
||||
{
|
||||
if (User == null)
|
||||
|
@ -94,6 +95,10 @@
|
|||
{
|
||||
<Register></Register>
|
||||
}
|
||||
else if (uri.LocalPath == "/passwordreset")
|
||||
{
|
||||
<PasswordReset></PasswordReset>
|
||||
}
|
||||
}
|
||||
</SoftErrorBoundary>
|
||||
</PageErrorBoundary>
|
||||
|
|
2
Moonlight/Shared/Views/Admin/AaPanels/Index.razor
Normal file
2
Moonlight/Shared/Views/Admin/AaPanels/Index.razor
Normal file
|
@ -0,0 +1,2 @@
|
|||
@page "/admin/aapanels"
|
||||
|
5
Moonlight/Shared/Views/Admin/Databases/Index.razor
Normal file
5
Moonlight/Shared/Views/Admin/Databases/Index.razor
Normal file
|
@ -0,0 +1,5 @@
|
|||
@page "/admin/databases"
|
||||
|
||||
<OnlyAdmin>
|
||||
|
||||
</OnlyAdmin>
|
|
@ -10,38 +10,54 @@
|
|||
|
||||
<OnlyAdmin>
|
||||
<LazyLoader Load="Load">
|
||||
<div class="row mb-5">
|
||||
<a class="btn btn-success" href="/admin/domains/new">Add new domain</a>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
<Table TableItem="Domain" Items="Domains" PageSize="25" TableHeadClass="border-bottom border-gray-200 fs-6 text-gray-600 fw-bold bg-light bg-opacity-75">
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Shared domain"))" Field="@(x => x.SharedDomain)" Sortable="true" Filterable="true" Width="10%">
|
||||
<Template>
|
||||
<span>@(context.SharedDomain.Name)</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true" Width="10%">
|
||||
<Template>
|
||||
<a class="invisible-a" href="/admin/users/view/@(context.Owner.Id)">@(context.Owner.Email)</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<a class="invisible-a" href="/domain/@(context.Id)">Manage</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Domain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
||||
CssClasses="btn-danger"
|
||||
OnClick="() => Delete(context)">
|
||||
</WButton>
|
||||
</Template>
|
||||
</Column>
|
||||
</Table>
|
||||
<div class="row">
|
||||
<div class="card">
|
||||
<div class="card-header border-0 pt-5">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<TL>Domains</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/domains/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-layer-plus"></i>
|
||||
<TL>New domain</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body pt-0">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="Domain" Items="Domains" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Shared domain"))" Field="@(x => x.SharedDomain)" Sortable="true" Filterable="true" Width="10%">
|
||||
<Template>
|
||||
<span>@(context.SharedDomain.Name)</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true" Width="10%">
|
||||
<Template>
|
||||
<a href="/admin/users/view/@(context.Owner.Id)">@(context.Owner.Email)</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<a href="/domain/@(context.Id)">Manage</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Domain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
||||
CssClasses="btn-sm btn-danger"
|
||||
OnClick="() => Delete(context)">
|
||||
</WButton>
|
||||
</Template>
|
||||
</Column>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
|
|
|
@ -9,45 +9,53 @@
|
|||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<OnlyAdmin>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body">
|
||||
<a href="/admin/servers/new" class="btn btn-success">
|
||||
<TL>Create new server</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<LazyLoader Load="Load">
|
||||
<div class="card card-body">
|
||||
@if (Servers.Any())
|
||||
{
|
||||
<Table TableItem="Server" Items="Servers" PageSize="25" TableHeadClass="border-bottom border-gray-200 fs-6 text-gray-600 fw-bold bg-light bg-opacity-75">
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="20%"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Cores"))" Field="@(x => x.Cpu)" Sortable="true" Filterable="true" Width="20%"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Memory"))" Field="@(x => x.Memory)" Sortable="true" Filterable="true" Width="20%"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Disk"))" Field="@(x => x.Disk)" Sortable="true" Filterable="true" Width="20%"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true" Width="20%">
|
||||
<Template>
|
||||
<a href="/admin/users/@(context.Owner.Id)/">@context.Owner.Email</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Server" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="20%">
|
||||
<Template>
|
||||
<a href="/admin/servers/edit/@(context.Id)">
|
||||
@(SmartTranslateService.Translate("Manage"))
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<TL>No servers found</TL>
|
||||
<div class="card">
|
||||
<div class="card-header border-0 pt-5">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold fs-3 mb-1"><TL>Servers</TL></span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/servers/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-layer-plus"></i>
|
||||
<TL>New server</TL>
|
||||
</a>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="card-body pt-0">
|
||||
@if (Servers.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="Server" Items="Servers" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Cores"))" Field="@(x => x.Cpu)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Memory"))" Field="@(x => x.Memory)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Disk"))" Field="@(x => x.Disk)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<a href="/admin/users/view/@(context.Owner.Id)/">@context.Owner.Email</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Server" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/servers/edit/@(context.Id)">
|
||||
@(SmartTranslateService.Translate("Manage"))
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<TL>No servers found</TL>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
|
@ -63,7 +71,7 @@
|
|||
.Get()
|
||||
.Include(x => x.Owner)
|
||||
.ToArray();
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
|
@ -125,37 +125,45 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
@if (Image != null)
|
||||
{
|
||||
<div class="mt-9 row d-flex">
|
||||
@foreach (var vars in ServerVariables.Chunk(4))
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body">
|
||||
@if (Image != null)
|
||||
{
|
||||
<div class="row mb-3">
|
||||
@foreach (var variable in vars)
|
||||
<div class="mt-9 row d-flex">
|
||||
@foreach (var vars in ServerVariables.Chunk(4))
|
||||
{
|
||||
<div class="col">
|
||||
<div class="card card-body">
|
||||
<label class="form-label"><TL>Name</TL></label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Key" type="text" class="form-control disabled" disabled="">
|
||||
<div class="row mb-3">
|
||||
@foreach (var variable in vars)
|
||||
{
|
||||
<div class="col">
|
||||
<div class="card card-body">
|
||||
<label class="form-label">
|
||||
<TL>Name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Key" type="text" class="form-control disabled" disabled="">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Value</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Value" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<label class="form-label"><TL>Value</TL></label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Value" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="card card-body">
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-primary" href="/admin/servers">
|
||||
<TL>Back</TL>
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="/admin/servers" class="btn btn-danger me-3">
|
||||
<TL>Cancel</TL>
|
||||
</a>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Create"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Creating"))"
|
||||
|
@ -201,7 +209,7 @@
|
|||
RebuildVariables();
|
||||
|
||||
|
||||
InvokeAsync(StateHasChanged);
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -253,7 +261,7 @@
|
|||
User = Users.FirstOrDefault();
|
||||
|
||||
Image = Images.FirstOrDefault();
|
||||
|
||||
|
||||
RebuildVariables();
|
||||
|
||||
if (Image != null)
|
||||
|
|
25
Moonlight/Shared/Views/Admin/Sys/DiscordBot.razor
Normal file
25
Moonlight/Shared/Views/Admin/Sys/DiscordBot.razor
Normal file
|
@ -0,0 +1,25 @@
|
|||
@page "/admin/system/discordbot"
|
||||
@using Moonlight.App.Services.DiscordBot
|
||||
@using Moonlight.Shared.Components.Navigations
|
||||
|
||||
@inject DiscordBotService DiscordBotService
|
||||
|
||||
<OnlyAdmin>
|
||||
<AdminSystemNavigation Index="6"/>
|
||||
|
||||
<div class="mt-3 card card-body">
|
||||
<WButton Text="Register commands"
|
||||
WorkingText="Working"
|
||||
CssClasses="btn-primary"
|
||||
OnClick="RegisterCommands">
|
||||
</WButton>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
|
||||
@code
|
||||
{
|
||||
private async Task RegisterCommands()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
|
@ -14,7 +14,7 @@
|
|||
<div class="card">
|
||||
<div class="card-body">
|
||||
<LazyLoader Load="Load">
|
||||
<Table TableItem="LogEntry" Items="LogEntries" PageSize="25" TableHeadClass="border-bottom border-gray-200 fs-6 text-gray-600 fw-bold bg-light bg-opacity-75">
|
||||
<Table TableItem="LogEntry" Items="LogEntries" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="LogEntry" Title="@(SmartTranslateService.Translate("Time"))" Field="@(x => x.CreatedAt)" Sortable="true" Filterable="false"></Column>
|
||||
<Column TableItem="LogEntry" Title="@(SmartTranslateService.Translate("Log level"))" Field="@(x => x.Level)" Sortable="true" Filterable="false"></Column>
|
||||
<Column TableItem="LogEntry" Title="@(SmartTranslateService.Translate("Log message"))" Field="@(x => x.Message)" Sortable="false" Filterable="true"></Column>
|
||||
|
|
|
@ -1,7 +1,66 @@
|
|||
@page "/admin/users"
|
||||
|
||||
@using Moonlight.Shared.Components.Navigations
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using BlazorTable
|
||||
@using Moonlight.App.Services
|
||||
|
||||
@inject UserRepository UserRepository
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<OnlyAdmin>
|
||||
<AdminSessionNavigation Index="0" />
|
||||
</OnlyAdmin>
|
||||
<AdminSessionNavigation Index="0"/>
|
||||
|
||||
<div class="card">
|
||||
<LazyLoader Load="Load">
|
||||
<div class="card-header border-0 pt-5">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<TL>Users</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/users/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-user-plus"></i>
|
||||
<TL>New user</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="User" Items="Users" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Email"))" Field="@(x => x.Email)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<a href="/admin/users/view/@(context.Id)">@(context.Email)</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("First name"))" Field="@(x => x.FirstName)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Last name"))" Field="@(x => x.LastName)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Created at"))" Field="@(x => x.CreatedAt)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/users/@(context.Id)/edit">
|
||||
<TL>Manage</TL>
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
|
||||
@code
|
||||
{
|
||||
private User[] Users;
|
||||
|
||||
private async Task Load(LazyLoader lazyLoader)
|
||||
{
|
||||
Users = UserRepository.Get().ToArray();
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
66
Moonlight/Shared/Views/Admin/Users/New.razor
Normal file
66
Moonlight/Shared/Views/Admin/Users/New.razor
Normal file
|
@ -0,0 +1,66 @@
|
|||
@page "/admin/users/new"
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Services
|
||||
@using Moonlight.App.Services.Interop
|
||||
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
@inject NavigationManager NavigationManager
|
||||
@inject ToastService ToastService
|
||||
@inject UserService UserService
|
||||
|
||||
<OnlyAdmin>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>First name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="User.FirstName" type="text" class="form-control">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Last name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="User.LastName" type="text" class="form-control">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Email</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="User.Email" type="email" class="form-control">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Password</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="User.Password" type="password" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="card card-body">
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="/admin/users" class="btn btn-danger me-3">
|
||||
<TL>Cancel</TL>
|
||||
</a>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Create"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Creating"))"
|
||||
CssClasses="btn-success"
|
||||
OnClick="Create">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
|
||||
@code
|
||||
{
|
||||
private User User = new();
|
||||
|
||||
private async Task Create()
|
||||
{
|
||||
await UserService.Register(User.Email, User.Password, User.FirstName, User.LastName);
|
||||
await ToastService.Success(SmartTranslateService.Translate("User successfully created"));
|
||||
NavigationManager.NavigateTo("/admin/users");
|
||||
}
|
||||
}
|
|
@ -16,58 +16,77 @@
|
|||
<OnlyAdmin>
|
||||
<AdminSessionNavigation Index="1"/>
|
||||
|
||||
<div class="card card-body">
|
||||
<div class="card">
|
||||
<LazyLoader Load="Load">
|
||||
<button class="btn btn-primary mb-3" @onclick="Refresh">
|
||||
<TL>Refresh</TL>
|
||||
</button>
|
||||
<button class="btn btn-warning mb-3" @onclick="MessageAll">
|
||||
<TL>Send a message to all users</TL>
|
||||
</button>
|
||||
|
||||
@if (AllSessions == null)
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<span class="spinner-border spinner-border-sm align-middle me-2"></span>
|
||||
<TL>Loading sessions</TL>
|
||||
<div class="card-header border-0 pt-5">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<TL>Sessions</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<button class="btn btn-sm btn-primary me-3" @onclick="Refresh">
|
||||
<i class="bx bx-revision"></i>
|
||||
<TL>Refresh</TL>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-warning" @onclick="MessageAll">
|
||||
<i class="bx bx-message-square-dots"></i>
|
||||
<TL>Send a message to all users</TL>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Table TableItem="Session" Items="AllSessions" PageSize="25" TableHeadClass="border-bottom border-gray-200 fs-6 text-gray-600 fw-bold bg-light bg-opacity-75">
|
||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Email"))" Field="@(x => x.User.Id)" Sortable="true" Filterable="true" Width="20%">
|
||||
<Template>
|
||||
<span>@(context.User == null ? "" : context.User.Email)</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("IP"))" Field="@(x => x.Ip)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("URL"))" Field="@(x => x.Url)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Device"))" Field="@(x => x.Device)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Time"))" Field="@(x => x.CreatedAt)" Sortable="true" Filterable="true" Width="10%">
|
||||
<Template>
|
||||
@{
|
||||
var time = Formatter.FormatUptime((DateTime.Now - context.CreatedAt).TotalMilliseconds);
|
||||
}
|
||||
<span>@(time)</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Actions"))" Field="@(x => x.Ip)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<button @onclick="() => Navigate(context)" class="btn btn-primary">
|
||||
<TL>Change url</TL>
|
||||
</button>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Session" Title="" Field="@(x => x.Ip)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<button @onclick="() => Message(context)" class="btn btn-warning">
|
||||
<TL>Message</TL>
|
||||
</button>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
}
|
||||
</div>
|
||||
<div class="card-body pt-0">
|
||||
@if (AllSessions == null)
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<span class="spinner-border spinner-border-sm align-middle me-2"></span>
|
||||
<TL>Loading sessions</TL>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Table TableItem="Session" Items="AllSessions" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Email"))" Field="@(x => x.User.Id)" Sortable="true" Filterable="true" Width="20%">
|
||||
<Template>
|
||||
@if (context.User == null)
|
||||
{
|
||||
<TL>Guest</TL>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="/admin/users/view/@(context.User.Id)">@(context.User.Email)</a>
|
||||
}
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("IP"))" Field="@(x => x.Ip)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("URL"))" Field="@(x => x.Url)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Device"))" Field="@(x => x.Device)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Time"))" Field="@(x => x.CreatedAt)" Sortable="true" Filterable="true" Width="10%">
|
||||
<Template>
|
||||
@{
|
||||
var time = Formatter.FormatUptime((DateTime.Now - context.CreatedAt).TotalMilliseconds);
|
||||
}
|
||||
<span>@(time)</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Actions"))" Field="@(x => x.Ip)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<button @onclick="() => Navigate(context)" class="btn btn-sm btn-primary">
|
||||
<TL>Change url</TL>
|
||||
</button>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Session" Title="" Field="@(x => x.Ip)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<button @onclick="() => Message(context)" class="btn btn-sm btn-warning">
|
||||
<TL>Message</TL>
|
||||
</button>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
}
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
|
@ -79,7 +98,7 @@
|
|||
private Task Load(LazyLoader arg)
|
||||
{
|
||||
AllSessions = SessionService.GetAll();
|
||||
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
while (true)
|
||||
|
@ -96,7 +115,7 @@
|
|||
}
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
|
@ -109,8 +128,8 @@
|
|||
private async Task Navigate(Session session)
|
||||
{
|
||||
var url = await AlertService.Text("URL", SmartTranslateService.Translate("Enter url"), "");
|
||||
|
||||
if(url == null)
|
||||
|
||||
if (url == null)
|
||||
return;
|
||||
|
||||
if (url == "")
|
||||
|
|
261
Moonlight/Shared/Views/Admin/Users/View.razor
Normal file
261
Moonlight/Shared/Views/Admin/Users/View.razor
Normal file
|
@ -0,0 +1,261 @@
|
|||
@page "/admin/users/view/{Id:int}"
|
||||
@using Moonlight.App.Database.Entities
|
||||
@using Moonlight.App.Helpers
|
||||
@using Moonlight.App.Repositories
|
||||
@using Moonlight.App.Repositories.Servers
|
||||
@using Microsoft.EntityFrameworkCore
|
||||
@using Moonlight.App.Repositories.Domains
|
||||
|
||||
@inject UserRepository UserRepository
|
||||
@inject ServerRepository ServerRepository
|
||||
@inject DomainRepository DomainRepository
|
||||
|
||||
<OnlyAdmin>
|
||||
<LazyLoader Load="Load">
|
||||
@if (User == null)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<TL>No user with this id found</TL>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card card-body mb-5">
|
||||
<div class="d-flex flex-column align-items-center text-center">
|
||||
<img src="/api/moonlight/avatar/@(User.Id)" class="rounded-circle" alt="Profile picture" width="150">
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card-body mb-5">
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-primary" href="/admin/users/edit/@(User.Id)"><TL>Edit</TL></a>
|
||||
<a class="btn btn-secondary" href="/admin/users"><TL>Back to list</TL></a>
|
||||
<a class="btn btn-primary" href="/admin/support/view/@(User.Id)"><TL>Open support</TL></a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card-xl-stretch mb-5">
|
||||
<div class="card-header border-0">
|
||||
<h3 class="card-title fw-bold text-dark">
|
||||
<TL>Servers</TL>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body pt-2">
|
||||
@foreach (var server in Servers)
|
||||
{
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<a href="/server/@(server.Uuid)" class="fs-6">@(server.Name) - @(server.Image.Name)</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
if (server != Servers.Last())
|
||||
{
|
||||
<div class="separator my-4"></div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card-xl-stretch">
|
||||
<div class="card-header border-0">
|
||||
<h3 class="card-title fw-bold text-dark">
|
||||
<TL>Domains</TL>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body pt-2">
|
||||
@foreach (var domain in Domains)
|
||||
{
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<a href="/domain/@(domain.Id)" class="fs-6">@(domain.Name).@(domain.SharedDomain.Name)</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
if (domain != Domains.Last())
|
||||
{
|
||||
<div class="separator my-4"></div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body fs-6">
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>First name</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.FirstName)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>Last name</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.LastName)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>Email</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.Email)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>Address</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.Address)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>City</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.City)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>State</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.State)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>Country</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.Country)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>Admin</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">
|
||||
@if (User.Admin)
|
||||
{
|
||||
<span>✅</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>❌</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>Status</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.Status)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>Totp</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.TotpEnabled)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>Discord</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.DiscordUsername)#@(User.DiscordDiscriminator)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>Subscription</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">
|
||||
@if (User.Subscription == null)
|
||||
{
|
||||
<span>
|
||||
<TL>None</TL>
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>@(User.Subscription.Name)</span>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>Created at</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(Formatter.FormatDate(User.CreatedAt))</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public int Id { get; set; }
|
||||
|
||||
private User? User;
|
||||
private Server[] Servers;
|
||||
private Domain[] Domains;
|
||||
|
||||
private Task Load(LazyLoader arg)
|
||||
{
|
||||
User = UserRepository.Get().FirstOrDefault(x => x.Id == Id);
|
||||
|
||||
if (User != null)
|
||||
{
|
||||
Servers = ServerRepository
|
||||
.Get()
|
||||
.Include(x => x.Owner)
|
||||
.Include(x => x.Image)
|
||||
.Where(x => x.Owner.Id == User.Id)
|
||||
.ToArray();
|
||||
|
||||
Domains = DomainRepository
|
||||
.Get()
|
||||
.Include(x => x.SharedDomain)
|
||||
.Include(x => x.Owner)
|
||||
.Where(x => x.Owner.Id == User.Id)
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
2
Moonlight/Shared/Views/Test.razor
Normal file
2
Moonlight/Shared/Views/Test.razor
Normal file
|
@ -0,0 +1,2 @@
|
|||
@page "/test"
|
||||
|
|
@ -306,3 +306,27 @@ WinSCP cannot be launched here;WinSCP cannot be launched here
|
|||
Create a new folder;Create a new folder
|
||||
Enter a name;Enter a name
|
||||
File upload complete;File upload complete
|
||||
New server;New server
|
||||
Sessions;Sessions
|
||||
New user;New user
|
||||
Created at;Created at
|
||||
Mail template not found;Mail template not found
|
||||
Missing admin permissions. This attempt has been logged ;)
|
||||
Address;Address
|
||||
City;City
|
||||
State;State
|
||||
Country;Country
|
||||
Totp;Totp
|
||||
Discord;Discord
|
||||
Subscription;Subscription
|
||||
None;None
|
||||
No user with this id found;No user with this id found
|
||||
Back to list;Back to list
|
||||
New domain;New domain
|
||||
Reset password;Reset password
|
||||
Password reset;Password reset
|
||||
Reset the password of your account;Reset the password of your account
|
||||
Wrong here?;Wrong here?
|
||||
A user with this email can not be found;A user with this email can not be found
|
||||
Passwort reset successfull. Check your mail;Passwort reset successfull. Check your mail
|
||||
Discord bot;Discord bot
|
||||
|
|
54
Moonlight/resources/mail/passwordReset.html
Normal file
54
Moonlight/resources/mail/passwordReset.html
Normal file
|
@ -0,0 +1,54 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Moonlight password reset</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background-color:#ffffff; padding: 45px 0 34px 0; border-radius: 24px; margin:40px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" height="auto"
|
||||
style="border-collapse:collapse">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="center" style="text-align:center; padding-bottom: 10px">
|
||||
<div style="text-align:center; margin:0 15px 34px 15px">
|
||||
<div style="margin-bottom: 10px">
|
||||
<a href="https://endelon-hosting.de" rel="noopener" target="_blank">
|
||||
<img alt="Logo" src="https://moonlight.endelon-hosting.de/assets/media/logo/MoonFullText.png" style="height: 35px">
|
||||
</a>
|
||||
</div>
|
||||
<div style="font-size: 14px; font-weight: 500; margin-bottom: 27px; font-family:Arial,Helvetica,sans-serif;">
|
||||
<p style="margin-bottom:9px; color:#181C32; font-size: 22px; font-weight:700">Hey {{FirstName}}, your password has been resetted</p>
|
||||
<p style="margin-bottom:2px; color:#7E8299">Your new password is: <b>{{Password}}</b></p>
|
||||
<p style="margin-bottom:2px; color:#7E8299">If this was not you please contact us. Also here is the data we collected.</p>
|
||||
<p style="margin-bottom:2px; color:#7E8299">IP: {{Ip}}</p>
|
||||
<p style="margin-bottom:2px; color:#7E8299">Device: {{Device}}</p>
|
||||
<p style="margin-bottom:2px; color:#7E8299">Location: {{Location}}</p>
|
||||
</div>
|
||||
<a href="https://moonlight.endelon-hosting.de" target="_blank"
|
||||
style="background-color:#50cd89; border-radius:6px;display:inline-block; padding:11px 19px; color: #FFFFFF; font-size: 14px; font-weight:500;">Open Moonlight
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="center"
|
||||
style="font-size: 13px; text-align:center; padding: 0 10px 10px 10px; font-weight: 500; color: #A1A5B7; font-family:Arial,Helvetica,sans-serif">
|
||||
<p style="color:#181C32; font-size: 16px; font-weight: 600; margin-bottom:9px">You need help?</p>
|
||||
<p style="margin-bottom:2px">We are happy to help!</p>
|
||||
<p style="margin-bottom:4px">More information at
|
||||
<a href="https://endelon.link/support" rel="noopener" target="_blank" style="font-weight: 600">endelon.link/support</a>.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="center"
|
||||
style="font-size: 13px; padding:0 15px; text-align:center; font-weight: 500; color: #A1A5B7;font-family:Arial,Helvetica,sans-serif">
|
||||
<p>Copyright 2022 Endelon Hosting </p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
50
Moonlight/resources/mail/register.html
Normal file
50
Moonlight/resources/mail/register.html
Normal file
|
@ -0,0 +1,50 @@
|
|||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>Welcome</title>
|
||||
</head>
|
||||
<body>
|
||||
<div style="background-color:#ffffff; padding: 45px 0 34px 0; border-radius: 24px; margin:40px auto; max-width: 600px;">
|
||||
<table align="center" border="0" cellpadding="0" cellspacing="0" width="100%" height="auto"
|
||||
style="border-collapse:collapse">
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="center" style="text-align:center; padding-bottom: 10px">
|
||||
<div style="text-align:center; margin:0 15px 34px 15px">
|
||||
<div style="margin-bottom: 10px">
|
||||
<a href="https://endelon-hosting.de" rel="noopener" target="_blank">
|
||||
<img alt="Logo" src="https://moonlight.endelon-hosting.de/assets/media/logo/MoonFullText.png" style="height: 35px">
|
||||
</a>
|
||||
</div>
|
||||
<div style="font-size: 14px; font-weight: 500; margin-bottom: 27px; font-family:Arial,Helvetica,sans-serif;">
|
||||
<p style="margin-bottom:9px; color:#181C32; font-size: 22px; font-weight:700">Hey {{FirstName}}, welcome to moonlight</p>
|
||||
<p style="margin-bottom:2px; color:#7E8299">We are happy to welcome you in ;)</p>
|
||||
</div>
|
||||
<a href="https://moonlight.endelon-hosting.de" target="_blank"
|
||||
style="background-color:#50cd89; border-radius:6px;display:inline-block; padding:11px 19px; color: #FFFFFF; font-size: 14px; font-weight:500;">Open Moonlight
|
||||
</a>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="center"
|
||||
style="font-size: 13px; text-align:center; padding: 0 10px 10px 10px; font-weight: 500; color: #A1A5B7; font-family:Arial,Helvetica,sans-serif">
|
||||
<p style="color:#181C32; font-size: 16px; font-weight: 600; margin-bottom:9px">You need help?</p>
|
||||
<p style="margin-bottom:2px">We are happy to help!</p>
|
||||
<p style="margin-bottom:4px">More information at
|
||||
<a href="https://endelon.link/support" rel="noopener" target="_blank" style="font-weight: 600">endelon.link/support</a>.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="center"
|
||||
style="font-size: 13px; padding:0 15px; text-align:center; font-weight: 500; color: #A1A5B7;font-family:Arial,Helvetica,sans-serif">
|
||||
<p>Copyright 2022 Endelon Hosting </p>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in a new issue