Merge pull request #1 from Moonlight-Panel/ServiceManager
Service manager
This commit is contained in:
commit
faa4fc6e18
|
@ -39,6 +39,8 @@ public class DataContext : DbContext
|
||||||
public DbSet<Revoke> Revokes { get; set; }
|
public DbSet<Revoke> Revokes { get; set; }
|
||||||
public DbSet<NotificationClient> NotificationClients { get; set; }
|
public DbSet<NotificationClient> NotificationClients { get; set; }
|
||||||
public DbSet<NotificationAction> NotificationActions { 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)
|
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 class Database
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public int AaPanelId { get; set; }
|
public int InternalAaPanelId { get; set; }
|
||||||
public User Owner { 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,
|
DisableTotp,
|
||||||
AddDomainRecord,
|
AddDomainRecord,
|
||||||
UpdateDomainRecord,
|
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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -67,8 +67,6 @@ public class MailService
|
||||||
Subject = $"Hey {user.FirstName}, there are news from moonlight",
|
Subject = $"Hey {user.FirstName}, there are news from moonlight",
|
||||||
To = { new MailAddress(user.Email) }
|
To = { new MailAddress(user.Email) }
|
||||||
});
|
});
|
||||||
|
|
||||||
Logger.Debug("Send!");
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
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 JWT.Builder;
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Exceptions;
|
using Moonlight.App.Exceptions;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services.LogServices;
|
using Moonlight.App.Services.LogServices;
|
||||||
|
@ -67,13 +68,13 @@ public class UserService
|
||||||
LastName = lastname,
|
LastName = lastname,
|
||||||
State = "",
|
State = "",
|
||||||
Status = UserStatus.Unverified,
|
Status = UserStatus.Unverified,
|
||||||
CreatedAt = DateTime.Now,
|
CreatedAt = DateTime.UtcNow,
|
||||||
DiscordDiscriminator = "",
|
DiscordDiscriminator = "",
|
||||||
DiscordId = -1,
|
DiscordId = -1,
|
||||||
DiscordUsername = "",
|
DiscordUsername = "",
|
||||||
TotpEnabled = false,
|
TotpEnabled = false,
|
||||||
TotpSecret = "",
|
TotpSecret = "",
|
||||||
UpdatedAt = DateTime.Now,
|
UpdatedAt = DateTime.UtcNow,
|
||||||
TokenValidTime = DateTime.Now.AddDays(-5)
|
TokenValidTime = DateTime.Now.AddDays(-5)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -142,12 +143,18 @@ 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.Password = BCrypt.Net.BCrypt.HashPassword(password);
|
||||||
user.TokenValidTime = DateTime.Now;
|
user.TokenValidTime = DateTime.Now;
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
|
|
||||||
|
if (isSystemAction)
|
||||||
|
{
|
||||||
|
await AuditLogService.LogSystem(AuditLogType.ChangePassword, user.Email);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
await MailService.SendMail(user!, "passwordChange", values =>
|
await MailService.SendMail(user!, "passwordChange", values =>
|
||||||
{
|
{
|
||||||
values.Add("Ip", IdentityService.GetIp());
|
values.Add("Ip", IdentityService.GetIp());
|
||||||
|
@ -157,6 +164,7 @@ public class UserService
|
||||||
|
|
||||||
await AuditLogService.Log(AuditLogType.ChangePassword, user.Email);
|
await AuditLogService.Log(AuditLogType.ChangePassword, user.Email);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public async Task<User> SftpLogin(int id, string password)
|
public async Task<User> SftpLogin(int id, string password)
|
||||||
{
|
{
|
||||||
|
@ -197,4 +205,29 @@ public class UserService
|
||||||
|
|
||||||
return token;
|
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\ICON-LICENSE" />
|
||||||
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\README.md" />
|
<_ContentIncludedByDefault Remove="wwwroot\css\open-iconic\README.md" />
|
||||||
<_ContentIncludedByDefault Remove="wwwroot\css\site.css" />
|
<_ContentIncludedByDefault Remove="wwwroot\css\site.css" />
|
||||||
|
<_ContentIncludedByDefault Remove="Shared\Components\Tables\Column.razor" />
|
||||||
|
<_ContentIncludedByDefault Remove="Shared\Components\Tables\Table.razor" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -62,6 +62,8 @@ namespace Moonlight
|
||||||
builder.Services.AddScoped<SubscriptionLimitRepository>();
|
builder.Services.AddScoped<SubscriptionLimitRepository>();
|
||||||
builder.Services.AddScoped<RevokeRepository>();
|
builder.Services.AddScoped<RevokeRepository>();
|
||||||
builder.Services.AddScoped<NotificationRepository>();
|
builder.Services.AddScoped<NotificationRepository>();
|
||||||
|
builder.Services.AddScoped<AaPanelRepository>();
|
||||||
|
builder.Services.AddScoped<WebsiteRepository>();
|
||||||
|
|
||||||
builder.Services.AddScoped<AuditLogEntryRepository>();
|
builder.Services.AddScoped<AuditLogEntryRepository>();
|
||||||
builder.Services.AddScoped<ErrorLogEntryRepository>();
|
builder.Services.AddScoped<ErrorLogEntryRepository>();
|
||||||
|
@ -100,6 +102,8 @@ namespace Moonlight
|
||||||
builder.Services.AddScoped<ErrorLogService>();
|
builder.Services.AddScoped<ErrorLogService>();
|
||||||
builder.Services.AddScoped<LogService>();
|
builder.Services.AddScoped<LogService>();
|
||||||
builder.Services.AddScoped<MailService>();
|
builder.Services.AddScoped<MailService>();
|
||||||
|
builder.Services.AddSingleton<TrashMailDetectorService>();
|
||||||
|
builder.Services.AddScoped<WebsiteService>();
|
||||||
|
|
||||||
// Support
|
// Support
|
||||||
builder.Services.AddSingleton<SupportServerService>();
|
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 class="d-flex flex-stack flex-wrap gap-3 fs-base fw-semibold mb-8">
|
||||||
<div></div>
|
<div></div>
|
||||||
|
|
||||||
<a href="/reset-password" class="link-primary">
|
<a href="/passwordreset" class="link-primary">
|
||||||
<TL>Forgot password?</TL>
|
<TL>Forgot password?</TL>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</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>
|
<TL>Resources</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</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>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -62,6 +62,7 @@
|
||||||
<PageErrorBoundary>
|
<PageErrorBoundary>
|
||||||
<SoftErrorBoundary>
|
<SoftErrorBoundary>
|
||||||
@if (uri.LocalPath != "/login" &&
|
@if (uri.LocalPath != "/login" &&
|
||||||
|
uri.LocalPath != "/passwordreset" &&
|
||||||
uri.LocalPath != "/register")
|
uri.LocalPath != "/register")
|
||||||
{
|
{
|
||||||
if (User == null)
|
if (User == null)
|
||||||
|
@ -94,6 +95,10 @@
|
||||||
{
|
{
|
||||||
<Register></Register>
|
<Register></Register>
|
||||||
}
|
}
|
||||||
|
else if (uri.LocalPath == "/passwordreset")
|
||||||
|
{
|
||||||
|
<PasswordReset></PasswordReset>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</SoftErrorBoundary>
|
</SoftErrorBoundary>
|
||||||
</PageErrorBoundary>
|
</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,11 +10,24 @@
|
||||||
|
|
||||||
<OnlyAdmin>
|
<OnlyAdmin>
|
||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
<div class="row mb-5">
|
<div class="row">
|
||||||
<a class="btn btn-success" href="/admin/domains/new">Add new domain</a>
|
<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="row mb-5">
|
</div>
|
||||||
<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">
|
<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("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("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%">
|
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Shared domain"))" Field="@(x => x.SharedDomain)" Sortable="true" Filterable="true" Width="10%">
|
||||||
|
@ -24,25 +37,28 @@
|
||||||
</Column>
|
</Column>
|
||||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true" Width="10%">
|
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true" Width="10%">
|
||||||
<Template>
|
<Template>
|
||||||
<a class="invisible-a" href="/admin/users/view/@(context.Owner.Id)">@(context.Owner.Email)</a>
|
<a href="/admin/users/view/@(context.Owner.Id)">@(context.Owner.Email)</a>
|
||||||
</Template>
|
</Template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
||||||
<Template>
|
<Template>
|
||||||
<a class="invisible-a" href="/domain/@(context.Id)">Manage</a>
|
<a href="/domain/@(context.Id)">Manage</a>
|
||||||
</Template>
|
</Template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column TableItem="Domain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
<Column TableItem="Domain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
||||||
<Template>
|
<Template>
|
||||||
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
||||||
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
||||||
CssClasses="btn-danger"
|
CssClasses="btn-sm btn-danger"
|
||||||
OnClick="() => Delete(context)">
|
OnClick="() => Delete(context)">
|
||||||
</WButton>
|
</WButton>
|
||||||
</Template>
|
</Template>
|
||||||
</Column>
|
</Column>
|
||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
</OnlyAdmin>
|
</OnlyAdmin>
|
||||||
|
|
||||||
|
|
|
@ -9,30 +9,36 @@
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
<OnlyAdmin>
|
<OnlyAdmin>
|
||||||
<div class="row mb-5">
|
<div class="row">
|
||||||
<div class="card card-body">
|
<LazyLoader Load="Load">
|
||||||
<a href="/admin/servers/new" class="btn btn-success">
|
<div class="card">
|
||||||
<TL>Create new server</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>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>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="row">
|
<div class="card-body pt-0">
|
||||||
<LazyLoader Load="Load">
|
|
||||||
<div class="card card-body">
|
|
||||||
@if (Servers.Any())
|
@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">
|
<div class="table-responsive">
|
||||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
|
<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("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="20%"/>
|
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Cores"))" Field="@(x => x.Cpu)" Sortable="true" Filterable="true" Width="20%"/>
|
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true"/>
|
||||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Memory"))" Field="@(x => x.Memory)" Sortable="true" Filterable="true" Width="20%"/>
|
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Cores"))" Field="@(x => x.Cpu)" Sortable="true" Filterable="true"/>
|
||||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Disk"))" Field="@(x => x.Disk)" Sortable="true" Filterable="true" Width="20%"/>
|
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Memory"))" Field="@(x => x.Memory)" Sortable="true" Filterable="true"/>
|
||||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true" Width="20%">
|
<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>
|
<Template>
|
||||||
<a href="/admin/users/@(context.Owner.Id)/">@context.Owner.Email</a>
|
<a href="/admin/users/view/@(context.Owner.Id)/">@context.Owner.Email</a>
|
||||||
</Template>
|
</Template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column TableItem="Server" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="20%">
|
<Column TableItem="Server" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||||
<Template>
|
<Template>
|
||||||
<a href="/admin/servers/edit/@(context.Id)">
|
<a href="/admin/servers/edit/@(context.Id)">
|
||||||
@(SmartTranslateService.Translate("Manage"))
|
@(SmartTranslateService.Translate("Manage"))
|
||||||
|
@ -41,6 +47,7 @@
|
||||||
</Column>
|
</Column>
|
||||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||||
</Table>
|
</Table>
|
||||||
|
</div>
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -49,6 +56,7 @@
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
</div>
|
</div>
|
||||||
</OnlyAdmin>
|
</OnlyAdmin>
|
||||||
|
|
|
@ -125,6 +125,8 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="row mb-5">
|
||||||
|
<div class="card card-body">
|
||||||
@if (Image != null)
|
@if (Image != null)
|
||||||
{
|
{
|
||||||
<div class="mt-9 row d-flex">
|
<div class="mt-9 row d-flex">
|
||||||
|
@ -135,11 +137,15 @@
|
||||||
{
|
{
|
||||||
<div class="col">
|
<div class="col">
|
||||||
<div class="card card-body">
|
<div class="card card-body">
|
||||||
<label class="form-label"><TL>Name</TL></label>
|
<label class="form-label">
|
||||||
|
<TL>Name</TL>
|
||||||
|
</label>
|
||||||
<div class="input-group mb-5">
|
<div class="input-group mb-5">
|
||||||
<input @bind="variable.Key" type="text" class="form-control disabled" disabled="">
|
<input @bind="variable.Key" type="text" class="form-control disabled" disabled="">
|
||||||
</div>
|
</div>
|
||||||
<label class="form-label"><TL>Value</TL></label>
|
<label class="form-label">
|
||||||
|
<TL>Value</TL>
|
||||||
|
</label>
|
||||||
<div class="input-group mb-5">
|
<div class="input-group mb-5">
|
||||||
<input @bind="variable.Value" type="text" class="form-control">
|
<input @bind="variable.Value" type="text" class="form-control">
|
||||||
</div>
|
</div>
|
||||||
|
@ -150,12 +156,14 @@
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="card card-body">
|
<div class="card card-body">
|
||||||
<div class="btn-group">
|
<div class="d-flex justify-content-end">
|
||||||
<a class="btn btn-primary" href="/admin/servers">
|
<a href="/admin/servers" class="btn btn-danger me-3">
|
||||||
<TL>Back</TL>
|
<TL>Cancel</TL>
|
||||||
</a>
|
</a>
|
||||||
<WButton Text="@(SmartTranslateService.Translate("Create"))"
|
<WButton Text="@(SmartTranslateService.Translate("Create"))"
|
||||||
WorkingText="@(SmartTranslateService.Translate("Creating"))"
|
WorkingText="@(SmartTranslateService.Translate("Creating"))"
|
||||||
|
|
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">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<LazyLoader Load="Load">
|
<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("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 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>
|
<Column TableItem="LogEntry" Title="@(SmartTranslateService.Translate("Log message"))" Field="@(x => x.Message)" Sortable="false" Filterable="true"></Column>
|
||||||
|
|
|
@ -1,7 +1,66 @@
|
||||||
@page "/admin/users"
|
@page "/admin/users"
|
||||||
|
|
||||||
@using Moonlight.Shared.Components.Navigations
|
@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>
|
<OnlyAdmin>
|
||||||
<AdminSessionNavigation Index="0" />
|
<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>
|
</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,15 +16,26 @@
|
||||||
<OnlyAdmin>
|
<OnlyAdmin>
|
||||||
<AdminSessionNavigation Index="1"/>
|
<AdminSessionNavigation Index="1"/>
|
||||||
|
|
||||||
<div class="card card-body">
|
<div class="card">
|
||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
<button class="btn btn-primary mb-3" @onclick="Refresh">
|
<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>
|
<TL>Refresh</TL>
|
||||||
</button>
|
</button>
|
||||||
<button class="btn btn-warning mb-3" @onclick="MessageAll">
|
<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>
|
<TL>Send a message to all users</TL>
|
||||||
</button>
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="card-body pt-0">
|
||||||
@if (AllSessions == null)
|
@if (AllSessions == null)
|
||||||
{
|
{
|
||||||
<div class="alert alert-info">
|
<div class="alert alert-info">
|
||||||
|
@ -34,10 +45,17 @@
|
||||||
}
|
}
|
||||||
else
|
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">
|
<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%">
|
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Email"))" Field="@(x => x.User.Id)" Sortable="true" Filterable="true" Width="20%">
|
||||||
<Template>
|
<Template>
|
||||||
<span>@(context.User == null ? "" : context.User.Email)</span>
|
@if (context.User == null)
|
||||||
|
{
|
||||||
|
<TL>Guest</TL>
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
<a href="/admin/users/view/@(context.User.Id)">@(context.User.Email)</a>
|
||||||
|
}
|
||||||
</Template>
|
</Template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("IP"))" Field="@(x => x.Ip)" Sortable="true" Filterable="true" Width="10%"/>
|
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("IP"))" Field="@(x => x.Ip)" Sortable="true" Filterable="true" Width="10%"/>
|
||||||
|
@ -53,14 +71,14 @@
|
||||||
</Column>
|
</Column>
|
||||||
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Actions"))" Field="@(x => x.Ip)" Sortable="false" Filterable="false" Width="10%">
|
<Column TableItem="Session" Title="@(SmartTranslateService.Translate("Actions"))" Field="@(x => x.Ip)" Sortable="false" Filterable="false" Width="10%">
|
||||||
<Template>
|
<Template>
|
||||||
<button @onclick="() => Navigate(context)" class="btn btn-primary">
|
<button @onclick="() => Navigate(context)" class="btn btn-sm btn-primary">
|
||||||
<TL>Change url</TL>
|
<TL>Change url</TL>
|
||||||
</button>
|
</button>
|
||||||
</Template>
|
</Template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column TableItem="Session" Title="" Field="@(x => x.Ip)" Sortable="false" Filterable="false" Width="10%">
|
<Column TableItem="Session" Title="" Field="@(x => x.Ip)" Sortable="false" Filterable="false" Width="10%">
|
||||||
<Template>
|
<Template>
|
||||||
<button @onclick="() => Message(context)" class="btn btn-warning">
|
<button @onclick="() => Message(context)" class="btn btn-sm btn-warning">
|
||||||
<TL>Message</TL>
|
<TL>Message</TL>
|
||||||
</button>
|
</button>
|
||||||
</Template>
|
</Template>
|
||||||
|
@ -68,6 +86,7 @@
|
||||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||||
</Table>
|
</Table>
|
||||||
}
|
}
|
||||||
|
</div>
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
</div>
|
</div>
|
||||||
</OnlyAdmin>
|
</OnlyAdmin>
|
||||||
|
@ -110,7 +129,7 @@
|
||||||
{
|
{
|
||||||
var url = await AlertService.Text("URL", SmartTranslateService.Translate("Enter url"), "");
|
var url = await AlertService.Text("URL", SmartTranslateService.Translate("Enter url"), "");
|
||||||
|
|
||||||
if(url == null)
|
if (url == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (url == "")
|
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
|
Create a new folder;Create a new folder
|
||||||
Enter a name;Enter a name
|
Enter a name;Enter a name
|
||||||
File upload complete;File upload complete
|
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