Merge pull request #229 from Moonlight-Panel/AddPermissionSystem

Added permission system and improved UIs and the IdentityService
This commit is contained in:
Marcel Baumgartner
2023-07-18 02:02:31 +02:00
committed by GitHub
116 changed files with 6482 additions and 3198 deletions

View File

@@ -46,6 +46,7 @@ public class DataContext : DbContext
public DbSet<WebSpace> WebSpaces { get; set; }
public DbSet<SupportChatMessage> SupportChatMessages { get; set; }
public DbSet<IpBan> IpBans { get; set; }
public DbSet<PermissionGroup> PermissionGroups { get; set; }
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
{

View File

@@ -0,0 +1,8 @@
namespace Moonlight.App.Database.Entities;
public class PermissionGroup
{
public int Id { get; set; }
public string Name { get; set; } = "";
public byte[] Permissions { get; set; } = Array.Empty<byte>();
}

View File

@@ -1,4 +1,5 @@
using Moonlight.App.Models.Misc;
using System.ComponentModel.DataAnnotations;
using Moonlight.App.Models.Misc;
namespace Moonlight.App.Database.Entities;
@@ -39,6 +40,8 @@ public class User
public bool TotpEnabled { get; set; } = false;
public string TotpSecret { get; set; } = "";
public DateTime TokenValidTime { get; set; } = DateTime.UtcNow;
public byte[] Permissions { get; set; } = Array.Empty<byte>();
public PermissionGroup? PermissionGroup { get; set; }
// Discord
public ulong DiscordId { get; set; }

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,28 @@
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class AddPermissions : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<byte[]>(
name: "Permissions",
table: "Users",
type: "longblob",
nullable: false);
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropColumn(
name: "Permissions",
table: "Users");
}
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,68 @@
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace Moonlight.App.Database.Migrations
{
/// <inheritdoc />
public partial class AddPermissionGroup : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.AddColumn<int>(
name: "PermissionGroupId",
table: "Users",
type: "int",
nullable: true);
migrationBuilder.CreateTable(
name: "PermissionGroups",
columns: table => new
{
Id = table.Column<int>(type: "int", nullable: false)
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
Name = table.Column<string>(type: "longtext", nullable: false)
.Annotation("MySql:CharSet", "utf8mb4"),
Permissions = table.Column<byte[]>(type: "longblob", nullable: false)
},
constraints: table =>
{
table.PrimaryKey("PK_PermissionGroups", x => x.Id);
})
.Annotation("MySql:CharSet", "utf8mb4");
migrationBuilder.CreateIndex(
name: "IX_Users_PermissionGroupId",
table: "Users",
column: "PermissionGroupId");
migrationBuilder.AddForeignKey(
name: "FK_Users_PermissionGroups_PermissionGroupId",
table: "Users",
column: "PermissionGroupId",
principalTable: "PermissionGroups",
principalColumn: "Id");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropForeignKey(
name: "FK_Users_PermissionGroups_PermissionGroupId",
table: "Users");
migrationBuilder.DropTable(
name: "PermissionGroups");
migrationBuilder.DropIndex(
name: "IX_Users_PermissionGroupId",
table: "Users");
migrationBuilder.DropColumn(
name: "PermissionGroupId",
table: "Users");
}
}
}

View File

@@ -475,6 +475,25 @@ namespace Moonlight.App.Database.Migrations
b.ToTable("NotificationClients");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.PermissionGroup", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("int");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("longtext");
b.Property<byte[]>("Permissions")
.IsRequired()
.HasColumnType("longblob");
b.HasKey("Id");
b.ToTable("PermissionGroups");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b =>
{
b.Property<int>("Id")
@@ -798,6 +817,13 @@ namespace Moonlight.App.Database.Migrations
.IsRequired()
.HasColumnType("longtext");
b.Property<int?>("PermissionGroupId")
.HasColumnType("int");
b.Property<byte[]>("Permissions")
.IsRequired()
.HasColumnType("longblob");
b.Property<int>("Rating")
.HasColumnType("int");
@@ -845,6 +871,8 @@ namespace Moonlight.App.Database.Migrations
b.HasIndex("CurrentSubscriptionId");
b.HasIndex("PermissionGroupId");
b.ToTable("Users");
});
@@ -1049,7 +1077,13 @@ namespace Moonlight.App.Database.Migrations
.WithMany()
.HasForeignKey("CurrentSubscriptionId");
b.HasOne("Moonlight.App.Database.Entities.PermissionGroup", "PermissionGroup")
.WithMany()
.HasForeignKey("PermissionGroupId");
b.Navigation("CurrentSubscription");
b.Navigation("PermissionGroup");
});
modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b =>

View File

@@ -0,0 +1,88 @@
namespace Moonlight.App.Helpers;
public class BitHelper
{
public static bool ReadBit(byte[] byteArray, int bitIndex)
{
if (bitIndex < 0)
throw new ArgumentOutOfRangeException("bitIndex");
int byteIndex = bitIndex / 8;
if (byteIndex >= byteArray.Length)
throw new ArgumentOutOfRangeException("bitIndex");
int bitNumber = bitIndex % 8;
byte mask = (byte)(1 << bitNumber);
return (byteArray[byteIndex] & mask) != 0;
}
public static byte[] WriteBit(byte[] byteArray, int bitIndex, bool value)
{
if (bitIndex < 0)
throw new ArgumentOutOfRangeException("bitIndex");
int byteIndex = bitIndex / 8;
byte[] resultArray;
if (byteIndex >= byteArray.Length)
{
// Create a new array with increased size and copy elements from old array
resultArray = new byte[byteIndex + 1];
Array.Copy(byteArray, resultArray, byteArray.Length);
}
else
{
// Create a new array and copy elements from old array
resultArray = new byte[byteArray.Length];
Array.Copy(byteArray, resultArray, byteArray.Length);
}
int bitNumber = bitIndex % 8;
byte mask = (byte)(1 << bitNumber);
if (value)
resultArray[byteIndex] |= mask; // Set the bit to 1
else
resultArray[byteIndex] &= (byte)~mask; // Set the bit to 0
return resultArray;
}
public static byte[] OverwriteByteArrays(byte[] targetArray, byte[] overwriteArray)
{
int targetLength = targetArray.Length;
int overwriteLength = overwriteArray.Length;
int maxLength = Math.Max(targetLength, overwriteLength);
byte[] resultArray = new byte[maxLength];
for (int i = 0; i < maxLength; i++)
{
byte targetByte = i < targetLength ? targetArray[i] : (byte)0;
byte overwriteByte = i < overwriteLength ? overwriteArray[i] : (byte)0;
for (int j = 0; j < 8; j++)
{
bool overwriteBit = (overwriteByte & (1 << j)) != 0;
if (i < targetLength)
{
bool targetBit = (targetByte & (1 << j)) != 0;
if (overwriteBit)
{
targetByte = targetBit ? (byte)(targetByte | (1 << j)) : (byte)(targetByte & ~(1 << j));
}
}
else if (overwriteBit)
{
targetByte |= (byte)(1 << j);
}
}
resultArray[i] = targetByte;
}
return resultArray;
}
}

View File

@@ -24,7 +24,7 @@ public class BillingController : Controller
[HttpGet("cancel")]
public async Task<ActionResult> Cancel()
{
var user = await IdentityService.Get();
var user = IdentityService.User;
if (user == null)
return Redirect("/login");
@@ -35,7 +35,7 @@ public class BillingController : Controller
[HttpGet("success")]
public async Task<ActionResult> Success()
{
var user = await IdentityService.Get();
var user = IdentityService.User;
if (user == null)
return Redirect("/login");

View File

@@ -25,7 +25,7 @@ public class RegisterController : Controller
[HttpGet]
public async Task<ActionResult<TokenRegister>> Register()
{
var user = await IdentityService.Get();
var user = IdentityService.User;
if (user == null)
return NotFound();

View File

@@ -54,7 +54,7 @@ public class OAuth2Controller : Controller
{
try
{
var currentUser = await IdentityService.Get();
var currentUser = IdentityService.User;
if (currentUser != null)
{

View File

@@ -0,0 +1,34 @@
using System.ComponentModel.DataAnnotations;
using Moonlight.App.Database.Entities;
using Moonlight.App.Models.Misc;
namespace Moonlight.App.Models.Forms;
public class UserEditDataModel
{
[Required]
public string FirstName { get; set; } = "";
[Required]
public string LastName { get; set; } = "";
[Required]
public string Email { get; set; } = "";
[Required]
public string Address { get; set; } = "";
[Required]
public string City { get; set; } = "";
[Required]
public string State { get; set; } = "";
[Required]
public string Country { get; set; } = "";
public bool Admin { get; set; }
public bool TotpEnabled { get; set; }
public ulong DiscordId { get; set; }
public PermissionGroup? PermissionGroup { get; set; }
}

View File

@@ -0,0 +1,10 @@
namespace Moonlight.App.Perms;
public class Permission
{
public int Index { get; set; } = 0;
public string Name { get; set; } = "";
public string Description { get; set; } = "";
public static implicit operator int(Permission permission) => permission.Index;
}

View File

@@ -0,0 +1,11 @@
namespace Moonlight.App.Perms;
public class PermissionRequired : Attribute
{
public string Name { get; private set; }
public PermissionRequired(string name)
{
Name = name;
}
}

View File

@@ -0,0 +1,55 @@
using System.Data;
using Moonlight.App.Helpers;
namespace Moonlight.App.Perms;
public class PermissionStorage
{
public byte[] Data;
public bool IsReadyOnly;
public PermissionStorage(byte[] data, bool isReadyOnly = false)
{
Data = data;
IsReadyOnly = isReadyOnly;
}
public bool this[Permission permission]
{
get
{
try
{
return BitHelper.ReadBit(Data, permission.Index);
}
catch (ArgumentOutOfRangeException)
{
return false;
}
catch (Exception e)
{
Logger.Verbose("Error reading permissions. (Can be intentional)");
Logger.Verbose(e);
return false;
}
}
set
{
if (IsReadyOnly)
throw new ReadOnlyException();
Data = BitHelper.WriteBit(Data, permission.Index, value);
}
}
public bool HasAnyPermissions()
{
foreach (var permission in Permissions.GetAllPermissions())
{
if (this[permission])
return true;
}
return false;
}
}

View File

@@ -0,0 +1,424 @@
namespace Moonlight.App.Perms;
public static class Permissions
{
public static Permission AdminDashboard = new()
{
Index = 0,
Name = "Admin Dashboard",
Description = "Access the main admin dashboard page"
};
public static Permission AdminStatistics = new()
{
Index = 1,
Name = "Admin Statistics",
Description = "View statistical information about the moonlight instance"
};
public static Permission AdminDomains = new()
{
Index = 4,
Name = "Admin Domains",
Description = "Manage domains in the admin area"
};
public static Permission AdminNewDomain = new()
{
Index = 5,
Name = "Admin New Domain",
Description = "Create a new domain in the admin area"
};
public static Permission AdminSharedDomains = new()
{
Index = 6,
Name = "Admin Shared Domains",
Description = "Manage shared domains in the admin area"
};
public static Permission AdminNewSharedDomain = new()
{
Index = 7,
Name = "Admin New Shared Domain",
Description = "Create a new shared domain in the admin area"
};
public static Permission AdminNodeDdos = new()
{
Index = 8,
Name = "Admin Node DDoS",
Description = "Manage DDoS protection for nodes in the admin area"
};
public static Permission AdminNodeEdit = new()
{
Index = 9,
Name = "Admin Node Edit",
Description = "Edit node settings in the admin area"
};
public static Permission AdminNodes = new()
{
Index = 10,
Name = "Admin Node",
Description = "Access the node management page in the admin area"
};
public static Permission AdminNewNode = new()
{
Index = 11,
Name = "Admin New Node",
Description = "Create a new node in the admin area"
};
public static Permission AdminNodeSetup = new()
{
Index = 12,
Name = "Admin Node Setup",
Description = "Set up a node in the admin area"
};
public static Permission AdminNodeView = new()
{
Index = 13,
Name = "Admin Node View",
Description = "View node details in the admin area"
};
public static Permission AdminNotificationDebugging = new()
{
Index = 14,
Name = "Admin Notification Debugging",
Description = "Manage debugging notifications in the admin area"
};
public static Permission AdminServerCleanup = new()
{
Index = 15,
Name = "Admin Server Cleanup",
Description = "Perform server cleanup tasks in the admin area"
};
public static Permission AdminServerEdit = new()
{
Index = 16,
Name = "Admin Server Edit",
Description = "Edit server settings in the admin area"
};
public static Permission AdminServers = new()
{
Index = 17,
Name = "Admin Server",
Description = "Access the server management page in the admin area"
};
public static Permission AdminServerManager = new()
{
Index = 18,
Name = "Admin Server Manager",
Description = "Manage servers in the admin area"
};
public static Permission AdminNewServer = new()
{
Index = 19,
Name = "Admin New Server",
Description = "Create a new server in the admin area"
};
public static Permission AdminServerImageEdit = new()
{
Index = 20,
Name = "Admin Server Image Edit",
Description = "Edit server image settings in the admin area"
};
public static Permission AdminServerImages = new()
{
Index = 21,
Name = "Admin Server Images",
Description = "Access the server image management page in the admin area"
};
public static Permission AdminServerImageNew = new()
{
Index = 22,
Name = "Admin Server Image New",
Description = "Create a new server image in the admin area"
};
public static Permission AdminServerViewAllocations = new()
{
Index = 23,
Name = "Admin Server View Allocations",
Description = "View server allocations in the admin area"
};
public static Permission AdminServerViewArchive = new()
{
Index = 24,
Name = "Admin Server View Archive",
Description = "View server archive in the admin area"
};
public static Permission AdminServerViewDebug = new()
{
Index = 25,
Name = "Admin Server View Debug",
Description = "View server debugging information in the admin area"
};
public static Permission AdminServerViewImage = new()
{
Index = 26,
Name = "Admin Server View Image",
Description = "View server image details in the admin area"
};
public static Permission AdminServerViewIndex = new()
{
Index = 27,
Name = "Admin Server View",
Description = "Access the server view page in the admin area"
};
public static Permission AdminServerViewOverview = new()
{
Index = 28,
Name = "Admin Server View Overview",
Description = "View server overview in the admin area"
};
public static Permission AdminServerViewResources = new()
{
Index = 29,
Name = "Admin Server View Resources",
Description = "View server resources in the admin area"
};
public static Permission AdminSubscriptionEdit = new()
{
Index = 30,
Name = "Admin Subscription Edit",
Description = "Edit subscription settings in the admin area"
};
public static Permission AdminSubscriptions = new()
{
Index = 31,
Name = "Admin Subscriptions",
Description = "Access the subscription management page in the admin area"
};
public static Permission AdminNewSubscription = new()
{
Index = 32,
Name = "Admin New Subscription",
Description = "Create a new subscription in the admin area"
};
public static Permission AdminSupport = new()
{
Index = 33,
Name = "Admin Support",
Description = "Access the support page in the admin area"
};
public static Permission AdminSupportView = new()
{
Index = 34,
Name = "Admin Support View",
Description = "View support details in the admin area"
};
public static Permission AdminSysConfiguration = new()
{
Index = 35,
Name = "Admin system Configuration",
Description = "Access system configuration settings in the admin area"
};
public static Permission AdminSysDiscordBot = new()
{
Index = 36,
Name = "Admin system Discord Bot",
Description = "Manage Discord bot settings in the admin area"
};
public static Permission AdminSystem = new()
{
Index = 37,
Name = "Admin system",
Description = "Access the system management page in the admin area"
};
public static Permission AdminSysMail = new()
{
Index = 38,
Name = "Admin system Mail",
Description = "Manage mail settings in the admin area"
};
public static Permission AdminSecurityMalware = new()
{
Index = 39,
Name = "Admin security Malware",
Description = "Manage malware settings in the admin area"
};
public static Permission AdminSysResources = new()
{
Index = 40,
Name = "Admin system Resources",
Description = "View system resources in the admin area"
};
public static Permission AdminSecurity = new()
{
Index = 41,
Name = "Admin Security",
Description = "View security logs in the admin area"
};
public static Permission AdminSysSentry = new()
{
Index = 42,
Name = "Admin system Sentry",
Description = "Manage Sentry settings in the admin area"
};
public static Permission AdminSysNewsEdit = new()
{
Index = 43,
Name = "Admin system News Edit",
Description = "Edit system news in the admin area"
};
public static Permission AdminSysNews = new()
{
Index = 44,
Name = "Admin system News",
Description = "Access the system news management page in the admin area"
};
public static Permission AdminSysNewsNew = new()
{
Index = 45,
Name = "Admin system News New",
Description = "Create new system news in the admin area"
};
public static Permission AdminUserEdit = new()
{
Index = 46,
Name = "Admin User Edit",
Description = "Edit user settings in the admin area"
};
public static Permission AdminUsers = new()
{
Index = 47,
Name = "Admin Users",
Description = "Access the user management page in the admin area"
};
public static Permission AdminNewUser = new()
{
Index = 48,
Name = "Admin New User",
Description = "Create a new user in the admin area"
};
public static Permission AdminUserSessions = new()
{
Index = 49,
Name = "Admin User Sessions",
Description = "View user sessions in the admin area"
};
public static Permission AdminUserView = new()
{
Index = 50,
Name = "Admin User View",
Description = "View user details in the admin area"
};
public static Permission AdminWebspaces = new()
{
Index = 51,
Name = "Admin Webspaces",
Description = "Access the webspaces management page in the admin area"
};
public static Permission AdminNewWebspace = new()
{
Index = 52,
Name = "Admin New Webspace",
Description = "Create a new webspace in the admin area"
};
public static Permission AdminWebspacesServerEdit = new()
{
Index = 53,
Name = "Admin Webspaces Server Edit",
Description = "Edit webspace server settings in the admin area"
};
public static Permission AdminWebspacesServers = new()
{
Index = 54,
Name = "Admin Webspaces Servers",
Description = "Access the webspace server management page in the admin area"
};
public static Permission AdminWebspacesServerNew = new()
{
Index = 55,
Name = "Admin Webspaces Server New",
Description = "Create a new webspace server in the admin area"
};
public static Permission AdminSecurityIpBans = new()
{
Index = 56,
Name = "Admin security ip bans",
Description = "Manage ip bans in the admin area"
};
public static Permission AdminSecurityPermissionGroups = new()
{
Index = 57,
Name = "Admin security permission groups",
Description = "View, add and delete permission groups"
};
public static Permission? FromString(string name)
{
var type = typeof(Permissions);
var field = type
.GetFields()
.FirstOrDefault(x => x.FieldType == typeof(Permission) && x.Name == name);
if (field != null)
{
var value = field.GetValue(null);
return value as Permission;
}
return null;
}
public static Permission[] GetAllPermissions()
{
var type = typeof(Permissions);
return type
.GetFields()
.Where(x => x.FieldType == typeof(Permission))
.Select(x => (x.GetValue(null) as Permission)!)
.ToArray();
}
}

View File

@@ -39,7 +39,7 @@ public class RatingService
if (!Enabled)
return false;
var user = await IdentityService.Get();
var user = IdentityService.User;
if (user == null)
return false;
@@ -62,7 +62,7 @@ public class RatingService
public async Task<bool> Rate(int rate)
{
var user = await IdentityService.Get();
var user = IdentityService.User;
// Double check states:

View File

@@ -2,9 +2,10 @@
using JWT.Algorithms;
using JWT.Builder;
using JWT.Exceptions;
using Microsoft.EntityFrameworkCore;
using Moonlight.App.Database.Entities;
using Moonlight.App.Helpers;
using Moonlight.App.Models.Misc;
using Moonlight.App.Perms;
using Moonlight.App.Repositories;
using UAParser;
@@ -12,16 +13,21 @@ namespace Moonlight.App.Services.Sessions;
public class IdentityService
{
private readonly UserRepository UserRepository;
private readonly Repository<User> UserRepository;
private readonly CookieService CookieService;
private readonly IHttpContextAccessor HttpContextAccessor;
private readonly string Secret;
private User? UserCache;
public User User { get; private set; }
public string Ip { get; private set; } = "N/A";
public string Device { get; private set; } = "N/A";
public PermissionStorage Permissions { get; private set; }
public PermissionStorage UserPermissions { get; private set; }
public PermissionStorage GroupPermissions { get; private set; }
public IdentityService(
CookieService cookieService,
UserRepository userRepository,
Repository<User> userRepository,
IHttpContextAccessor httpContextAccessor,
ConfigService configService)
{
@@ -34,13 +40,17 @@ public class IdentityService
.Moonlight.Security.Token;
}
public async Task<User?> Get()
public async Task Load()
{
await LoadIp();
await LoadDevice();
await LoadUser();
}
private async Task LoadUser()
{
try
{
if (UserCache != null)
return UserCache;
var token = "none";
// Load token via http context if available
@@ -60,13 +70,13 @@ public class IdentityService
if (token == "none")
{
return null;
return;
}
if (string.IsNullOrEmpty(token))
return null;
return;
var json = "";
string json;
try
{
@@ -77,18 +87,18 @@ public class IdentityService
}
catch (TokenExpiredException)
{
return null;
return;
}
catch (SignatureVerificationException)
{
Logger.Warn($"Detected a manipulated JWT: {token}", "security");
return null;
return;
}
catch (Exception e)
{
Logger.Error("Error reading jwt");
Logger.Error(e);
return null;
return;
}
// To make it easier to use the json data
@@ -101,8 +111,9 @@ public class IdentityService
if (user == null)
{
Logger.Warn($"Cannot find user with the id '{userid}' in the database. Maybe the user has been deleted or a token has been successfully faked by a hacker");
return null;
Logger.Warn(
$"Cannot find user with the id '{userid}' in the database. Maybe the user has been deleted or a token has been successfully faked by a hacker");
return;
}
var iat = data.GetValue<long>("iat", -1);
@@ -110,46 +121,54 @@ public class IdentityService
if (iat == -1)
{
Logger.Debug("Legacy token found (without the time the token has been issued at)");
return null;
return;
}
var iatD = DateTimeOffset.FromUnixTimeSeconds(iat).ToUniversalTime().DateTime;
if (iatD < user.TokenValidTime)
return null;
return;
UserCache = user;
User = user;
user.LastIp = GetIp();
UserRepository.Update(user);
ConstructPermissions();
return UserCache;
User.LastIp = Ip;
UserRepository.Update(User);
}
catch (Exception e)
{
Logger.Error("Unexpected error while processing token");
Logger.Error(e);
return null;
return;
}
}
public string GetIp()
private Task LoadIp()
{
if (HttpContextAccessor.HttpContext == null)
return "N/A";
if(HttpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-Real-IP"))
{
return HttpContextAccessor.HttpContext.Request.Headers["X-Real-IP"]!;
Ip = "N/A";
return Task.CompletedTask;
}
return HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
if (HttpContextAccessor.HttpContext.Request.Headers.ContainsKey("X-Real-IP"))
{
Ip = HttpContextAccessor.HttpContext.Request.Headers["X-Real-IP"]!;
return Task.CompletedTask;
}
public string GetDevice()
Ip = HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
return Task.CompletedTask;
}
private Task LoadDevice()
{
if (HttpContextAccessor.HttpContext == null)
return "N/A";
{
Device = "N/A";
return Task.CompletedTask;
}
try
{
@@ -159,17 +178,86 @@ public class IdentityService
{
var version = userAgent.Remove(0, "Moonlight.App/".Length).Split(' ').FirstOrDefault();
return "Moonlight App " + version;
Device = "Moonlight App " + version;
return Task.CompletedTask;
}
var uaParser = Parser.GetDefault();
var info = uaParser.Parse(userAgent);
return $"{info.OS} - {info.Device}";
Device = $"{info.OS} - {info.Device}";
return Task.CompletedTask;
}
catch (Exception e)
{
return "UserAgent not present";
Device = "UserAgent not present";
return Task.CompletedTask;
}
}
public Task SavePermissions()
{
if (User != null)
{
User.Permissions = UserPermissions.Data;
UserRepository.Update(User);
ConstructPermissions();
}
return Task.CompletedTask;
}
private void ConstructPermissions()
{
if (User == null)
{
UserPermissions = new(Array.Empty<byte>());
GroupPermissions = new(Array.Empty<byte>(), true);
Permissions = new(Array.Empty<byte>(), true);
return;
}
var user = UserRepository
.Get()
.Include(x => x.PermissionGroup)
.First(x => x.Id == User.Id);
UserPermissions = new PermissionStorage(user.Permissions);
if (user.PermissionGroup == null)
GroupPermissions = new PermissionStorage(Array.Empty<byte>(), true);
else
GroupPermissions = new PermissionStorage(user.PermissionGroup.Permissions, true);
if (user.Admin)
{
Permissions = new PermissionStorage(Array.Empty<byte>());
foreach (var permission in Perms.Permissions.GetAllPermissions())
{
Permissions[permission] = true;
}
Permissions.IsReadyOnly = true;
return;
}
Permissions = new(Array.Empty<byte>());
foreach (var permission in Perms.Permissions.GetAllPermissions())
{
Permissions[permission] = GroupPermissions[permission];
}
foreach (var permission in Perms.Permissions.GetAllPermissions())
{
if (UserPermissions[permission])
{
Permissions[permission] = true;
}
}
Permissions.IsReadyOnly = true;
}
}

View File

@@ -19,7 +19,7 @@ public class IpBanService
public Task<bool> IsBanned()
{
var ip = IdentityService.GetIp();
var ip = IdentityService.Ip;
return Task.FromResult(
IpBanRepository

View File

@@ -15,7 +15,7 @@ public class IpLocateService
public async Task<string> GetLocation()
{
var ip = IdentityService.GetIp();
var ip = IdentityService.Ip;
var location = "N/A";
if (ip != "N/A")

View File

@@ -40,9 +40,9 @@ public class SessionClientService
public async Task Start()
{
User = await IdentityService.Get();
Ip = IdentityService.GetIp();
Device = IdentityService.GetDevice();
User = IdentityService.User;
Ip = IdentityService.Ip;
Device = IdentityService.Device;
if (User != null) // Track users last visit
{

View File

@@ -34,7 +34,7 @@ public class SupportChatAdminService : IDisposable
public async Task Start(User recipient)
{
User = await IdentityService.Get();
User = IdentityService.User;
Recipient = recipient;
if (User != null)

View File

@@ -33,7 +33,7 @@ public class SupportChatClientService : IDisposable
public async Task Start()
{
User = await IdentityService.Get();
User = IdentityService.User;
if (User != null)
{

View File

@@ -25,32 +25,30 @@ public class TotpService
return Task.FromResult(codeserver == code);
}
public async Task<bool> GetEnabled()
public Task<bool> GetEnabled()
{
var user = await IdentityService.Get();
return user!.TotpEnabled;
return Task.FromResult(IdentityService.User.TotpEnabled);
}
public async Task<string> GetSecret()
public Task<string> GetSecret()
{
var user = await IdentityService.Get();
return user!.TotpSecret;
return Task.FromResult(IdentityService.User.TotpSecret);
}
public async Task GenerateSecret()
public Task GenerateSecret()
{
var user = (await IdentityService.Get())!;
var user = IdentityService.User;
user.TotpSecret = Base32Encoding.ToString(KeyGeneration.GenerateRandomKey(20));;
UserRepository.Update(user);
return Task.CompletedTask;
}
public async Task Enable(string code)
{
var user = (await IdentityService.Get())!;
var user = IdentityService.User;
if (!await Verify(user.TotpSecret, code))
{
@@ -61,9 +59,9 @@ public class TotpService
UserRepository.Update(user);
}
public async Task Disable()
public Task Disable()
{
var user = (await IdentityService.Get())!;
var user = IdentityService.User;
user.TotpEnabled = false;
user.TotpSecret = "";
@@ -71,5 +69,7 @@ public class TotpService
UserRepository.Update(user);
//TODO: AuditLog
return Task.CompletedTask;
}
}

View File

@@ -85,8 +85,8 @@ public class UserService
TotpSecret = "",
UpdatedAt = DateTimeService.GetCurrent(),
TokenValidTime = DateTimeService.GetCurrent().AddDays(-5),
LastIp = IdentityService.GetIp(),
RegisterIp = IdentityService.GetIp()
LastIp = IdentityService.Ip,
RegisterIp = IdentityService.Ip
});
await MailService.SendMail(user!, "register", values => {});
@@ -174,8 +174,8 @@ public class UserService
await MailService.SendMail(user!, "passwordChange", values =>
{
values.Add("Ip", IdentityService.GetIp());
values.Add("Device", IdentityService.GetDevice());
values.Add("Ip", IdentityService.Ip);
values.Add("Device", IdentityService.Device);
values.Add("Location", location);
});
@@ -212,8 +212,8 @@ public class UserService
{
await MailService.SendMail(user!, "login", values =>
{
values.Add("Ip", IdentityService.GetIp());
values.Add("Device", IdentityService.GetDevice());
values.Add("Ip", IdentityService.Ip);
values.Add("Device", IdentityService.Device);
values.Add("Location", location);
});
}
@@ -249,8 +249,8 @@ public class UserService
await MailService.SendMail(user, "passwordReset", values =>
{
values.Add("Ip", IdentityService.GetIp());
values.Add("Device", IdentityService.GetDevice());
values.Add("Ip", IdentityService.Ip);
values.Add("Device", IdentityService.Device);
values.Add("Location", location);
values.Add("Password", newPassword);
});

View File

@@ -2,12 +2,19 @@
<Router AppAssembly="@typeof(BlazorApp).Assembly">
<Found Context="routeData">
<CascadingValue TValue="Type" Name="TargetPageType" Value="routeData.PageType">
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
</CascadingValue>
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="@typeof(NotFoundLayout)">
<p role="alert">Sorry, there's nothing at this address.</p>
<NotFoundAlert />
</LayoutView>
</NotFound>
</Router>
@code
{
}

View File

@@ -77,6 +77,9 @@
<_ContentIncludedByDefault Remove="Shared\Views\Admin\Servers\Cleanup\Exceptions\Edit.razor" />
<_ContentIncludedByDefault Remove="Shared\Components\News\NewsEditor.razor" />
<_ContentIncludedByDefault Remove="Shared\News\Edit.razor" />
<_ContentIncludedByDefault Remove="Shared\Views\Admin\AaPanels\Index.razor" />
<_ContentIncludedByDefault Remove="Shared\Views\Admin\Databases\Index.razor" />
<_ContentIncludedByDefault Remove="Shared\Components\StateLogic\OnlyAdmin.razor" />
</ItemGroup>
<ItemGroup>

View File

@@ -0,0 +1,13 @@
<div class="mx-auto">
<div class="card">
<div class="d-flex justify-content-center pt-5">
<img height="300" width="300" src="/assets/media/svg/warning.svg" alt="Warning"/>
</div>
<span class="card-title text-center fs-3">
<TL>You have no permission to access this resource</TL>
</span>
<p class="card-body text-center fs-4 text-gray-800">
<TL>You have no permission to access this resource. This attempt has been logged ;)</TL>
</p>
</div>
</div>

View File

@@ -14,7 +14,7 @@
<span class="bullet me-5"></span> <TL>The resource was deleted</TL>
</li>
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> <TL>You have to permission to access this resource</TL>
<span class="bullet me-5"></span> <TL>You have no permission to access this resource</TL>
</li>
<li class="d-flex align-items-center py-2">
<span class="bullet me-5"></span> <TL>You may have entered invalid data</TL>

View File

@@ -1,10 +0,0 @@
@using Moonlight.App.Services
<div class="card card-flush w-lg-650px py-5">
<div class="card-body py-15 py-lg-20">
<h1 class="fw-bolder text-gray-900 mb-5"><TL>Setup complete</TL></h1>
<div class="fw-semibold fs-6 text-gray-500 mb-8">
<TL>It looks like this moonlight instance is ready to go</TL>
</div>
</div>
</div>

View File

@@ -49,9 +49,10 @@
private PasswordModel Password = new();
private User User;
private async Task Load(LazyLoader loader)
private Task Load(LazyLoader loader)
{
User = await IdentityService.Get();
User = IdentityService.User;
return Task.CompletedTask;
}
private async Task DoChange()

View File

@@ -50,9 +50,10 @@
private User User;
private NameModel Name = new ();
private async Task Load(LazyLoader loader)
private Task Load(LazyLoader loader)
{
User = await IdentityService.Get();
User = IdentityService.User;
return Task.CompletedTask;
}
private async Task SetName()

View File

@@ -57,7 +57,7 @@ else
{
receivedExceptions.Add(exception);
var user = await IdentityService.Get();
var user = IdentityService.User;
var id = user == null ? -1 : user.Id;
Logger.Error($"[{id}] An unhanded exception occured:");

View File

@@ -1,22 +0,0 @@
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/nodes">
<TL>Nodes</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/nodes/ddos">
<TL>DDos</TL>
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; } = 0;
}

View File

@@ -0,0 +1,32 @@
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/security">
<TL>Overview</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/security/malware">
<TL>Malware</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/admin/security/ipbans">
<TL>Ip bans</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 3 ? "active" : "")" href="/admin/security/permissiongroups">
<TL>Permission groups</TL>
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; } = 0;
}

View File

@@ -0,0 +1,37 @@
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-0 pb-0">
<ul class="nav nav-stretch nav-line-tabs nav-line-tabs-2x border-transparent fs-5 fw-bold">
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 0 ? "active" : "")" href="/admin/servers">
<TL>Overview</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/servers/manager">
<TL>Manager</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/admin/servers/cleanup">
<TL>Cleanup</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 3 ? "active" : "")" href="/admin/nodes">
<TL>Nodes</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 4 ? "active" : "")" href="/admin/servers/images">
<TL>Images</TL>
</a>
</li>
</ul>
</div>
</div>
@code
{
[Parameter]
public int Index { get; set; } = 0;
}

View File

@@ -9,21 +9,6 @@
<TL>Overview</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 1 ? "active" : "")" href="/admin/system/sentry">
<TL>Sentry</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 2 ? "active" : "")" href="/admin/system/malware">
<TL>Malware</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 3 ? "active" : "")" href="/admin/system/security">
<TL>Security</TL>
</a>
</li>
<li class="nav-item mt-2">
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 5 ? "active" : "")" href="/admin/system/resources">
<TL>Resources</TL>

View File

@@ -1,5 +1,8 @@
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Misc
@using Moonlight.App.Services.Sessions
@inject IdentityService IdentityService
<div class="card mb-5 mb-xl-10">
<div class="card-body pt-9 pb-0">
@@ -8,16 +11,16 @@
<div class="d-flex justify-content-between align-items-start flex-wrap mb-2">
<div class="d-flex flex-column">
<div class="d-flex align-items-center mb-2">
<a class="text-gray-900 fs-2 fw-bold me-1 @(User.StreamerMode ? "blur" : "")">@(User.FirstName) @(User.LastName)</a>
<a class="text-gray-900 fs-2 fw-bold me-1 @(IdentityService.User.StreamerMode ? "blur" : "")">@(IdentityService.User.FirstName) @(IdentityService.User.LastName)</a>
@if (User.Status == UserStatus.Verified)
@if (IdentityService.User.Status == UserStatus.Verified)
{
<i class="text-success bx bx-md bxs-badge-check"></i>
}
</div>
<div class="d-flex flex-wrap fw-semibold fs-6 mb-4 pe-2">
<span class="d-flex align-items-center text-gray-400 mb-2 @(User.StreamerMode ? "blur" : "")">
@(User.Email)
<span class="d-flex align-items-center text-gray-400 mb-2 @(IdentityService.User.StreamerMode ? "blur" : "")">
@(IdentityService.User.Email)
</span>
</div>
</div>
@@ -51,9 +54,6 @@
@code
{
[CascadingParameter]
public User User { get; set; }
[Parameter]
public int Index { get; set; } = 0;
}

View File

@@ -106,7 +106,7 @@
{
if (firstRender)
{
User = await IdentityService.Get();
User = IdentityService.User;
await InvokeAsync(StateHasChanged);
}

View File

@@ -0,0 +1,86 @@
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services
@using Moonlight.App.Perms
@inject ModalService ModalService
@inject SmartTranslateService SmartTranslateService
<div id="permissionEditor" class="modal" tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-centered modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">
<TL>Edit permissions</TL>
</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
@if (Enabled)
{
<div class="table-responsive">
<table class="table align-middle table-row-dashed fs-6 gy-5">
<tbody class="text-gray-600 fw-semibold">
@foreach (var permission in Permissions.GetAllPermissions())
{
<tr>
<td class="text-gray-800">
@(permission.Name)
</td>
<td>
@(permission.Description)
</td>
<td>
<div class="form-check form-switch form-check-custom form-check-solid">
<input class="form-check-input" type="checkbox" @bind="Storage[permission]"/>
</div>
</td>
</tr>
}
</tbody>
</table>
</div>
}
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">
<TL>Close</TL>
</button>
<WButton Text="@(SmartTranslateService.Translate("Save"))"
WorkingText="@(SmartTranslateService.Translate("Saving"))"
CssClasses="btn-primary"
OnClick="Save">
</WButton>
</div>
</div>
</div>
</div>
@code
{
[Parameter]
public byte[] InitialData { get; set; } = Array.Empty<byte>();
[Parameter]
public Func<byte[], Task>? OnSave { get; set; }
private bool Enabled = false;
private PermissionStorage Storage;
public async Task Launch()
{
Enabled = true;
Storage = new(InitialData);
await InvokeAsync(StateHasChanged);
await ModalService.Show("permissionEditor");
}
private async Task Save()
{
OnSave?.Invoke(Storage.Data);
await ModalService.Hide("permissionEditor");
Enabled = false;
await InvokeAsync(StateHasChanged);
}
}

View File

@@ -0,0 +1,62 @@
@using Moonlight.App.Services.Sessions
@using Moonlight.App.Perms
@using Moonlight.App.Helpers
@using Moonlight.Shared.Components.Alerts
@inject IdentityService IdentityService
@inject NavigationManager NavigationManager
@if (Allowed)
{
@ChildContent
}
else
{
<NoPermissionAlert />
}
@code
{
[CascadingParameter(Name = "TargetPageType")]
public Type TargetPageType { get; set; }
[Parameter]
public RenderFragment ChildContent { get; set; }
private bool Allowed = false;
protected override Task OnParametersSetAsync()
{
var attributes = TargetPageType.GetCustomAttributes(true);
var permAttrs = attributes
.Where(x => x.GetType() == typeof(PermissionRequired))
.Select(x => x as PermissionRequired)
.ToArray();
Allowed = true;
foreach (var permissionRequired in permAttrs)
{
var permission = Permissions.FromString(permissionRequired!.Name);
if (permission == null)
{
Allowed = false;
break;
}
if (!IdentityService.Permissions[permission])
{
Allowed = false;
break;
}
}
if (!Allowed)
{
Logger.Warn($"{IdentityService.Ip} has tried to access {NavigationManager.Uri} without permission", "security");
}
return Task.CompletedTask;
}
}

View File

@@ -49,7 +49,7 @@
{
if (firstRender)
{
User = await IdentityService.Get();
User = IdentityService.User;
sidebar = await JsRuntime.InvokeAsync<string>("document.body.getAttribute", "data-kt-app-layout");
StateHasChanged();
}

View File

@@ -69,7 +69,7 @@ else
</a>
</div>
if (User.Admin)
if (IdentityService.Permissions.HasAnyPermissions())
{
<div class="menu-item pt-5">
<div class="menu-content">
@@ -92,61 +92,22 @@ else
<span class="menu-title"><TL>System</TL></span>
</a>
</div>
<div data-kt-menu-trigger="click" class="menu-item menu-accordion">
<span class="menu-link">
<div class="menu-item">
<a class="menu-link" href="/admin/security">
<span class="menu-icon">
<i class="bx bx-shield"></i>
</span>
<span class="menu-title"><TL>Security</TL></span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/servers">
<span class="menu-icon">
<i class="bx bx-server"></i>
</span>
<span class="menu-title"><TL>Servers</TL></span>
<span class="menu-arrow"></span>
</span>
<div class="menu-sub menu-sub-accordion">
<div class="menu-item">
<a class="menu-link" href="/admin/servers">
<span class="menu-bullet">
<span class="bullet bullet-dot"></span>
</span>
<span class="menu-title"><TL>Overview</TL></span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/servers/manager">
<span class="menu-bullet">
<span class="bullet bullet-dot"></span>
</span>
<span class="menu-title"><TL>Manager</TL></span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/servers/cleanup">
<span class="menu-bullet">
<span class="bullet bullet-dot"></span>
</span>
<span class="menu-title"><TL>Cleanup</TL></span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/nodes">
<span class="menu-bullet">
<span class="bullet bullet-dot"></span>
</span>
<span class="menu-title"><TL>Nodes</TL></span>
</a>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/servers/images">
<span class="menu-bullet">
<span class="bullet bullet-dot"></span>
</span>
<span class="menu-title"><TL>Images</TL></span>
</a>
</div>
</div>
</div>
<div class="menu-item">
<a class="menu-link" href="/admin/webspaces">
<span class="menu-icon">
@@ -228,7 +189,7 @@ else
{
if (firstRender)
{
User = await IdentityService.Get();
User = IdentityService.User;
await InvokeAsync(StateHasChanged);
}

View File

@@ -1,28 +0,0 @@
@using Moonlight.App.Services.Sessions
@using Moonlight.App.Database.Entities
@if (User != null)
{
if (User.Admin)
{
@ChildContent
}
else if(!Silent)
{
<div class="alert alert-danger">
<TL>Missing admin permissions. This attempt has been logged ;)</TL>
</div>
}
}
@code
{
[Parameter]
public RenderFragment ChildContent { get; set; }
[CascadingParameter]
public User? User { get; set; }
[Parameter]
public bool Silent { get; set; } = false;
}

View File

@@ -42,7 +42,6 @@
}
<GlobalErrorBoundary>
<CascadingValue Value="User">
<PageTitle>@(string.IsNullOrEmpty(title) ? "Dashboard - " : title)Moonlight</PageTitle>
<div class="d-flex flex-column flex-root app-root" id="kt_app_root">
@@ -70,31 +69,33 @@
uri.LocalPath != "/passwordreset" &&
uri.LocalPath != "/register")
{
if (User == null)
if (IdentityService.User == null)
{
<Login></Login>
}
else
{
if (User.Status == UserStatus.Banned)
if (IdentityService.User.Status == UserStatus.Banned)
{
<BannedAlert></BannedAlert>
}
else if (User.Status == UserStatus.Disabled)
else if (IdentityService.User.Status == UserStatus.Disabled)
{
<DisabledAlert></DisabledAlert>
}
else if (User.Status == UserStatus.PasswordPending)
else if (IdentityService.User.Status == UserStatus.PasswordPending)
{
<PasswordChangeView></PasswordChangeView>
}
else if (User.Status == UserStatus.DataPending)
else if (IdentityService.User.Status == UserStatus.DataPending)
{
<UserDataSetView></UserDataSetView>
}
else
{
<RenderPermissionChecker>
@Body
</RenderPermissionChecker>
<RatingPopup/>
}
@@ -153,19 +154,17 @@
</div>
</div>
</div>
</CascadingValue>
</GlobalErrorBoundary>
@code
{
private User? User;
private bool UserProcessed = false;
private bool IsIpBanned = false;
protected override void OnAfterRender(bool firstRender)
{
if(firstRender)
if (firstRender)
AddBodyAttribute("data-kt-app-page-loading", "on");
//Initialize classes and attributes for layout with dark sidebar
@@ -202,7 +201,7 @@
NavigationManager.NavigateTo(NavigationManager.Uri, true);
});
User = await IdentityService.Get();
await IdentityService.Load();
UserProcessed = true;
await InvokeAsync(StateHasChanged);
@@ -213,7 +212,10 @@
await JsRuntime.InvokeVoidAsync("KTMenu.createInstances");
await JsRuntime.InvokeVoidAsync("KTDrawer.createInstances");
}
catch (Exception){ /* ignore errors to make sure that the session call is executed */ }
catch (Exception)
{
/* ignore errors to make sure that the session call is executed */
}
await SessionClientService.Start();
@@ -223,14 +225,14 @@
await DynamicBackgroundService.Reset();
};
if (User != null)
if (IdentityService.User != null)
{
await Event.On<SupportChatMessage>(
$"supportChat.{User.Id}.message",
$"supportChat.{IdentityService.User.Id}.message",
this,
async message =>
{
if (!NavigationManager.Uri.EndsWith("/support") && message.Sender != User)
if (!NavigationManager.Uri.EndsWith("/support") && message.Sender != IdentityService.User)
{
await ToastService.Info($"Support: {message.Content}");
}
@@ -257,9 +259,9 @@
await KeyListenerService.DisposeAsync();
if (User != null)
if (IdentityService.User != null)
{
await Event.Off($"supportChat.{User.Id}.message", this);
await Event.Off($"supportChat.{IdentityService.User.Id}.message", this);
}
}

View File

@@ -60,8 +60,6 @@
@code
{
private User? User;
protected override void OnInitialized()
{
AddBodyAttribute("data-kt-app-page-loading", "on");
@@ -95,7 +93,7 @@
{
try
{
User = await IdentityService.Get();
await IdentityService.Load();
await InvokeAsync(StateHasChanged);
await Task.Delay(300);

View File

@@ -1,2 +0,0 @@
@page "/admin/aapanels"

View File

@@ -1,5 +0,0 @@
@page "/admin/databases"
<OnlyAdmin>
</OnlyAdmin>

View File

@@ -9,8 +9,9 @@
@inject DomainService DomainService
@inject SmartTranslateService SmartTranslateService
<OnlyAdmin>
<LazyLoader @ref="LazyLoader" Load="Load">
@attribute [PermissionRequired(nameof(Permissions.AdminDomains))]
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="row">
<div class="card">
<div class="card-header border-0 pt-5">
@@ -60,8 +61,7 @@
</div>
</div>
</div>
</LazyLoader>
</OnlyAdmin>
</LazyLoader>
@code
{

View File

@@ -11,8 +11,9 @@
@inject NavigationManager NavigationManager
@inject DomainService DomainService
<OnlyAdmin>
<div class="row mb-5">
@attribute [PermissionRequired(nameof(Permissions.AdminNewDomain))]
<div class="row mb-5">
<div class="card card-body p-10">
<LazyLoader Load="Load">
<SmartForm Model="Model" OnValidSubmit="Add">
@@ -48,7 +49,6 @@
</LazyLoader>
</div>
</div>
</OnlyAdmin>
@code
{

View File

@@ -12,8 +12,9 @@
@inject AlertService AlertService
@inject ToastService ToastService
<OnlyAdmin>
<LazyLoader @ref="LazyLoader" Load="Load">
@attribute [PermissionRequired(nameof(Permissions.AdminSharedDomains))]
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="card">
<div class="card-header border-0 pt-5">
<h3 class="card-title align-items-start flex-column">
@@ -44,7 +45,6 @@
</div>
</div>
</LazyLoader>
</OnlyAdmin>
@code
{

View File

@@ -12,6 +12,8 @@
@inject ToastService ToastService
@inject NavigationManager NavigationManager
@attribute [PermissionRequired(nameof(Permissions.AdminNewSharedDomain))]
<LazyLoader Load="Load" @ref="LazyLoader">
<div class="row mb-5">
<div class="card card-body">

View File

@@ -14,8 +14,9 @@
@inject DomainRepository DomainRepository
@inject ConfigService ConfigService
<OnlyAdmin>
<LazyLoader Load="Load">
@attribute [PermissionRequired(nameof(Permissions.AdminDashboard))]
<LazyLoader Load="Load">
<div class="row mb-5">
<div class="col-12 col-lg-6 col-xl">
<a class="mt-4 card" href="/admin/servers">
@@ -124,8 +125,7 @@
<HealthCheckView HealthCheck="@HealthCheckData"/>
}
</LazyLoader>
</LazyLoader>
</OnlyAdmin>
</LazyLoader>
@code
{

View File

@@ -15,10 +15,11 @@
@inject SmartTranslateService SmartTranslateService
@inject EventSystem Event
<OnlyAdmin>
<AdminNodesNavigation Index="1"/>
@attribute [PermissionRequired(nameof(Permissions.AdminNodeDdos))]
<LazyLoader @ref="LazyLoader" Load="Load">
<AdminNodesNavigation Index="1"/>
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="card">
<div class="card-body pt-0">
<div class="table-responsive">
@@ -68,8 +69,7 @@
</div>
</div>
</div>
</LazyLoader>
</OnlyAdmin>
</LazyLoader>
@code
{
@@ -99,4 +99,6 @@
{
await Event.Off("node.ddos", this);
}
//TODO: Move to security
}

View File

@@ -9,8 +9,9 @@
@inject SmartTranslateService SmartTranslateService
@inject NavigationManager NavigationManager
<OnlyAdmin>
<LazyLoader Load="Load" @ref="LazyLoader">
@attribute [PermissionRequired(nameof(Permissions.AdminNodeEdit))]
<LazyLoader Load="Load" @ref="LazyLoader">
@if (Node == null)
{
<div class="alert alert-warning">
@@ -149,7 +150,6 @@
</div>
}
</LazyLoader>
</OnlyAdmin>
@code
{

View File

@@ -13,10 +13,11 @@
@inject NodeService NodeService
@inject SmartTranslateService SmartTranslateService
<OnlyAdmin>
<AdminNodesNavigation Index="0"/>
@attribute [PermissionRequired(nameof(Permissions.AdminNodes))]
<LazyLoader @ref="LazyLoader" Load="Load">
<AdminServersNavigation Index="3" />
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="card">
<div class="card-header border-0 pt-5">
<h3 class="card-title align-items-start flex-column">
@@ -94,8 +95,7 @@
}
</div>
</div>
</LazyLoader>
</OnlyAdmin>
</LazyLoader>
@code
{

View File

@@ -9,8 +9,9 @@
@inject NodeRepository NodeRepository
@inject NavigationManager NavigationManager
<OnlyAdmin>
<div class="d-flex flex-center">
@attribute [PermissionRequired(nameof(Permissions.AdminNewNode))]
<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">
@@ -73,7 +74,6 @@
</div>
</div>
</div>
</OnlyAdmin>
@code
{

View File

@@ -6,12 +6,13 @@
@inject NodeRepository NodeRepository
@inject ConfigService ConfigService
@attribute [PermissionRequired(nameof(Permissions.AdminNodeSetup))]
@{
var appUrl = ConfigService.Get().Moonlight.AppUrl;
}
<OnlyAdmin>
<LazyLoader Load="Load">
<LazyLoader Load="Load">
@if (Node == null)
{
<div class="alert alert-warning">
@@ -141,7 +142,6 @@
</div>
}
</LazyLoader>
</OnlyAdmin>
@code
{

View File

@@ -10,7 +10,8 @@
@inject NodeRepository NodeRepository
@inject NodeService NodeService
<OnlyAdmin>
@attribute [PermissionRequired(nameof(Permissions.AdminNodeView))]
<LazyLoader Load="Load">
@if (Node == null)
{
@@ -252,7 +253,6 @@ else
</div>
}
</LazyLoader>
</OnlyAdmin>
@code
{

View File

@@ -12,8 +12,9 @@
@implements IDisposable
<OnlyAdmin>
<LazyLoader Load="Load">
@attribute [PermissionRequired(nameof(Permissions.AdminNotificationDebugging))]
<LazyLoader Load="Load">
<div class="card card-body">
<Table TableItem="ActiveNotificationClient" Items="Clients" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="ActiveNotificationClient" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Client.Id)" Sortable="false" Filterable="true"/>
@@ -31,8 +32,6 @@
</Table>
</div>
</LazyLoader>
</OnlyAdmin>
@code
{

View File

@@ -0,0 +1,7 @@
@page "/admin/security"
@using Moonlight.Shared.Components.Navigations
@attribute [PermissionRequired(nameof(Permissions.AdminSecurity))]
<AdminSecurityNavigation Index="0" />

View File

@@ -1,4 +1,4 @@
@page "/admin/system/security"
@page "/admin/security/ipbans"
@using Moonlight.Shared.Components.Navigations
@using BlazorTable
@@ -13,10 +13,11 @@
@inject EventSystem Event
@inject ToastService ToastService
<OnlyAdmin>
<AdminSystemNavigation Index="3"/>
@attribute [PermissionRequired(nameof(Permissions.AdminSecurityIpBans))]
<div class="card mb-5">
<AdminSecurityNavigation Index="2"/>
<div class="card mb-5">
<div class="card-header">
<div class="card-title">
<TL>Ip Bans</TL>
@@ -40,7 +41,8 @@
</div>
<div class="card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
<Table TableItem="IpBan" Items="IpBans" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<div class="table-responsive">
<Table TableItem="IpBan" Items="AllIpBans" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="IpBan" Title="@(SmartTranslateService.Translate("Ip"))" Field="@(x => x.Ip)" Filterable="true" Sortable="false"/>
<Column TableItem="IpBan" Title="" Field="@(x => x.Id)" Filterable="false" Sortable="false">
<Template>
@@ -51,21 +53,21 @@
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
</div>
</OnlyAdmin>
</div>
@code
{
private IpBan[] IpBans;
private IpBan[] AllIpBans;
private string Ip;
private LazyLoader LazyLoader;
private Task Load(LazyLoader arg)
{
IpBans = IpBanRepository.Get().ToArray();
AllIpBans = IpBanRepository.Get().ToArray();
return Task.CompletedTask;
}

View File

@@ -0,0 +1,134 @@
@page "/admin/security/malware"
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services.Background
@using Moonlight.App.Services
@using BlazorTable
@using Moonlight.App.Database.Entities
@using Moonlight.App.Events
@using Moonlight.App.Models.Misc
@inject MalwareScanService MalwareScanService
@inject SmartTranslateService SmartTranslateService
@inject EventSystem Event
@implements IDisposable
@attribute [PermissionRequired(nameof(Permissions.AdminSecurityMalware))]
<AdminSecurityNavigation Index="1"/>
<div class="row">
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-body">
@if (MalwareScanService.IsRunning)
{
<span class="fs-3 spinner-border align-middle me-3"></span>
}
<span class="fs-3">@(MalwareScanService.Status)</span>
</div>
<div class="card-footer">
@if (MalwareScanService.IsRunning)
{
<button class="btn btn-success disabled">
<TL>Scan in progress</TL>
</button>
}
else
{
<WButton Text="@(SmartTranslateService.Translate("Start scan"))"
CssClasses="btn-success"
OnClick="MalwareScanService.Start">
</WButton>
}
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Results</TL>
</span>
</div>
<div class="card-body">
<LazyLoader @ref="LazyLoaderResults" Load="LoadResults">
<div class="table-responsive">
<Table TableItem="Server" Items="ScanResults.Keys" 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("Server"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<a href="/server/@(context.Uuid)">@(context.Name)</a>
</Template>
</Column>
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Results"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<div class="row">
@foreach (var result in ScanResults[context])
{
<div class="col-12 col-md-6 p-3">
<div class="accordion" id="scanResult@(result.GetHashCode())">
<div class="accordion-item">
<h2 class="accordion-header" id="scanResult-header@(result.GetHashCode())">
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#scanResult-body@(result.GetHashCode())" aria-expanded="false" aria-controls="scanResult-body@(result.GetHashCode())">
<span>@(result.Title)</span>
</button>
</h2>
<div id="scanResult-body@(result.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="scanResult-header@(result.GetHashCode())" data-bs-parent="#scanResult">
<div class="accordion-body">
<p>
@(result.Description)
</p>
</div>
</div>
</div>
</div>
</div>
}
</div>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
</div>
</div>
</div>
@code
{
private readonly Dictionary<Server, MalwareScanResult[]> ScanResults = new();
private LazyLoader LazyLoaderResults;
protected override async Task OnInitializedAsync()
{
await Event.On<Object>("malwareScan.status", this, async o => { await InvokeAsync(StateHasChanged); });
await Event.On<Object>("malwareScan.result", this, async o => { await LazyLoaderResults.Reload(); });
}
private Task LoadResults(LazyLoader arg)
{
ScanResults.Clear();
lock (MalwareScanService.ScanResults)
{
foreach (var result in MalwareScanService.ScanResults)
{
ScanResults.Add(result.Key, result.Value);
}
}
return Task.CompletedTask;
}
public async void Dispose()
{
await Event.Off("malwareScan.status", this);
await Event.Off("malwareScan.result", this);
}
}

View File

@@ -0,0 +1,132 @@
@page "/admin/security/permissiongroups"
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using BlazorTable
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services.Sessions
@inject SmartTranslateService SmartTranslateService
@inject Repository<PermissionGroup> PermissionGroupRepository
@inject SessionServerService SessionServerService
@inject Repository<User> UserRepository
@inject AlertService AlertService
@inject ToastService ToastService
@attribute [PermissionRequired(nameof(Permissions.AdminSecurityPermissionGroups))]
<AdminSecurityNavigation Index="3"/>
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Permission groups</TL>
</span>
<div class="card-toolbar">
<WButton Text="@(SmartTranslateService.Translate("New"))"
CssClasses="btn-sm btn-success"
OnClick="NewGroupPermission">
</WButton>
</div>
</div>
<div class="card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="table-responsive">
<Table TableItem="PermissionGroup" Items="AllPermissionGroups" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="PermissionGroup" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Filterable="true" Sortable="false"/>
<Column TableItem="PermissionGroup" Title="" Field="@(x => x.Id)" Filterable="false" Sortable="false">
<Template>
<div class="text-end">
<WButton Text="@(SmartTranslateService.Translate("Edit permissions"))"
CssClasses="btn-primary me-2"
OnClick="() => EditPermissions(context)">
</WButton>
<DeleteButton Confirm="true" OnClick="() => DeletePermissionGroup(context)"></DeleteButton>
</div>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
</div>
<PermissionEditor @ref="PermissionEditor" OnSave="OnSave"/>
@code
{
private PermissionGroup[] AllPermissionGroups;
private PermissionGroup CurrentPermissionGroup;
private LazyLoader LazyLoader;
private PermissionEditor PermissionEditor;
private Task Load(LazyLoader arg)
{
AllPermissionGroups = PermissionGroupRepository
.Get()
.ToArray();
return Task.CompletedTask;
}
private async Task EditPermissions(PermissionGroup group)
{
CurrentPermissionGroup = group;
PermissionEditor.InitialData = CurrentPermissionGroup.Permissions;
await PermissionEditor.Launch();
}
private async Task DeletePermissionGroup(PermissionGroup group)
{
PermissionGroupRepository.Delete(group);
await LazyLoader.Reload();
}
private async Task OnSave(byte[] data)
{
CurrentPermissionGroup.Permissions = data;
PermissionGroupRepository.Update(CurrentPermissionGroup);
await ToastService.Success("Successfully modified permissions");
var usersWithTheGroup = UserRepository
.Get()
.Include(x => x.PermissionGroup)
.Where(x => x.PermissionGroup != null)
.Where(x => x.PermissionGroup!.Id == CurrentPermissionGroup.Id)
.ToArray();
foreach (var user in usersWithTheGroup)
{
await SessionServerService.ReloadUserSessions(user);
}
}
private async Task NewGroupPermission()
{
var name = await AlertService.Text(
SmartTranslateService.Translate("Enter the name for the new group"),
"",
""
);
if(string.IsNullOrEmpty(name))
return;
var group = new PermissionGroup()
{
Name = name,
Permissions = Array.Empty<byte>()
};
PermissionGroupRepository.Add(group);
await LazyLoader.Reload();
}
}

View File

@@ -1,14 +1,18 @@
@page "/admin/servers/cleanup"
@using Moonlight.App.Events
@using Moonlight.App.Services.Background
@using Moonlight.Shared.Components.Navigations
@inject CleanupService CleanupService
@inject EventSystem Event
@implements IDisposable
<OnlyAdmin>
<div class="row g-5 g-xl-10 mb-5 mb-xl-10">
@attribute [PermissionRequired(nameof(Permissions.AdminServerCleanup))]
<AdminServersNavigation Index="2" />
<div class="row g-5 g-xl-10 mb-5 mb-xl-10">
<div class="col-xl-3">
<div class="card card-flush bgi-no-repeat bgi-size-contain bgi-position-x-end h-xl-100" style="background-color: #170049;">
<div class="card-header pt-5 mb-3">
@@ -76,7 +80,6 @@
</div>
</div>
</div>
</OnlyAdmin>
@code
{

View File

@@ -13,8 +13,9 @@
@inject ImageRepository ImageRepository
@inject Repository<User> UserRepository
<OnlyAdmin>
<LazyLoader @ref="LazyLoader" Load="Load">
@attribute [PermissionRequired(nameof(Permissions.AdminServerEdit))]
<LazyLoader @ref="LazyLoader" Load="Load">
@if (Server == null)
{
<div class="alert alert-danger">
@@ -171,7 +172,6 @@
</SmartForm>
}
</LazyLoader>
</OnlyAdmin>
@code
{

View File

@@ -12,17 +12,18 @@
@inject ToastService ToastService
@inject FileDownloadService FileDownloadService
<OnlyAdmin>
@attribute [PermissionRequired(nameof(Permissions.AdminServerImageEdit))]
<div class="row">
<LazyLoader @ref="LazyLoader" Load="Load">
@if (Image == null)
{
<LazyLoader @ref="LazyLoader" Load="Load">
@if (Image == null)
{
<div class="alert alert-danger">
<TL>No image with this id found</TL>
</div>
}
else
{
}
else
{
<div class="row">
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="card card-body">
@@ -254,10 +255,9 @@ else
</div>
</div>
</div>
}
</LazyLoader>
</div>
</OnlyAdmin>
}
</LazyLoader>
</div>
@code
{

View File

@@ -9,6 +9,7 @@
@using System.Text
@using Moonlight.App.Helpers
@using Newtonsoft.Json
@using Moonlight.Shared.Components.Navigations
@inject Repository<Image> ImageRepository
@inject Repository<ImageVariable> ImageVariableRepository
@@ -16,9 +17,11 @@
@inject SmartTranslateService SmartTranslateService
@inject AlertService AlertService
<OnlyAdmin>
<div class="row">
<LazyLoader @ref="LazyLoader" Load="Load">
@attribute [PermissionRequired(nameof(Permissions.AdminServerImages))]
<AdminServersNavigation Index="4" />
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="card">
<div class="card-header border-0 pt-5">
<h3 class="card-title align-items-start flex-column">
@@ -103,8 +106,6 @@
</div>
</div>
</LazyLoader>
</div>
</OnlyAdmin>
@code
{

View File

@@ -4,17 +4,22 @@
@using Moonlight.App.Repositories.Servers
@using BlazorTable
@using Microsoft.EntityFrameworkCore
@using Moonlight.Shared.Components.Navigations
@inject ServerRepository ServerRepository
@inject SmartTranslateService SmartTranslateService
<OnlyAdmin>
<div class="row">
<LazyLoader Load="Load">
@attribute [PermissionRequired(nameof(Permissions.AdminServers))]
<AdminServersNavigation Index="0" />
<LazyLoader Load="Load">
<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>
<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">
@@ -62,8 +67,6 @@
</div>
</div>
</LazyLoader>
</div>
</OnlyAdmin>
@code
{
@@ -74,6 +77,8 @@
Servers = ServerRepository
.Get()
.Include(x => x.Owner)
.ToArray() // Execute query and use the moonlight instance to sort
.OrderBy(x => x.Id)
.ToArray();
return Task.CompletedTask;

View File

@@ -6,7 +6,7 @@
@using Moonlight.App.Database.Entities
@using BlazorTable
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.ApiClients.Daemon.Resources
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.ApiClients.Wings
@using Moonlight.App.Helpers
@using Moonlight.App.Models.Misc
@@ -18,17 +18,31 @@
@inject AlertService AlertService
@inject ServerService ServerService
<OnlyAdmin>
<div class="card mb-5">
<div class="card-body">
@attribute [PermissionRequired(nameof(Permissions.AdminServerManager))]
<AdminServersNavigation Index="1"/>
<div class="card">
<div class="card-header">
<span class="card-title">
@if (IsRunning)
{
<span><TL>Status</TL>:&nbsp;<TL>Currently scanning</TL> @(Node?.Name)</span>
}
else
{
<span><TL>Status</TL>:&nbsp;<TL>Scan complete</TL></span>
}
</span>
<div class="card-toolbar">
<WButton Text="@(SmartTranslateService.Translate("Refresh"))"
WorkingText="@(SmartTranslateService.Translate("Working"))"
CssClasses="btn-primary"
CssClasses="btn-primary me-2"
OnClick="() => Task.Run(Scan)">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Stop all"))"
WorkingText="@(SmartTranslateService.Translate("Working"))"
CssClasses="btn-danger"
CssClasses="btn-danger me-2"
OnClick="StopAll">
</WButton>
<WButton Text="@(SmartTranslateService.Translate("Kill all"))"
@@ -38,19 +52,7 @@
</WButton>
</div>
</div>
<div class="card mb-5 bg-secondary">
<div class="card-body d-flex align-items-center">
@if (IsRunning)
{
<h4><TL>Currently scanning</TL>: @(Node?.Name)</h4>
}
else
{
<TL>Scan complete</TL>
}
</div>
</div>
<div class="card card-body">
<div class="card-body">
<div class="table-responsive">
<Table TableItem="RunningServer" Items="RunningServers" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
<Column TableItem="RunningServer" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Server.Name)" Sortable="true" Filterable="true">
@@ -97,7 +99,7 @@
</Table>
</div>
</div>
</OnlyAdmin>
</div>
@code
{

View File

@@ -17,8 +17,9 @@
@inject NavigationManager NavigationManager
@inject UserRepository UserRepository
<OnlyAdmin>
<LazyLoader Load="Load">
@attribute [PermissionRequired(nameof(Permissions.AdminNewServer))]
<LazyLoader Load="Load">
<SmartForm Model="Model" OnValidSubmit="Create">
<div class="row mb-5">
<div class="card card-body p-10">
@@ -173,7 +174,6 @@
</div>
</SmartForm>
</LazyLoader>
</OnlyAdmin>
@code
{

View File

@@ -10,8 +10,9 @@
@inject StatisticsViewService StatisticsViewService
@inject SmartTranslateService SmartTranslateService
<OnlyAdmin>
<div class="row mt-4 mb-2">
@attribute [PermissionRequired(nameof(Permissions.AdminStatistics))]
<div class="row mt-4 mb-2">
<div class="col-12 col-lg-6 col-xl">
<div class="card card-body">
<select class="form-select" @bind="TimeSpanBind">
@@ -33,9 +34,9 @@
</select>
</div>
</div>
</div>
</div>
<LazyLoader @ref="Loader" Load="Load">
<LazyLoader @ref="Loader" Load="Load">
@foreach (var charts in Charts.Chunk(2))
{
<div class="row">
@@ -80,8 +81,7 @@
</div>
</div>
</div>
</LazyLoader>
</OnlyAdmin>
</LazyLoader>
@code
{

View File

@@ -10,8 +10,9 @@
@inject SubscriptionRepository SubscriptionRepository
@inject SubscriptionService SubscriptionService
<OnlyAdmin>
<div class="card card-body p-10">
@attribute [PermissionRequired(nameof(Permissions.AdminSubscriptionEdit))]
<div class="card card-body p-10">
<LazyLoader Load="Load">
@if (Subscription == null)
{
@@ -157,8 +158,7 @@
</SmartForm>
}
</LazyLoader>
</div>
</OnlyAdmin>
</div>
@code
{

View File

@@ -9,8 +9,9 @@
@inject SubscriptionRepository SubscriptionRepository
@inject SubscriptionService SubscriptionService
<OnlyAdmin>
<div class="card">
@attribute [PermissionRequired(nameof(Permissions.AdminSubscriptions))]
<div class="card">
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="card-header border-0 pt-5">
<h3 class="card-title align-items-start flex-column">
@@ -54,7 +55,6 @@
</div>
</LazyLoader>
</div>
</OnlyAdmin>
@code
{

View File

@@ -8,8 +8,9 @@
@inject SubscriptionRepository SubscriptionRepository
@inject SubscriptionService SubscriptionService
<OnlyAdmin>
<div class="card card-body p-10">
@attribute [PermissionRequired(nameof(Permissions.AdminNewSubscription))]
<div class="card card-body p-10">
<SmartForm Model="Model" OnValidSubmit="OnSubmit">
<label class="form-label">
<TL>Name</TL>
@@ -145,7 +146,6 @@
</div>
</SmartForm>
</div>
</OnlyAdmin>
@code
{

View File

@@ -8,8 +8,9 @@
@implements IDisposable
<OnlyAdmin>
<LazyLoader @ref="LazyLoader" Load="Load">
@attribute [PermissionRequired(nameof(Permissions.AdminSupport))]
<LazyLoader @ref="LazyLoader" Load="Load">
<div class="card">
<div class="card-body">
<div class="d-flex flex-column flex-xl-row p-5 pb-0">
@@ -70,7 +71,6 @@
</div>
</div>
</LazyLoader>
</OnlyAdmin>
@code
{

View File

@@ -14,8 +14,9 @@
@implements IDisposable
<OnlyAdmin>
<LazyLoader Load="Load">
@attribute [PermissionRequired(nameof(Permissions.AdminSupportView))]
<LazyLoader Load="Load">
@if (User == null)
{
<div class="alert alert-danger">
@@ -220,7 +221,6 @@
</div>
}
</LazyLoader>
</OnlyAdmin>
@code
{

View File

@@ -1,106 +0,0 @@
@page "/admin/system/auditlog"
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Repositories.LogEntries
@using Moonlight.App.Services
@using Moonlight.App.Database.Entities.LogsEntries
@using BlazorTable
@using Moonlight.App.Helpers
@using Moonlight.App.Models.Misc
@using Moonlight.Shared.Components.AuditLogEntrys
@inject AuditLogEntryRepository AuditLogEntryRepository
<OnlyAdmin>
<AdminSystemNavigation Index="2"/>
<div class="card">
<div class="card-header card-header-stretch">
<div class="card-title d-flex align-items-center">
<span class="me-3 lh-0">
<i class="bx bx-md bx-calendar"></i>
</span>
<h3 class="fw-bold m-0 text-gray-800">@(Formatter.FormatDateOnly(DateTime))</h3>
</div>
<div class="card-toolbar m-0">
<ul class="nav nav-tabs nav-line-tabs nav-stretch fs-6 border-0 fw-bold">
<li class="nav-item">
<button @onclick="Left" class="nav-link justify-content-center text-active-gray-800">
<i class="bx bx-md bx-left-arrow"></i>
</button>
</li>
<li class="nav-item">
<button @onclick="Right" class="nav-link justify-content-center text-active-gray-800">
<i class="bx bx-md bx-right-arrow"></i>
</button>
</li>
</ul>
</div>
</div>
<div class="card-body">
<LazyLoader @ref="LazyLoader" Load="Load">
@if (AuditLogEntries.Any())
{
<div class="timeline">
@foreach (var entry in AuditLogEntries)
{
switch (entry.Type)
{
case AuditLogType.Login:
<AuditLogEntryLogin Entry="entry"/>
break;
case AuditLogType.Register:
<AuditLogEntryRegister Entry="entry"/>
break;
case AuditLogType.ChangePassword:
<AuditLogEntryChangePassword Entry="entry"/>
break;
case AuditLogType.ChangePowerState:
<AuditLogEntryChangePowerState Entry="entry"/>
break;
}
}
</div>
}
else
{
<div class="alert alert-primary">
<TL>No records found for this day</TL>
</div>
}
</LazyLoader>
</div>
</div>
</OnlyAdmin>
@code
{
private AuditLogEntry[] AuditLogEntries;
private LazyLoader LazyLoader;
private DateTime DateTime = DateTime.Today;
private Task Load(LazyLoader arg)
{
AuditLogEntries = AuditLogEntryRepository
.Get()
.Where(x => x.CreatedAt.Date == DateTime.Date)
.OrderByDescending(x => x.Id)
.ToArray();
return Task.CompletedTask;
}
private async Task Left()
{
DateTime = DateTime.AddDays(1);
await LazyLoader.Reload();
}
private async Task Right()
{
DateTime = DateTime.AddDays(-1);
await LazyLoader.Reload();
}
}

View File

@@ -9,10 +9,11 @@
@inject ToastService ToastService
@inject SmartTranslateService SmartTranslateService
<OnlyAdmin>
<AdminSystemNavigation Index="8"/>
@attribute [PermissionRequired(nameof(Permissions.AdminSysConfiguration))]
<LazyLoader Load="Load">
<AdminSystemNavigation Index="8"/>
<LazyLoader Load="Load">
<div class="card">
<SmartForm Model="Config" OnValidSubmit="OnSubmit">
<div class="card-body">
@@ -27,8 +28,7 @@
</div>
</SmartForm>
</div>
</LazyLoader>
</OnlyAdmin>
</LazyLoader>
@code
{

View File

@@ -4,10 +4,11 @@
@inject DiscordBotService DiscordBotService
<OnlyAdmin>
<AdminSystemNavigation Index="6"/>
@attribute [PermissionRequired(nameof(Permissions.AdminSysDiscordBot))]
<div class="mt-3 card card-body">
<AdminSystemNavigation Index="6"/>
<div class="mt-3 card card-body">
<WButton Text="Register commands"
WorkingText="Working"
CssClasses="btn-primary"
@@ -19,8 +20,7 @@
CssClasses="mt-3 btn-danger"
OnClick="VoidCommands">
</WButton>
</div>
</OnlyAdmin>
</div>
@code
{

View File

@@ -7,10 +7,11 @@
@inject HostSystemHelper HostSystemHelper
@inject MoonlightService MoonlightService
<OnlyAdmin>
<AdminSystemNavigation Index="0"/>
@attribute [PermissionRequired(nameof(Permissions.AdminSystem))]
<LazyLoader Load="Load">
<AdminSystemNavigation Index="0"/>
<LazyLoader Load="Load">
<div class="row">
<div class="col-xxl-6 my-3">
<div class="card">
@@ -91,8 +92,7 @@
</div>
</div>
</div>
</LazyLoader>
</OnlyAdmin>
</LazyLoader>
@code
{

View File

@@ -5,21 +5,23 @@
@using Moonlight.App.Helpers.Files
@using Moonlight.App.Helpers
@using BlazorTable
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Misc
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services.Mail
@using Moonlight.App.Services.Sessions
@inject SmartTranslateService SmartTranslateService
@inject ToastService ToastService
@inject AlertService AlertService
@inject MailService MailService
@inject IdentityService IdentityService
<OnlyAdmin>
<AdminSystemNavigation Index="9"/>
@attribute [PermissionRequired(nameof(Permissions.AdminSysMail))]
<div class="card mb-3">
<AdminSystemNavigation Index="9"/>
<div class="card mb-3">
<div class="card-header">
<span class="card-title">
<TL>Actions</TL>
@@ -32,9 +34,9 @@
OnClick="SendTestMail">
</WButton>
</div>
</div>
</div>
<LazyLoader @ref="LazyLoader" Load="Load">
<LazyLoader @ref="LazyLoader" Load="Load">
@if (CurrentMailTemplate == null)
{
<div class="card">
@@ -87,13 +89,11 @@
OnCancel="OnCancelTemplateEdit"
OnSubmit="OnSubmitTemplateEdit"/>
}
</LazyLoader>
</OnlyAdmin>
</LazyLoader>
@code
{
[CascadingParameter]
public User User { get; set; }
private MailTemplate[] MailTemplateFiles;
private FileAccess FileAccess;
@@ -174,7 +174,7 @@
private async Task SendTestMail()
{
await MailService.SendMailRaw(User, "<html><body>If you see this mail, your moonlight mail configuration is ready to use</body></html>");
await MailService.SendMailRaw(IdentityService.User, "<html><body>If you see this mail, your moonlight mail configuration is ready to use</body></html>");
await AlertService.Info(SmartTranslateService.Translate("A test mail has been sent to the email address of your account"));
}
}

View File

@@ -1,134 +0,0 @@
@page "/admin/system/malware"
@using Moonlight.Shared.Components.Navigations
@using Moonlight.App.Services.Background
@using Moonlight.App.Services
@using BlazorTable
@using Moonlight.App.Database.Entities
@using Moonlight.App.Events
@using Moonlight.App.Models.Misc
@inject MalwareScanService MalwareScanService
@inject SmartTranslateService SmartTranslateService
@inject EventSystem Event
@implements IDisposable
<OnlyAdmin>
<AdminSystemNavigation Index="2"/>
<div class="row">
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-body">
@if (MalwareScanService.IsRunning)
{
<span class="fs-3 spinner-border align-middle me-3"></span>
}
<span class="fs-3">@(MalwareScanService.Status)</span>
</div>
<div class="card-footer">
@if (MalwareScanService.IsRunning)
{
<button class="btn btn-success disabled">
<TL>Scan in progress</TL>
</button>
}
else
{
<WButton Text="@(SmartTranslateService.Translate("Start scan"))"
CssClasses="btn-success"
OnClick="MalwareScanService.Start">
</WButton>
}
</div>
</div>
</div>
<div class="col-12 col-lg-6">
<div class="card">
<div class="card-header">
<span class="card-title">
<TL>Results</TL>
</span>
</div>
<div class="card-body">
<LazyLoader @ref="LazyLoaderResults" Load="LoadResults">
<div class="table-responsive">
<Table TableItem="Server" Items="ScanResults.Keys" 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("Server"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<a href="/server/@(context.Uuid)">@(context.Name)</a>
</Template>
</Column>
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Results"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
<Template>
<div class="row">
@foreach (var result in ScanResults[context])
{
<div class="col-12 col-md-6 p-3">
<div class="accordion" id="scanResult@(result.GetHashCode())">
<div class="accordion-item">
<h2 class="accordion-header" id="scanResult-header@(result.GetHashCode())">
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#scanResult-body@(result.GetHashCode())" aria-expanded="false" aria-controls="scanResult-body@(result.GetHashCode())">
<span>@(result.Title)</span>
</button>
</h2>
<div id="scanResult-body@(result.GetHashCode())" class="accordion-collapse collapse" aria-labelledby="scanResult-header@(result.GetHashCode())" data-bs-parent="#scanResult">
<div class="accordion-body">
<p>
@(result.Description)
</p>
</div>
</div>
</div>
</div>
</div>
}
</div>
</Template>
</Column>
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
</Table>
</div>
</LazyLoader>
</div>
</div>
</div>
</div>
</OnlyAdmin>
@code
{
private readonly Dictionary<Server, MalwareScanResult[]> ScanResults = new();
private LazyLoader LazyLoaderResults;
protected override async Task OnInitializedAsync()
{
await Event.On<Object>("malwareScan.status", this, async o => { await InvokeAsync(StateHasChanged); });
await Event.On<Object>("malwareScan.result", this, async o => { await LazyLoaderResults.Reload(); });
}
private Task LoadResults(LazyLoader arg)
{
ScanResults.Clear();
lock (MalwareScanService.ScanResults)
{
foreach (var result in MalwareScanService.ScanResults)
{
ScanResults.Add(result.Key, result.Value);
}
}
return Task.CompletedTask;
}
public async void Dispose()
{
await Event.Off("malwareScan.status", this);
await Event.Off("malwareScan.result", this);
}
}

View File

@@ -10,8 +10,9 @@
@inject NavigationManager NavigationManager
@inject NewsEntryRepository NewsEntryRepository
<OnlyAdmin>
<LazyLoader Load="Load">
@attribute [PermissionRequired(nameof(Permissions.AdminSysNewsEdit))]
<LazyLoader Load="Load">
@if (Entry == null)
{
<div class="alert bg-info d-flex flex-column flex-sm-row w-100 p-5">
@@ -48,8 +49,7 @@
</div>
</div>
}
</LazyLoader>
</OnlyAdmin>
</LazyLoader>
@code
{

View File

@@ -12,18 +12,19 @@
@inject SmartTranslateService SmartTranslateService
@inject AlertService AlertService
<OnlyAdmin>
<AdminSystemNavigation Index="7" />
@attribute [PermissionRequired(nameof(Permissions.AdminSysNews))]
<div class="card card-body mb-6">
<AdminSystemNavigation Index="7"/>
<div class="card card-body mb-6">
<div class="text-end">
<a href="/admin/system/news/new" class="btn btn-success">
<TL>New entry</TL>
</a>
</div>
</div>
</div>
<LazyLoader @ref="LazyLoader" Load="Load">
<LazyLoader @ref="LazyLoader" Load="Load">
@foreach (var entry in Entries)
{
<div class="card mb-6">
@@ -54,8 +55,7 @@
</div>
</div>
}
</LazyLoader>
</OnlyAdmin>
</LazyLoader>
@code
{

View File

@@ -9,8 +9,9 @@
@inject NavigationManager NavigationManager
@inject NewsEntryRepository NewsEntryRepository
<OnlyAdmin>
<div class="card mb-6">
@attribute [PermissionRequired(nameof(Permissions.AdminSysNewsNew))]
<div class="card mb-6">
<div class="card-header">
<h3 class="card-title w-75">
<input type="text" @bind="Model.Title" placeholder="@SmartTranslateService.Translate("Title...")" class="form-control form-control-flush"/>
@@ -29,8 +30,7 @@
WorkingText="@SmartTranslateService.Translate("Saving...")">
</WButton>
</div>
</div>
</OnlyAdmin>
</div>
@code
{

View File

@@ -11,10 +11,11 @@
@inject ConfigService ConfigService
@inject AlertService AlertService
<OnlyAdmin>
<AdminSystemNavigation Index="5"/>
@attribute [PermissionRequired(nameof(Permissions.AdminSysResources))]
<div class="card card-body mb-6">
<AdminSystemNavigation Index="5"/>
<div class="card card-body mb-6">
<div class="text-end">
<WButton CssClasses="btn btn-primary"
Text="@(SmartTranslateService.Translate("Reload config"))"
@@ -22,13 +23,12 @@
OnClick="ReloadConfig">
</WButton>
</div>
</div>
</div>
<div class="card card-body">
<div class="card card-body">
<FileManager Access="@(new HostFileAccess(PathBuilder.Dir("storage")))">
</FileManager>
</div>
</OnlyAdmin>
</div>
@code
{

View File

@@ -3,10 +3,11 @@
@using Moonlight.Shared.Components.Navigations
@using global::Sentry
<OnlyAdmin>
<AdminSystemNavigation Index="1"/>
@attribute [PermissionRequired(nameof(Permissions.AdminSysSentry))]
<div class="row">
<AdminSystemNavigation Index="1"/>
<div class="row">
<div class="col-xxl-6 my-3">
<div class="card">
<div class="card-header">
@@ -28,8 +29,7 @@
</div>
</div>
</div>
</div>
</OnlyAdmin>
</div>
@code
{

View File

@@ -1,19 +1,23 @@
@page "/admin/users/edit/{Id:int}"
@using Moonlight.App.Repositories
@using Moonlight.App.Database.Entities
@using Moonlight.App.Models.Forms
@using Moonlight.App.Models.Misc
@using Moonlight.App.Services
@using Moonlight.App.Services.Interop
@using Moonlight.App.Services.Sessions
@using Mappy.Net
@inject UserRepository UserRepository
@inject Repository<User> UserRepository
@inject Repository<PermissionGroup> PermissionGroupRepository
@inject UserService UserService
@inject SessionServerService SessionServerService
@inject ToastService ToastService
@inject SmartTranslateService SmartTranslateService
<OnlyAdmin>
<LazyLoader Load="Load">
@attribute [PermissionRequired(nameof(Permissions.AdminUserEdit))]
<LazyLoader Load="Load">
@if (User == null)
{
<div class="alert alert-danger">
@@ -32,6 +36,7 @@
</div>
</div>
<SmartForm Model="Model" OnValidSubmit="Update">
<div class="mt-5 row">
<div class="col-xl-6 mb-5 mb-xl-10">
<div class="card card-body p-10">
@@ -39,19 +44,19 @@
<label class="form-label">
<TL>First name</TL>
</label>
<input @bind="User.FirstName" type="text" class="form-control">
<InputText @bind-Value="Model.FirstName" class="form-control"/>
</div>
<div class="mb-10">
<label class="form-label">
<TL>Last name</TL>
</label>
<input @bind="User.LastName" type="text" class="form-control">
<InputText @bind-Value="Model.LastName" class="form-control"/>
</div>
<div class="mb-10">
<label class="form-label">
<TL>Email</TL>
</label>
<input @bind="User.Email" type="email" class="form-control">
<InputText @bind-Value="Model.Email" type="email" class="form-control"/>
</div>
</div>
<div class="mt-5 card card-body p-10">
@@ -86,16 +91,28 @@
</WButton>
</div>
</div>
<div class="mt-5 card card-body p-10">
<div class="input-group">
<SmartDropdown T="PermissionGroup"
@bind-Value="Model.PermissionGroup"
Items="PermissionGroups"
DisplayFunc="@(x => x.Name)"
SearchProp="@(x => x.Name)">
</SmartDropdown>
</div>
</div>
<div class="mt-5 card card-body p-10">
<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("Update"))"
WorkingText="@(SmartTranslateService.Translate("Updating"))"
CssClasses="btn-success"
OnClick="Update">
<WButton Text="@(SmartTranslateService.Translate("Edit permissions"))"
CssClasses="btn-primary me-3"
OnClick="EditPermissions">
</WButton>
<button type="submit" class="btn btn-success">
<TL>Update</TL>
</button>
</div>
</div>
</div>
@@ -105,34 +122,34 @@
<label class="form-label">
<TL>Address</TL>
</label>
<input @bind="User.Address" type="text" class="form-control">
<InputText @bind-Value="Model.Address" class="form-control"/>
</div>
<div class="mb-10">
<label class="form-label">
<TL>City</TL>
</label>
<input @bind="User.City" type="text" class="form-control">
<InputText @bind-Value="Model.City" class="form-control"/>
</div>
<div class="mb-10">
<label class="form-label">
<TL>State</TL>
</label>
<input @bind="User.State" type="text" class="form-control">
<InputText @bind-Value="Model.State" class="form-control"/>
</div>
<div class="mb-10">
<label class="form-label">
<TL>Country</TL>
</label>
<input @bind="User.Country" type="text" class="form-control">
<InputText @bind-Value="Model.Country" class="form-control"/>
</div>
<div class="mb-10">
<input @bind="User.TotpEnabled" type="checkbox" class="form-check-input">
<input @bind="Model.TotpEnabled" type="checkbox" class="form-check-input">
<label class="form-label">
<TL>Totp</TL>
</label>
</div>
<div class="mb-10">
<input @bind="User.Admin" type="checkbox" class="form-check-input">
<input @bind="Model.Admin" type="checkbox" class="form-check-input">
<label class="form-label">
<TL>Admin</TL>
</label>
@@ -143,14 +160,17 @@
<label class="form-label">
<TL>Discord id</TL>
</label>
<input @bind="User.DiscordId" type="number" class="form-control">
<input @bind="Model.DiscordId" type="number" class="form-control">
</div>
</div>
</div>
</div>
</SmartForm>
}
</LazyLoader>
</OnlyAdmin>
<PermissionEditor @ref="PermissionEditor" OnSave="SavePermissions"/>
</LazyLoader>
@code
{
@@ -158,13 +178,25 @@
public int Id { get; set; }
private User? User;
private UserEditDataModel Model { get; set; } = new();
private string NewPassword = "";
private PermissionGroup[] PermissionGroups;
private PermissionEditor PermissionEditor;
private Task Load(LazyLoader arg)
{
User = UserRepository.Get().FirstOrDefault(x => x.Id == Id);
if (User != null)
{
Model = Mapper.Map<UserEditDataModel>(User);
PermissionGroups = PermissionGroupRepository
.Get()
.ToArray();
}
return Task.CompletedTask;
}
@@ -181,6 +213,7 @@
private async Task Update()
{
User = Mapper.Map(User, Model);
UserRepository.Update(User!);
await ToastService.Success(SmartTranslateService.Translate("Successfully updated user"));
@@ -195,4 +228,20 @@
await ToastService.Success(SmartTranslateService.Translate("Successfully updated password"));
}
private async Task EditPermissions()
{
PermissionEditor.InitialData = User!.Permissions;
await PermissionEditor.Launch();
}
private async Task SavePermissions(byte[] data)
{
User!.Permissions = data;
UserRepository.Update(User);
await SessionServerService.ReloadUserSessions(User);
await ToastService.Success(SmartTranslateService.Translate("Successfully updated user"));
}
}

View File

@@ -9,10 +9,11 @@
@inject UserRepository UserRepository
@inject SmartTranslateService SmartTranslateService
<OnlyAdmin>
<AdminSessionNavigation Index="0"/>
@attribute [PermissionRequired(nameof(Permissions.AdminUsers))]
<div class="card">
<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">
@@ -51,8 +52,7 @@
</div>
</div>
</LazyLoader>
</div>
</OnlyAdmin>
</div>
@code
{

View File

@@ -8,8 +8,9 @@
@inject ToastService ToastService
@inject UserService UserService
<OnlyAdmin>
<div class="row mb-5">
@attribute [PermissionRequired(nameof(Permissions.AdminNewUser))]
<div class="row mb-5">
<div class="card card-body p-10">
<label class="form-label">
<TL>First name</TL>
@@ -36,8 +37,8 @@
<input @bind="User.Password" type="password" class="form-control">
</div>
</div>
</div>
<div class="row">
</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">
@@ -50,8 +51,7 @@
</WButton>
</div>
</div>
</div>
</OnlyAdmin>
</div>
@code
{

View File

@@ -13,10 +13,11 @@
@inject AlertService AlertService
@inject ToastService ToastService
<OnlyAdmin>
<AdminSessionNavigation Index="1"/>
@attribute [PermissionRequired(nameof(Permissions.AdminUserSessions))]
<div class="card">
<AdminSessionNavigation Index="1"/>
<div class="card">
<LazyLoader Load="Load">
<div class="card-header border-0 pt-5">
<h3 class="card-title align-items-start flex-column">
@@ -96,8 +97,7 @@
}
</div>
</LazyLoader>
</div>
</OnlyAdmin>
</div>
@code
{

View File

@@ -1,52 +1,124 @@
@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
@using Moonlight.App.Helpers
@using Moonlight.App.Services.Files
@inject UserRepository UserRepository
@inject ServerRepository ServerRepository
@inject DomainRepository DomainRepository
@inject Repository<User> UserRepository
@inject Repository<Server> ServerRepository
@inject Repository<Domain> DomainRepository
@inject Repository<WebSpace> WebSpaceRepository
@inject ResourceService ResourceService
@attribute [PermissionRequired(nameof(Permissions.AdminUserView))]
<OnlyAdmin>
<LazyLoader Load="Load">
@if (User == null)
{
@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">
}
else
{
<div class="d-flex flex-column flex-lg-row">
<div class="flex-column flex-lg-row-auto w-lg-250px w-xl-350px mb-10">
<div class="card mb-5 mb-xl-8">
<div class="card-body">
<div class="d-flex flex-center flex-column py-5">
<div class="symbol symbol-100px symbol-circle mb-7">
<img src="@(ResourceService.Avatar(User))" alt="Avatar">
</div>
<span class="fs-3 text-gray-800 fw-bold mb-3">
@(User.FirstName) @(User.LastName)
</span>
@if (User.Admin)
{
<div class="mb-5">
<div class="badge badge-lg badge-light-primary d-inline">
<TL>Admin</TL>
</div>
</div>
<div class="card card-body mb-5">
<div class="btn-group">
<a class="btn btn-primary" href="/admin/users/edit/@(User.Id)">
}
</div>
<div>
<div class="pb-5 fs-6">
<div class="fw-bold mt-5">
<TL>Account ID</TL>
</div>
<div class="text-gray-600">@(User.Id)</div>
<div class="fw-bold mt-5">Email</div>
<div class="text-gray-600">
@(User.Email)
</div>
<div class="fw-bold mt-5">
<TL>Address</TL>
</div>
<div class="text-gray-600">@(User.Address), <br>@(User.City)<br>@(User.State)<br>@(User.Country)</div>
<div class="fw-bold mt-5">
<TL>Status</TL>
</div>
<div class="text-gray-600">
@(User.Status)
</div>
<div class="fw-bold mt-5">
<TL>TOTP</TL>
</div>
<div class="text-gray-600">
@(User.TotpEnabled)
</div>
<div class="fw-bold mt-5">
<TL>Discord ID</TL>
</div>
<div class="text-gray-600">
@(User.DiscordId)
</div>
<div class="fw-bold mt-5">
<TL>Last Login IP</TL>
</div>
<div class="text-gray-600">
@(User.LastIp) <TL>at</TL> @(Formatter.FormatDate(User.LastVisitedAt))
</div>
<div class="fw-bold mt-5">
<TL>Register IP</TL>
</div>
<div class="text-gray-600">
@(User.RegisterIp) <TL>at</TL> @(Formatter.FormatDate(User.CreatedAt))
</div>
</div>
</div>
</div>
</div>
</div>
<div class="flex-lg-row-fluid ms-lg-15">
<div class="card mb-3">
<div class="card-header border-0">
<span class="card-title"></span>
<div class="card-toolbar">
<a href="/admin/users/edit/@(User.Id)" class="btn btn-primary">
<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">
</div>
<div class="accordion mb-3" id="serversList">
<div class="accordion-item">
<h2 class="accordion-header" id="serversList-header">
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#serversList-body" aria-expanded="false" aria-controls="serversList-body">
<TL>Servers</TL>
</h3>
</div>
<div class="card-body pt-2">
</button>
</h2>
<div id="serversList-body" class="accordion-collapse collapse" aria-labelledby="serversList-header" data-bs-parent="#serversList">
<div class="accordion-body">
@foreach (var server in Servers)
{
<div class="d-flex align-items-center">
@@ -62,13 +134,18 @@ else
}
</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">
</div>
<div class="accordion mb-3" id="domainsList">
<div class="accordion-item">
<h2 class="accordion-header" id="domainsList-header">
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#domainsList-body" aria-expanded="false" aria-controls="domainsList-body">
<TL>Domains</TL>
</button>
</h2>
<div id="domainsList-body" class="accordion-collapse collapse" aria-labelledby="domainsList-header" data-bs-parent="#domainsList">
<div class="accordion-body">
@foreach (var domain in Domains)
{
<div class="d-flex align-items-center">
@@ -85,161 +162,38 @@ else
</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.DiscordId)</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">
</span>
<div class="accordion mb-3" id="webspacesList">
<div class="accordion-item">
<h2 class="accordion-header" id="webspacesList-header">
<button class="accordion-button fs-4 fw-semibold collapsed" type="button" data-bs-toggle="collapse" data-bs-target="#webspacesList-body" aria-expanded="false" aria-controls="webspacesList-body">
<TL>Webspaces</TL>
</button>
</h2>
<div id="webspacesList-body" class="accordion-collapse collapse" aria-labelledby="webspacesList-header" data-bs-parent="#webspacesList">
<div class="accordion-body">
@foreach (var webSpace in WebSpaces)
{
<div class="d-flex align-items-center">
<div class="flex-grow-1">
<a href="/webspace/@(webSpace.Id)" class="fs-6">@(webSpace.Domain) - @(webSpace.CloudPanel.Name)</a>
</div>
</div>
if (webSpace != WebSpaces.Last())
{
<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 class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>Register ip</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">@(User.RegisterIp)</span>
</div>
</div>
<div class="separator my-4"></div>
<div class="row">
<label class="col-lg-4 fw-semibold text-muted">
<TL>Last ip</TL>
</label>
<div class="col-lg-8">
<span class="fw-bold fs-6 text-gray-800">@(User.LastIp)</span>
}
}
</div>
</div>
</div>
</div>
</div>
</div>
}
}
</LazyLoader>
</OnlyAdmin>
@code
{
@@ -249,6 +203,7 @@ else
private User? User;
private Server[] Servers;
private Domain[] Domains;
private WebSpace[] WebSpaces;
private Task Load(LazyLoader arg)
{
@@ -269,6 +224,13 @@ else
.Include(x => x.Owner)
.Where(x => x.Owner.Id == User.Id)
.ToArray();
WebSpaces = WebSpaceRepository
.Get()
.Include(x => x.CloudPanel)
.Include(x => x.Owner)
.Where(x => x.Owner.Id == User.Id)
.ToArray();
}
return Task.CompletedTask;

View File

@@ -11,10 +11,11 @@
@inject Repository<WebSpace> WebSpaceRepository
@inject WebSpaceService WebSpaceService
<OnlyAdmin>
<AdminWebspacesNavigation Index="0"/>
@attribute [PermissionRequired(nameof(Permissions.AdminWebspaces))]
<div class="card">
<AdminWebspacesNavigation Index="0"/>
<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">
@@ -65,8 +66,7 @@
</div>
</LazyLoader>
</div>
</div>
</OnlyAdmin>
</div>
@code
{

View File

@@ -9,8 +9,9 @@
@inject UserRepository UserRepository
@inject NavigationManager NavigationManager
<OnlyAdmin>
<div class="card card-body p-10">
@attribute [PermissionRequired(nameof(Permissions.AdminNewWebspace))]
<div class="card card-body p-10">
<LazyLoader Load="Load">
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
<label class="form-label">
@@ -36,8 +37,7 @@
</div>
</SmartForm>
</LazyLoader>
</div>
</OnlyAdmin>
</div>
@code
{

View File

@@ -8,8 +8,9 @@
@inject Repository<CloudPanel> CloudPanelRepository
@inject NavigationManager NavigationManager
<OnlyAdmin>
<LazyLoader Load="Load">
@attribute [PermissionRequired(nameof(Permissions.AdminWebspacesServerEdit))]
<LazyLoader Load="Load">
@if (CloudPanel == null)
{
<div class="d-flex justify-content-center flex-center">
@@ -63,7 +64,6 @@
</div>
}
</LazyLoader>
</OnlyAdmin>
@code
{

View File

@@ -10,10 +10,11 @@
@inject Repository<CloudPanel> CloudPanelRepository
@inject WebSpaceService WebSpaceService
<OnlyAdmin>
<AdminWebspacesNavigation Index="1"/>
@attribute [PermissionRequired(nameof(Permissions.AdminWebspacesServers))]
<div class="card">
<AdminWebspacesNavigation Index="1"/>
<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">
@@ -78,8 +79,7 @@
</div>
</LazyLoader>
</div>
</div>
</OnlyAdmin>
</div>
@code
{

View File

@@ -7,8 +7,9 @@
@inject Repository<CloudPanel> CloudPanelRepository
@inject NavigationManager NavigationManager
<OnlyAdmin>
<div class="card card-body p-10">
@attribute [PermissionRequired(nameof(Permissions.AdminWebspacesServerNew))]
<div class="card card-body p-10">
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
<label class="form-label">
<TL>Name</TL>
@@ -41,7 +42,6 @@
</div>
</SmartForm>
</div>
</OnlyAdmin>
@code
{

View File

@@ -4,6 +4,7 @@
@using Moonlight.App.Models.Forms
@using Moonlight.App.Repositories.Domains
@using Microsoft.EntityFrameworkCore
@using Moonlight.App.Services.Sessions
@inject SubscriptionService SubscriptionService
@inject DomainService DomainService
@@ -11,6 +12,7 @@
@inject SharedDomainRepository SharedDomainRepository
@inject NavigationManager NavigationManager
@inject SmartTranslateService SmartTranslateService
@inject IdentityService IdentityService
<LazyLoader Load="Load">
@if (!SharedDomains.Any())
@@ -114,8 +116,7 @@
@code
{
[CascadingParameter]
public User User { get; set; }
private SharedDomain[] SharedDomains;
@@ -130,12 +131,12 @@
Model = new();
await lazyLoader.SetText(SmartTranslateService.Translate("Loading your subscription"));
Subscription = await SubscriptionService.GetActiveSubscription(User);
Subscription = await SubscriptionService.GetActiveSubscription(IdentityService.User);
AllowOrder = DomainRepository
.Get()
.Include(x => x.Owner)
.Count(x => x.Owner.Id == User.Id) < (await SubscriptionService.GetLimit(User, "domains")).Amount;
.Count(x => x.Owner.Id == IdentityService.User.Id) < (await SubscriptionService.GetLimit(IdentityService.User, "domains")).Amount;
await lazyLoader.SetText("Loading shared domains");
SharedDomains = SharedDomainRepository.Get().ToArray();
@@ -146,9 +147,9 @@
if (DomainRepository
.Get()
.Include(x => x.Owner)
.Count(x => x.Owner.Id == User.Id) < (await SubscriptionService.GetLimit(User, "domains")).Amount)
.Count(x => x.Owner.Id == IdentityService.User.Id) < (await SubscriptionService.GetLimit(IdentityService.User, "domains")).Amount)
{
var domain = await DomainService.Create(Model.Name, Model.SharedDomain, User);
var domain = await DomainService.Create(Model.Name, Model.SharedDomain, IdentityService.User);
NavigationManager.NavigateTo($"/domain/{domain.Id}");
}

Some files were not shown because too many files have changed in this diff Show More