Merge pull request #229 from Moonlight-Panel/AddPermissionSystem
Added permission system and improved UIs and the IdentityService
This commit is contained in:
commit
6a30db07a7
|
@ -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)
|
||||
{
|
||||
|
|
8
Moonlight/App/Database/Entities/PermissionGroup.cs
Normal file
8
Moonlight/App/Database/Entities/PermissionGroup.cs
Normal 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>();
|
||||
}
|
|
@ -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; }
|
||||
|
|
1109
Moonlight/App/Database/Migrations/20230715095531_AddPermissions.Designer.cs
generated
Normal file
1109
Moonlight/App/Database/Migrations/20230715095531_AddPermissions.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
1139
Moonlight/App/Database/Migrations/20230715214550_AddPermissionGroup.Designer.cs
generated
Normal file
1139
Moonlight/App/Database/Migrations/20230715214550_AddPermissionGroup.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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");
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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 =>
|
||||
|
|
88
Moonlight/App/Helpers/BitHelper.cs
Normal file
88
Moonlight/App/Helpers/BitHelper.cs
Normal 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;
|
||||
}
|
||||
}
|
|
@ -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");
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -54,7 +54,7 @@ public class OAuth2Controller : Controller
|
|||
{
|
||||
try
|
||||
{
|
||||
var currentUser = await IdentityService.Get();
|
||||
var currentUser = IdentityService.User;
|
||||
|
||||
if (currentUser != null)
|
||||
{
|
||||
|
|
34
Moonlight/App/Models/Forms/UserEditDataModel.cs
Normal file
34
Moonlight/App/Models/Forms/UserEditDataModel.cs
Normal 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; }
|
||||
}
|
10
Moonlight/App/Perms/Permission.cs
Normal file
10
Moonlight/App/Perms/Permission.cs
Normal 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;
|
||||
}
|
11
Moonlight/App/Perms/PermissionRequired.cs
Normal file
11
Moonlight/App/Perms/PermissionRequired.cs
Normal 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;
|
||||
}
|
||||
}
|
55
Moonlight/App/Perms/PermissionStorage.cs
Normal file
55
Moonlight/App/Perms/PermissionStorage.cs
Normal 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;
|
||||
}
|
||||
}
|
424
Moonlight/App/Perms/Permissions.cs
Normal file
424
Moonlight/App/Perms/Permissions.cs
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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:
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
return UserCache;
|
||||
ConstructPermissions();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
Ip = HttpContextAccessor.HttpContext.Connection.RemoteIpAddress!.ToString();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public string GetDevice()
|
||||
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;
|
||||
}
|
||||
}
|
|
@ -19,7 +19,7 @@ public class IpBanService
|
|||
|
||||
public Task<bool> IsBanned()
|
||||
{
|
||||
var ip = IdentityService.GetIp();
|
||||
var ip = IdentityService.Ip;
|
||||
|
||||
return Task.FromResult(
|
||||
IpBanRepository
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -33,7 +33,7 @@ public class SupportChatClientService : IDisposable
|
|||
|
||||
public async Task Start()
|
||||
{
|
||||
User = await IdentityService.Get();
|
||||
User = IdentityService.User;
|
||||
|
||||
if (User != null)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
});
|
||||
|
|
|
@ -2,12 +2,19 @@
|
|||
|
||||
<Router AppAssembly="@typeof(BlazorApp).Assembly">
|
||||
<Found Context="routeData">
|
||||
<RouteView RouteData="@routeData" DefaultLayout="@typeof(MainLayout)"/>
|
||||
<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>
|
||||
</Router>
|
||||
|
||||
@code
|
||||
{
|
||||
|
||||
}
|
|
@ -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>
|
||||
|
|
13
Moonlight/Shared/Components/Alerts/NoPermissionAlert.razor
Normal file
13
Moonlight/Shared/Components/Alerts/NoPermissionAlert.razor
Normal 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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:");
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -106,7 +106,7 @@
|
|||
{
|
||||
if (firstRender)
|
||||
{
|
||||
User = await IdentityService.Get();
|
||||
User = IdentityService.User;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
|
86
Moonlight/Shared/Components/Partials/PermissionEditor.razor
Normal file
86
Moonlight/Shared/Components/Partials/PermissionEditor.razor
Normal 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);
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
|
|
|
@ -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,60 +92,21 @@ 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>
|
||||
</a>
|
||||
</div>
|
||||
<div class="menu-item">
|
||||
<a class="menu-link" href="/admin/webspaces">
|
||||
|
@ -228,7 +189,7 @@ else
|
|||
{
|
||||
if (firstRender)
|
||||
{
|
||||
User = await IdentityService.Get();
|
||||
User = IdentityService.User;
|
||||
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
|
@ -42,92 +42,79 @@
|
|||
}
|
||||
|
||||
<GlobalErrorBoundary>
|
||||
<CascadingValue Value="User">
|
||||
<PageTitle>@(string.IsNullOrEmpty(title) ? "Dashboard - " : title)Moonlight</PageTitle>
|
||||
<PageTitle>@(string.IsNullOrEmpty(title) ? "Dashboard - " : title)Moonlight</PageTitle>
|
||||
|
||||
<div class="d-flex flex-column flex-root app-root" id="kt_app_root">
|
||||
<div class="app-page flex-column flex-column-fluid" id="kt_app_page">
|
||||
<canvas id="snow" class="snow-canvas"></canvas>
|
||||
<div class="d-flex flex-column flex-root app-root" id="kt_app_root">
|
||||
<div class="app-page flex-column flex-column-fluid" id="kt_app_page">
|
||||
<canvas id="snow" class="snow-canvas"></canvas>
|
||||
|
||||
@{
|
||||
//TODO: Add a way to disable the snow
|
||||
}
|
||||
@{
|
||||
//TODO: Add a way to disable the snow
|
||||
}
|
||||
|
||||
<PageHeader></PageHeader>
|
||||
<div class="app-wrapper flex-column flex-row-fluid" id="kt_app_wrapper">
|
||||
<Sidebar></Sidebar>
|
||||
<div class="app-main flex-column flex-row-fluid" id="kt_app_main">
|
||||
<div class="d-flex flex-column flex-column-fluid">
|
||||
<div id="kt_app_content" class="app-content flex-column-fluid" style="background-position: center; background-size: cover; background-repeat: no-repeat; background-attachment: fixed; background-image: url('@(DynamicBackgroundService.BackgroundImageUrl)')">
|
||||
<div id="kt_app_content_container" class="app-container container-fluid">
|
||||
<div class="mt-10">
|
||||
<SoftErrorBoundary>
|
||||
@if (!IsIpBanned)
|
||||
<PageHeader></PageHeader>
|
||||
<div class="app-wrapper flex-column flex-row-fluid" id="kt_app_wrapper">
|
||||
<Sidebar></Sidebar>
|
||||
<div class="app-main flex-column flex-row-fluid" id="kt_app_main">
|
||||
<div class="d-flex flex-column flex-column-fluid">
|
||||
<div id="kt_app_content" class="app-content flex-column-fluid" style="background-position: center; background-size: cover; background-repeat: no-repeat; background-attachment: fixed; background-image: url('@(DynamicBackgroundService.BackgroundImageUrl)')">
|
||||
<div id="kt_app_content_container" class="app-container container-fluid">
|
||||
<div class="mt-10">
|
||||
<SoftErrorBoundary>
|
||||
@if (!IsIpBanned)
|
||||
{
|
||||
if (UserProcessed)
|
||||
{
|
||||
if (UserProcessed)
|
||||
if (uri.LocalPath != "/login" &&
|
||||
uri.LocalPath != "/passwordreset" &&
|
||||
uri.LocalPath != "/register")
|
||||
{
|
||||
if (uri.LocalPath != "/login" &&
|
||||
uri.LocalPath != "/passwordreset" &&
|
||||
uri.LocalPath != "/register")
|
||||
if (IdentityService.User == null)
|
||||
{
|
||||
if (User == null)
|
||||
{
|
||||
<Login></Login>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (User.Status == UserStatus.Banned)
|
||||
{
|
||||
<BannedAlert></BannedAlert>
|
||||
}
|
||||
else if (User.Status == UserStatus.Disabled)
|
||||
{
|
||||
<DisabledAlert></DisabledAlert>
|
||||
}
|
||||
else if (User.Status == UserStatus.PasswordPending)
|
||||
{
|
||||
<PasswordChangeView></PasswordChangeView>
|
||||
}
|
||||
else if (User.Status == UserStatus.DataPending)
|
||||
{
|
||||
<UserDataSetView></UserDataSetView>
|
||||
}
|
||||
else
|
||||
{
|
||||
@Body
|
||||
|
||||
<RatingPopup/>
|
||||
}
|
||||
}
|
||||
<Login></Login>
|
||||
}
|
||||
else
|
||||
{
|
||||
if (uri.LocalPath == "/login")
|
||||
if (IdentityService.User.Status == UserStatus.Banned)
|
||||
{
|
||||
<Login></Login>
|
||||
<BannedAlert></BannedAlert>
|
||||
}
|
||||
else if (uri.LocalPath == "/register")
|
||||
else if (IdentityService.User.Status == UserStatus.Disabled)
|
||||
{
|
||||
<Register></Register>
|
||||
<DisabledAlert></DisabledAlert>
|
||||
}
|
||||
else if (uri.LocalPath == "/passwordreset")
|
||||
else if (IdentityService.User.Status == UserStatus.PasswordPending)
|
||||
{
|
||||
<PasswordReset></PasswordReset>
|
||||
<PasswordChangeView></PasswordChangeView>
|
||||
}
|
||||
else if (IdentityService.User.Status == UserStatus.DataPending)
|
||||
{
|
||||
<UserDataSetView></UserDataSetView>
|
||||
}
|
||||
else
|
||||
{
|
||||
<RenderPermissionChecker>
|
||||
@Body
|
||||
</RenderPermissionChecker>
|
||||
|
||||
<RatingPopup/>
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="modal d-block">
|
||||
<div class="modal-dialog modal-dialog-centered mw-900px">
|
||||
<div class="modal-content">
|
||||
<div class="pt-2 modal-body py-lg-10 px-lg-10">
|
||||
<h2>@(SmartTranslateService.Translate("Authenticating"))...</h2>
|
||||
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Verifying token, loading user data"))</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
if (uri.LocalPath == "/login")
|
||||
{
|
||||
<Login></Login>
|
||||
}
|
||||
else if (uri.LocalPath == "/register")
|
||||
{
|
||||
<Register></Register>
|
||||
}
|
||||
else if (uri.LocalPath == "/passwordreset")
|
||||
{
|
||||
<PasswordReset></PasswordReset>
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
|
@ -136,38 +123,50 @@
|
|||
<div class="modal-dialog modal-dialog-centered mw-900px">
|
||||
<div class="modal-content">
|
||||
<div class="pt-2 modal-body py-lg-10 px-lg-10">
|
||||
<h2>@(SmartTranslateService.Translate("Your ip has been banned"))</h2>
|
||||
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Your ip address has been banned by an admin"))</p>
|
||||
<h2>@(SmartTranslateService.Translate("Authenticating"))...</h2>
|
||||
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Verifying token, loading user data"))</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</SoftErrorBoundary>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="modal d-block">
|
||||
<div class="modal-dialog modal-dialog-centered mw-900px">
|
||||
<div class="modal-content">
|
||||
<div class="pt-2 modal-body py-lg-10 px-lg-10">
|
||||
<h2>@(SmartTranslateService.Translate("Your ip has been banned"))</h2>
|
||||
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Your ip address has been banned by an admin"))</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</SoftErrorBoundary>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
<Footer></Footer>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</CascadingValue>
|
||||
</div>
|
||||
</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
|
||||
AddBodyAttribute("data-kt-app-reset-transition", "true");
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,2 +0,0 @@
|
|||
@page "/admin/aapanels"
|
||||
|
|
@ -1,5 +0,0 @@
|
|||
@page "/admin/databases"
|
||||
|
||||
<OnlyAdmin>
|
||||
|
||||
</OnlyAdmin>
|
|
@ -9,59 +9,59 @@
|
|||
@inject DomainService DomainService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<OnlyAdmin>
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<div class="row">
|
||||
<div class="card">
|
||||
<div class="card-header border-0 pt-5">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<TL>Domains</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/domains/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-layer-plus"></i>
|
||||
<TL>New domain</TL>
|
||||
</a>
|
||||
</div>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminDomains))]
|
||||
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<div class="row">
|
||||
<div class="card">
|
||||
<div class="card-header border-0 pt-5">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<TL>Domains</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/domains/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-layer-plus"></i>
|
||||
<TL>New domain</TL>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body pt-0">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="Domain" Items="Domains" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Shared domain"))" Field="@(x => x.SharedDomain)" Sortable="true" Filterable="true" Width="10%">
|
||||
<Template>
|
||||
<span>@(context.SharedDomain.Name)</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true" Width="10%">
|
||||
<Template>
|
||||
<a href="/admin/users/view/@(context.Owner.Id)">@(context.Owner.Email)</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<a href="/domain/@(context.Id)">Manage</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Domain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<DeleteButton Confirm="true"
|
||||
AdditionalCssClasses="float-end"
|
||||
OnClick="() => Delete(context)">
|
||||
</DeleteButton>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body pt-0">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="Domain" Items="Domains" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Shared domain"))" Field="@(x => x.SharedDomain)" Sortable="true" Filterable="true" Width="10%">
|
||||
<Template>
|
||||
<span>@(context.SharedDomain.Name)</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true" Width="10%">
|
||||
<Template>
|
||||
<a href="/admin/users/view/@(context.Owner.Id)">@(context.Owner.Email)</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<a href="/domain/@(context.Id)">Manage</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Domain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<DeleteButton Confirm="true"
|
||||
AdditionalCssClasses="float-end"
|
||||
OnClick="() => Delete(context)">
|
||||
</DeleteButton>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -12,39 +12,39 @@
|
|||
@inject AlertService AlertService
|
||||
@inject ToastService ToastService
|
||||
|
||||
<OnlyAdmin>
|
||||
<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">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<span><TL>Shared domains</TL></span>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/domains/shared/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-layer-plus"></i>
|
||||
<span><TL>Add shared domain</TL></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<Table TableItem="SharedDomain" Items="SharedDomains" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="SharedDomain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="SharedDomain" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="SharedDomain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<DeleteButton Confirm="true"
|
||||
AdditionalCssClasses="float-end"
|
||||
OnClick="() => Delete(context)">
|
||||
</DeleteButton>
|
||||
</Template>
|
||||
</Column>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
@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">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<span><TL>Shared domains</TL></span>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/domains/shared/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-layer-plus"></i>
|
||||
<span><TL>Add shared domain</TL></span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<Table TableItem="SharedDomain" Items="SharedDomains" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="SharedDomain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="SharedDomain" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="SharedDomain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<DeleteButton Confirm="true"
|
||||
AdditionalCssClasses="float-end"
|
||||
OnClick="() => Delete(context)">
|
||||
</DeleteButton>
|
||||
</Template>
|
||||
</Column>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -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">
|
||||
|
|
|
@ -14,118 +14,118 @@
|
|||
@inject DomainRepository DomainRepository
|
||||
@inject ConfigService ConfigService
|
||||
|
||||
<OnlyAdmin>
|
||||
<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">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center gx-0">
|
||||
<div class="col">
|
||||
<h6 class="text-uppercase text-muted mb-2">
|
||||
<TL>Servers</TL>
|
||||
</h6>
|
||||
<span class="h2 mb-0">
|
||||
@(ServerCount)
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<span class="h2 text-muted mb-0">
|
||||
<i class="text-primary bx bx-server bx-lg"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 col-xl">
|
||||
<a class="mt-4 card" href="/admin/webspaces">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center gx-0">
|
||||
<div class="col">
|
||||
<h6 class="text-uppercase text-muted mb-2">
|
||||
<TL>Webspaces</TL>
|
||||
</h6>
|
||||
<span class="h2 mb-0">
|
||||
@(WebSpaceCount)
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<span class="h2 text-muted mb-0">
|
||||
<i class="text-primary bx bx-globe bx-lg"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 col-xl">
|
||||
<a class="mt-4 card" href="/admin/domains">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center gx-0">
|
||||
<div class="col">
|
||||
<h6 class="text-uppercase text-muted mb-2">
|
||||
<TL>Domains</TL>
|
||||
</h6>
|
||||
<span class="h2 mb-0">
|
||||
@(DomainCount)
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<span class="h2 text-muted mb-">
|
||||
<i class="text-primary bx bx-purchase-tag bx-lg"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 col-xl">
|
||||
<a class="mt-4 card" href="/admin/users">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center gx-0">
|
||||
<div class="col">
|
||||
<h6 class="text-uppercase text-muted mb-2">
|
||||
<TL>Users</TL>
|
||||
</h6>
|
||||
<span class="h2 mb-0">
|
||||
@(UserCount)
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<span class="h2 text-muted mb-">
|
||||
<i class="text-primary bx bx-user bx-lg"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminDashboard))]
|
||||
|
||||
<LazyLoader Load="LoadHealthCheckData">
|
||||
@if (HealthCheckData == null)
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<TL>Moonlight health</TL>
|
||||
<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">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center gx-0">
|
||||
<div class="col">
|
||||
<h6 class="text-uppercase text-muted mb-2">
|
||||
<TL>Servers</TL>
|
||||
</h6>
|
||||
<span class="h2 mb-0">
|
||||
@(ServerCount)
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-warning">
|
||||
<TL>Unable to fetch health check data</TL>
|
||||
<div class="col-auto">
|
||||
<span class="h2 text-muted mb-0">
|
||||
<i class="text-primary bx bx-server bx-lg"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<HealthCheckView HealthCheck="@HealthCheckData"/>
|
||||
}
|
||||
</LazyLoader>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 col-xl">
|
||||
<a class="mt-4 card" href="/admin/webspaces">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center gx-0">
|
||||
<div class="col">
|
||||
<h6 class="text-uppercase text-muted mb-2">
|
||||
<TL>Webspaces</TL>
|
||||
</h6>
|
||||
<span class="h2 mb-0">
|
||||
@(WebSpaceCount)
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<span class="h2 text-muted mb-0">
|
||||
<i class="text-primary bx bx-globe bx-lg"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 col-xl">
|
||||
<a class="mt-4 card" href="/admin/domains">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center gx-0">
|
||||
<div class="col">
|
||||
<h6 class="text-uppercase text-muted mb-2">
|
||||
<TL>Domains</TL>
|
||||
</h6>
|
||||
<span class="h2 mb-0">
|
||||
@(DomainCount)
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<span class="h2 text-muted mb-">
|
||||
<i class="text-primary bx bx-purchase-tag bx-lg"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
<div class="col-12 col-lg-6 col-xl">
|
||||
<a class="mt-4 card" href="/admin/users">
|
||||
<div class="card-body">
|
||||
<div class="row align-items-center gx-0">
|
||||
<div class="col">
|
||||
<h6 class="text-uppercase text-muted mb-2">
|
||||
<TL>Users</TL>
|
||||
</h6>
|
||||
<span class="h2 mb-0">
|
||||
@(UserCount)
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-auto">
|
||||
<span class="h2 text-muted mb-">
|
||||
<i class="text-primary bx bx-user bx-lg"></i>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LazyLoader Load="LoadHealthCheckData">
|
||||
@if (HealthCheckData == null)
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<TL>Moonlight health</TL>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="alert alert-warning">
|
||||
<TL>Unable to fetch health check data</TL>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<HealthCheckView HealthCheck="@HealthCheckData"/>
|
||||
}
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -15,61 +15,61 @@
|
|||
@inject SmartTranslateService SmartTranslateService
|
||||
@inject EventSystem Event
|
||||
|
||||
<OnlyAdmin>
|
||||
<AdminNodesNavigation Index="1"/>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminNodeDdos))]
|
||||
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="DdosAttack" Items="DdosAttacks" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
@if (context.Ongoing)
|
||||
{
|
||||
<TL>DDos attack started</TL>
|
||||
}
|
||||
else
|
||||
{
|
||||
<TL>DDos attack stopped</TL>
|
||||
}
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Node"))" Field="@(x => x.Node)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/nodes/view/@(context.Id)">
|
||||
@(context.Node.Name)
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="DdosAttack" Title="Ip" Field="@(x => x.Ip)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
@if (context.Ongoing)
|
||||
{
|
||||
@(context.Data)
|
||||
<TL> packets</TL>
|
||||
}
|
||||
else
|
||||
{
|
||||
@(context.Data)
|
||||
<span> MB</span>
|
||||
}
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Date"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
@(Formatter.FormatDate(context.CreatedAt))
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
<AdminNodesNavigation Index="1"/>
|
||||
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<div class="card">
|
||||
<div class="card-body pt-0">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="DdosAttack" Items="DdosAttacks" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
@if (context.Ongoing)
|
||||
{
|
||||
<TL>DDos attack started</TL>
|
||||
}
|
||||
else
|
||||
{
|
||||
<TL>DDos attack stopped</TL>
|
||||
}
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Node"))" Field="@(x => x.Node)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/nodes/view/@(context.Id)">
|
||||
@(context.Node.Name)
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="DdosAttack" Title="Ip" Field="@(x => x.Ip)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
@if (context.Ongoing)
|
||||
{
|
||||
@(context.Data)
|
||||
<TL> packets</TL>
|
||||
}
|
||||
else
|
||||
{
|
||||
@(context.Data)
|
||||
<span> MB</span>
|
||||
}
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Date"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
@(Formatter.FormatDate(context.CreatedAt))
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
|
@ -99,4 +99,6 @@
|
|||
{
|
||||
await Event.Off("node.ddos", this);
|
||||
}
|
||||
|
||||
//TODO: Move to security
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -13,89 +13,89 @@
|
|||
@inject NodeService NodeService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<OnlyAdmin>
|
||||
<AdminNodesNavigation Index="0"/>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminNodes))]
|
||||
|
||||
<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">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<TL>Nodes</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/nodes/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-layer-plus"></i>
|
||||
<TL>New node</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body pt-0">
|
||||
@if (Nodes.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="Node" Items="Nodes" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Id)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
@{
|
||||
var ss = StatusCache.ContainsKey(context) ? StatusCache[context] : null;
|
||||
}
|
||||
<AdminServersNavigation Index="3" />
|
||||
|
||||
@if (ss == null)
|
||||
{
|
||||
<span class="text-danger">Offline</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-success">Online (@(ss.Version))</span>
|
||||
}
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<a href="/admin/nodes/view/@(context.Id)">@(context.Name)</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Fqdn"))" Field="@(x => x.Fqdn)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Node" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/nodes/edit/@(context.Id)">
|
||||
@(SmartTranslateService.Translate("Edit"))
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Node" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/nodes/setup/@(context.Id)">
|
||||
@(SmartTranslateService.Translate("Setup"))
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Node" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
||||
CssClasses="btn-sm btn-danger"
|
||||
OnClick="() => Delete(context)">
|
||||
</WButton>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<TL>No nodes found</TL>
|
||||
</div>
|
||||
}
|
||||
<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">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<TL>Nodes</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/nodes/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-layer-plus"></i>
|
||||
<TL>New node</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
<div class="card-body pt-0">
|
||||
@if (Nodes.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="Node" Items="Nodes" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Id)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
@{
|
||||
var ss = StatusCache.ContainsKey(context) ? StatusCache[context] : null;
|
||||
}
|
||||
|
||||
@if (ss == null)
|
||||
{
|
||||
<span class="text-danger">Offline</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-success">Online (@(ss.Version))</span>
|
||||
}
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<a href="/admin/nodes/view/@(context.Id)">@(context.Name)</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Fqdn"))" Field="@(x => x.Fqdn)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Node" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/nodes/edit/@(context.Id)">
|
||||
@(SmartTranslateService.Translate("Edit"))
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Node" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/nodes/setup/@(context.Id)">
|
||||
@(SmartTranslateService.Translate("Setup"))
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Node" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
||||
CssClasses="btn-sm btn-danger"
|
||||
OnClick="() => Delete(context)">
|
||||
</WButton>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<TL>No nodes found</TL>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
7
Moonlight/Shared/Views/Admin/Security/Index.razor
Normal file
7
Moonlight/Shared/Views/Admin/Security/Index.razor
Normal file
|
@ -0,0 +1,7 @@
|
|||
@page "/admin/security"
|
||||
|
||||
@using Moonlight.Shared.Components.Navigations
|
||||
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminSecurity))]
|
||||
|
||||
<AdminSecurityNavigation Index="0" />
|
|
@ -1,4 +1,4 @@
|
|||
@page "/admin/system/security"
|
||||
@page "/admin/security/ipbans"
|
||||
|
||||
@using Moonlight.Shared.Components.Navigations
|
||||
@using BlazorTable
|
||||
|
@ -13,34 +13,36 @@
|
|||
@inject EventSystem Event
|
||||
@inject ToastService ToastService
|
||||
|
||||
<OnlyAdmin>
|
||||
<AdminSystemNavigation Index="3"/>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminSecurityIpBans))]
|
||||
|
||||
<div class="card mb-5">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<TL>Ip Bans</TL>
|
||||
</div>
|
||||
<div class="card-toolbar">
|
||||
<table class="w-100">
|
||||
<tr>
|
||||
<td class="w-100">
|
||||
<input @bind="Ip" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter a ip"))"/>
|
||||
</td>
|
||||
<td>
|
||||
<WButton OnClick="AddIpBan"
|
||||
CssClasses="btn btn-primary ms-2"
|
||||
Text="@(SmartTranslateService.Translate("Add"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Adding"))">
|
||||
</WButton>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
<AdminSecurityNavigation Index="2"/>
|
||||
|
||||
<div class="card mb-5">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<TL>Ip Bans</TL>
|
||||
</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="card-toolbar">
|
||||
<table class="w-100">
|
||||
<tr>
|
||||
<td class="w-100">
|
||||
<input @bind="Ip" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter a ip"))"/>
|
||||
</td>
|
||||
<td>
|
||||
<WButton OnClick="AddIpBan"
|
||||
CssClasses="btn btn-primary ms-2"
|
||||
Text="@(SmartTranslateService.Translate("Add"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Adding"))">
|
||||
</WButton>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<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>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</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;
|
||||
}
|
||||
|
@ -83,7 +85,7 @@
|
|||
|
||||
Ip = "";
|
||||
await InvokeAsync(StateHasChanged);
|
||||
|
||||
|
||||
await Event.Emit("ipBan.update");
|
||||
|
||||
await ToastService.Success(
|
134
Moonlight/Shared/Views/Admin/Security/Malware.razor
Normal file
134
Moonlight/Shared/Views/Admin/Security/Malware.razor
Normal 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);
|
||||
}
|
||||
}
|
132
Moonlight/Shared/Views/Admin/Security/PermissionGroups.razor
Normal file
132
Moonlight/Shared/Views/Admin/Security/PermissionGroups.razor
Normal 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();
|
||||
}
|
||||
}
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -13,165 +13,165 @@
|
|||
@inject ImageRepository ImageRepository
|
||||
@inject Repository<User> UserRepository
|
||||
|
||||
<OnlyAdmin>
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
@if (Server == null)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<TL>No server with this id found</TL>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Identifier</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-id-card"></i>
|
||||
</span>
|
||||
<input type="number" class="form-control disabled" disabled="" value="@(Server.Id)">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>UuidIdentifier</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-id-card"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control disabled" disabled="" value="@(Server.Uuid)">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Server name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-purchase-tag-alt"></i>
|
||||
</span>
|
||||
<InputText @bind-Value="Model.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Server name"))"></InputText>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Owner</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<SmartDropdown T="User"
|
||||
@bind-Value="Model.Owner"
|
||||
Items="Users"
|
||||
DisplayFunc="@(x => x.Email)"
|
||||
SearchProp="@(x => x.Email)">
|
||||
</SmartDropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Cpu cores</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-chip"></i>
|
||||
</span>
|
||||
<InputNumber @bind-Value="Model.Cpu" type="number" class="form-control"></InputNumber>
|
||||
<span class="input-group-text">
|
||||
<TL>CPU Cores (100% = 1 Core)</TL>
|
||||
</span>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Memory</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-microchip"></i>
|
||||
</span>
|
||||
<InputNumber @bind-Value="Model.Memory" type="number" class="form-control"></InputNumber>
|
||||
<span class="input-group-text">
|
||||
MB
|
||||
</span>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Disk</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-hdd"></i>
|
||||
</span>
|
||||
<InputNumber @bind-Value="Model.Disk" type="number" class="form-control"></InputNumber>
|
||||
<span class="input-group-text">
|
||||
MB
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Override startup command</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-terminal"></i>
|
||||
</span>
|
||||
<InputText @bind-Value="Model.OverrideStartup" type="text" class="form-control" placeholder="@(Server.Image.Startup)"></InputText>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Docker image</TL>
|
||||
</label>
|
||||
<select @bind="Model.DockerImageIndex" class="form-select">
|
||||
@foreach (var image in DockerImages)
|
||||
{
|
||||
<option value="@(DockerImages.IndexOf(image))">@(image.Name)</option>
|
||||
}
|
||||
</select>
|
||||
<label class="form-label">
|
||||
<TL>Cleanup exception</TL>
|
||||
</label>
|
||||
<input @bind="Model.IsCleanupException" class="form-check" type="checkbox"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
@foreach (var vars in Server.Variables.Chunk(4))
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminServerEdit))]
|
||||
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
@if (Server == null)
|
||||
{
|
||||
<div class="row mb-3">
|
||||
@foreach (var variable in vars)
|
||||
{
|
||||
<div class="col">
|
||||
<div class="card card-body">
|
||||
<label class="form-label">
|
||||
<TL>Name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Key" type="text" class="form-control disabled" disabled="">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Value</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Value" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="alert alert-danger">
|
||||
<TL>No server with this id found</TL>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="card card-body">
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="/admin/servers/images" class="btn btn-danger me-3">
|
||||
<TL>Cancel</TL>
|
||||
</a>
|
||||
<button class="btn btn-success" type="submit"><TL>Save</TL></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SmartForm>
|
||||
}
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
else
|
||||
{
|
||||
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Identifier</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-id-card"></i>
|
||||
</span>
|
||||
<input type="number" class="form-control disabled" disabled="" value="@(Server.Id)">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>UuidIdentifier</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-id-card"></i>
|
||||
</span>
|
||||
<input type="text" class="form-control disabled" disabled="" value="@(Server.Uuid)">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Server name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-purchase-tag-alt"></i>
|
||||
</span>
|
||||
<InputText @bind-Value="Model.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Server name"))"></InputText>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Owner</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<SmartDropdown T="User"
|
||||
@bind-Value="Model.Owner"
|
||||
Items="Users"
|
||||
DisplayFunc="@(x => x.Email)"
|
||||
SearchProp="@(x => x.Email)">
|
||||
</SmartDropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Cpu cores</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-chip"></i>
|
||||
</span>
|
||||
<InputNumber @bind-Value="Model.Cpu" type="number" class="form-control"></InputNumber>
|
||||
<span class="input-group-text">
|
||||
<TL>CPU Cores (100% = 1 Core)</TL>
|
||||
</span>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Memory</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-microchip"></i>
|
||||
</span>
|
||||
<InputNumber @bind-Value="Model.Memory" type="number" class="form-control"></InputNumber>
|
||||
<span class="input-group-text">
|
||||
MB
|
||||
</span>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Disk</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-hdd"></i>
|
||||
</span>
|
||||
<InputNumber @bind-Value="Model.Disk" type="number" class="form-control"></InputNumber>
|
||||
<span class="input-group-text">
|
||||
MB
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Override startup command</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-terminal"></i>
|
||||
</span>
|
||||
<InputText @bind-Value="Model.OverrideStartup" type="text" class="form-control" placeholder="@(Server.Image.Startup)"></InputText>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Docker image</TL>
|
||||
</label>
|
||||
<select @bind="Model.DockerImageIndex" class="form-select">
|
||||
@foreach (var image in DockerImages)
|
||||
{
|
||||
<option value="@(DockerImages.IndexOf(image))">@(image.Name)</option>
|
||||
}
|
||||
</select>
|
||||
<label class="form-label">
|
||||
<TL>Cleanup exception</TL>
|
||||
</label>
|
||||
<input @bind="Model.IsCleanupException" class="form-check" type="checkbox"/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
@foreach (var vars in Server.Variables.Chunk(4))
|
||||
{
|
||||
<div class="row mb-3">
|
||||
@foreach (var variable in vars)
|
||||
{
|
||||
<div class="col">
|
||||
<div class="card card-body">
|
||||
<label class="form-label">
|
||||
<TL>Name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Key" type="text" class="form-control disabled" disabled="">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Value</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Value" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="card card-body">
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="/admin/servers/images" class="btn btn-danger me-3">
|
||||
<TL>Cancel</TL>
|
||||
</a>
|
||||
<button class="btn btn-success" type="submit"><TL>Save</TL></button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SmartForm>
|
||||
}
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -12,252 +12,252 @@
|
|||
@inject ToastService ToastService
|
||||
@inject FileDownloadService FileDownloadService
|
||||
|
||||
<OnlyAdmin>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminServerImageEdit))]
|
||||
|
||||
<div class="row">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
@if (Image == null)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<TL>No image with this id found</TL>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="card card-body">
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Name</TL>
|
||||
</label>
|
||||
<input @bind="Image.Name" type="text" class="form-control">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
@if (Image == null)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<TL>No image with this id found</TL>
|
||||
</div>
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Description</TL>
|
||||
</label>
|
||||
<textarea @bind="Image.Description" type="text" class="form-control"></textarea>
|
||||
</div>
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Background image url</TL>
|
||||
</label>
|
||||
<input
|
||||
@bind="Image.BackgroundImageUrl"
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="@(SmartTranslateService.Translate("Leave empty for the default background image"))">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="card card-body">
|
||||
<label class="form-label">
|
||||
<TL>Tags</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="AddTagName" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter tag name"))">
|
||||
<button @onclick="AddTag" class="btn btn-primary">
|
||||
<TL>Add</TL>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
@if (Tags.Any())
|
||||
{
|
||||
<div class="row">
|
||||
@foreach (var tag in Tags)
|
||||
{
|
||||
<button @onclick="() => RemoveTag(tag)" class="col m-3 btn btn-outline-primary mw-25">
|
||||
@(tag)
|
||||
</button>
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="card card-body">
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Name</TL>
|
||||
</label>
|
||||
<input @bind="Image.Name" type="text" class="form-control">
|
||||
</div>
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Description</TL>
|
||||
</label>
|
||||
<textarea @bind="Image.Description" type="text" class="form-control"></textarea>
|
||||
</div>
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Background image url</TL>
|
||||
</label>
|
||||
<input
|
||||
@bind="Image.BackgroundImageUrl"
|
||||
type="text"
|
||||
class="form-control"
|
||||
placeholder="@(SmartTranslateService.Translate("Leave empty for the default background image"))">
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-primary">
|
||||
<TL>No tags found</TL>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="card card-body">
|
||||
<label class="form-label">
|
||||
<TL>Docker images</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="NewDockerImage.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter docker image name"))">
|
||||
<button @onclick="AddDockerImage" class="btn btn-primary">
|
||||
<TL>Add</TL>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
@if (Image.DockerImages.Any())
|
||||
{
|
||||
<div class="row">
|
||||
@foreach (var imageDocker in Image.DockerImages)
|
||||
{
|
||||
<button @onclick="() => RemoveDockerImage(imageDocker)" class="col m-3 btn btn-outline-primary mw-25">
|
||||
@(imageDocker.Name)
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-primary">
|
||||
<TL>No docker images found</TL>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="card card-body">
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Default image</TL>
|
||||
</label>
|
||||
<select @bind="DefaultImageIndex" class="form-select">
|
||||
@foreach (var image in Image.DockerImages)
|
||||
{
|
||||
<option value="@(image.Id)">@(image.Name)</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Allocations</TL>
|
||||
</label>
|
||||
<input @bind="Image.Allocations" type="number" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mx-0">
|
||||
<div class="card card-body">
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Startup command</TL>
|
||||
</label>
|
||||
<input @bind="Image.Startup" type="text" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Install container</TL>
|
||||
</label>
|
||||
<input @bind="Image.InstallDockerImage" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Install entry</TL>
|
||||
</label>
|
||||
<input @bind="Image.InstallEntrypoint" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card card-flush">
|
||||
<FileEditor @ref="Editor" Language="shell" InitialData="@(Image.InstallScript)" HideControls="true"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-8">
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="card card-body">
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Configuration files</TL>
|
||||
</label>
|
||||
<textarea @bind="Image.ConfigFiles" class="form-control"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="card card-body">
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Startup detection</TL>
|
||||
</label>
|
||||
<input @bind="Image.StartupDetection" type="text" class="form-control">
|
||||
</div>
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Stop command</TL>
|
||||
</label>
|
||||
<input @bind="Image.StopCommand" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-6">
|
||||
<div class="card card-body">
|
||||
<div class="input-group mb-5">
|
||||
<input type="text" @bind="ImageVariable.Key" placeholder="@(SmartTranslateService.Translate("Key"))" class="form-control">
|
||||
<input type="text" @bind="ImageVariable.DefaultValue" placeholder="@(SmartTranslateService.Translate("Default value"))" class="form-control">
|
||||
<button @onclick="AddVariable" class="btn btn-primary">
|
||||
<TL>Add</TL>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@if (Image!.Variables.Any())
|
||||
{
|
||||
<div class="row">
|
||||
@foreach (var variable in Image!.Variables)
|
||||
{
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" @bind="variable.Key" placeholder="@(SmartTranslateService.Translate("Key"))" class="form-control">
|
||||
<input type="text" @bind="variable.DefaultValue" placeholder="@(SmartTranslateService.Translate("Default value"))" class="form-control">
|
||||
<button @onclick="() => RemoveVariable(variable)" class="btn btn-danger">
|
||||
<TL>Remove</TL>
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="card card-body">
|
||||
<label class="form-label">
|
||||
<TL>Tags</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="AddTagName" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter tag name"))">
|
||||
<button @onclick="AddTag" class="btn btn-primary">
|
||||
<TL>Add</TL>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
<div>
|
||||
@if (Tags.Any())
|
||||
{
|
||||
<div class="row">
|
||||
@foreach (var tag in Tags)
|
||||
{
|
||||
<button @onclick="() => RemoveTag(tag)" class="col m-3 btn btn-outline-primary mw-25">
|
||||
@(tag)
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-primary">
|
||||
<TL>No tags found</TL>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-primary">
|
||||
<TL>No variables found</TL>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="card card-body">
|
||||
<label class="form-label">
|
||||
<TL>Docker images</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="NewDockerImage.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Enter docker image name"))">
|
||||
<button @onclick="AddDockerImage" class="btn btn-primary">
|
||||
<TL>Add</TL>
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
@if (Image.DockerImages.Any())
|
||||
{
|
||||
<div class="row">
|
||||
@foreach (var imageDocker in Image.DockerImages)
|
||||
{
|
||||
<button @onclick="() => RemoveDockerImage(imageDocker)" class="col m-3 btn btn-outline-primary mw-25">
|
||||
@(imageDocker.Name)
|
||||
</button>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-primary">
|
||||
<TL>No docker images found</TL>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="card card-body">
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Default image</TL>
|
||||
</label>
|
||||
<select @bind="DefaultImageIndex" class="form-select">
|
||||
@foreach (var image in Image.DockerImages)
|
||||
{
|
||||
<option value="@(image.Id)">@(image.Name)</option>
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Allocations</TL>
|
||||
</label>
|
||||
<input @bind="Image.Allocations" type="number" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mx-0">
|
||||
<div class="card card-body">
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Startup command</TL>
|
||||
</label>
|
||||
<input @bind="Image.Startup" type="text" class="form-control">
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Install container</TL>
|
||||
</label>
|
||||
<input @bind="Image.InstallDockerImage" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Install entry</TL>
|
||||
</label>
|
||||
<input @bind="Image.InstallEntrypoint" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="card card-flush">
|
||||
<FileEditor @ref="Editor" Language="shell" InitialData="@(Image.InstallScript)" HideControls="true"/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-8">
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="card card-body">
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Configuration files</TL>
|
||||
</label>
|
||||
<textarea @bind="Image.ConfigFiles" class="form-control"></textarea>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xl-6 mb-5 mb-xl-10">
|
||||
<div class="card card-body">
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Startup detection</TL>
|
||||
</label>
|
||||
<input @bind="Image.StartupDetection" type="text" class="form-control">
|
||||
</div>
|
||||
<div class="mb-10">
|
||||
<label class="form-label">
|
||||
<TL>Stop command</TL>
|
||||
</label>
|
||||
<input @bind="Image.StopCommand" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row my-6">
|
||||
<div class="card card-body">
|
||||
<div class="input-group mb-5">
|
||||
<input type="text" @bind="ImageVariable.Key" placeholder="@(SmartTranslateService.Translate("Key"))" class="form-control">
|
||||
<input type="text" @bind="ImageVariable.DefaultValue" placeholder="@(SmartTranslateService.Translate("Default value"))" class="form-control">
|
||||
<button @onclick="AddVariable" class="btn btn-primary">
|
||||
<TL>Add</TL>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@if (Image!.Variables.Any())
|
||||
{
|
||||
<div class="row">
|
||||
@foreach (var variable in Image!.Variables)
|
||||
{
|
||||
<div class="input-group mb-3">
|
||||
<input type="text" @bind="variable.Key" placeholder="@(SmartTranslateService.Translate("Key"))" class="form-control">
|
||||
<input type="text" @bind="variable.DefaultValue" placeholder="@(SmartTranslateService.Translate("Default value"))" class="form-control">
|
||||
<button @onclick="() => RemoveVariable(variable)" class="btn btn-danger">
|
||||
<TL>Remove</TL>
|
||||
</button>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-primary">
|
||||
<TL>No variables found</TL>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="card card-body">
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="/admin/servers/images" class="btn btn-danger me-3">
|
||||
<TL>Cancel</TL>
|
||||
</a>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Export"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Exporting"))"
|
||||
CssClasses="btn-primary me-3"
|
||||
OnClick="Export">
|
||||
</WButton>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Save"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Saving"))"
|
||||
CssClasses="btn-success"
|
||||
OnClick="Save">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="card card-body">
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="/admin/servers/images" class="btn btn-danger me-3">
|
||||
<TL>Cancel</TL>
|
||||
</a>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Export"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Exporting"))"
|
||||
CssClasses="btn-primary me-3"
|
||||
OnClick="Export">
|
||||
</WButton>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Save"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Saving"))"
|
||||
CssClasses="btn-success"
|
||||
OnClick="Save">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -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,95 +17,95 @@
|
|||
@inject SmartTranslateService SmartTranslateService
|
||||
@inject AlertService AlertService
|
||||
|
||||
<OnlyAdmin>
|
||||
<div class="row">
|
||||
<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">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<TL>Images</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/servers/images/new" class="btn btn-sm btn-light-success me-3">
|
||||
<i class="bx bx-layer-plus"></i>
|
||||
<TL>New image</TL>
|
||||
</a>
|
||||
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden="" multiple=""/>
|
||||
<label for="fileUpload" class="btn btn-sm btn-light-primary me-3">
|
||||
<span class="svg-icon svg-icon-2">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
|
||||
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<TL>Import</TL>
|
||||
</label>
|
||||
<InputFile OnChange="OnEggFileChanged" type="file" id="eggFileUpload" hidden="" multiple=""/>
|
||||
<label for="eggFileUpload" class="btn btn-sm btn-light-primary">
|
||||
<span class="svg-icon svg-icon-2">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
|
||||
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<TL>Import pterodactyl egg</TL>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body pt-0">
|
||||
@if (Images.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="Image" Items="Images" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Description"))" Field="@(x => x.Description)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Uuid"))" Field="@(x => x.Uuid)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Servers with this image"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
@{
|
||||
var i = ServersCount.TryGetValue(context, out var value) ? value.ToString() : "N/A";
|
||||
}
|
||||
<span>
|
||||
@(i)
|
||||
@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">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<TL>Images</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/servers/images/new" class="btn btn-sm btn-light-success me-3">
|
||||
<i class="bx bx-layer-plus"></i>
|
||||
<TL>New image</TL>
|
||||
</a>
|
||||
<InputFile OnChange="OnFileChanged" type="file" id="fileUpload" hidden="" multiple=""/>
|
||||
<label for="fileUpload" class="btn btn-sm btn-light-primary me-3">
|
||||
<span class="svg-icon svg-icon-2">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
|
||||
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Image" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/servers/images/edit/@(context.Id)">
|
||||
@(SmartTranslateService.Translate("Edit"))
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Image" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
||||
CssClasses="btn-danger"
|
||||
OnClick="() => Delete(context)">
|
||||
</WButton>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
<TL>Import</TL>
|
||||
</label>
|
||||
<InputFile OnChange="OnEggFileChanged" type="file" id="eggFileUpload" hidden="" multiple=""/>
|
||||
<label for="eggFileUpload" class="btn btn-sm btn-light-primary">
|
||||
<span class="svg-icon svg-icon-2">
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path opacity="0.3" d="M10 4H21C21.6 4 22 4.4 22 5V7H10V4Z" fill="currentColor"></path>
|
||||
<path d="M10.4 3.60001L12 6H21C21.6 6 22 6.4 22 7V19C22 19.6 21.6 20 21 20H3C2.4 20 2 19.6 2 19V4C2 3.4 2.4 3 3 3H9.20001C9.70001 3 10.2 3.20001 10.4 3.60001ZM16 11.6L12.7 8.29999C12.3 7.89999 11.7 7.89999 11.3 8.29999L8 11.6H11V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H16Z" fill="currentColor"></path>
|
||||
<path opacity="0.3" d="M11 11.6V17C11 17.6 11.4 18 12 18C12.6 18 13 17.6 13 17V11.6H11Z" fill="currentColor"></path>
|
||||
</svg>
|
||||
</span>
|
||||
<TL>Import pterodactyl egg</TL>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body pt-0">
|
||||
@if (Images.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="Image" Items="Images" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Description"))" Field="@(x => x.Description)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Uuid"))" Field="@(x => x.Uuid)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Image" Title="@(SmartTranslateService.Translate("Servers with this image"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
@{
|
||||
var i = ServersCount.TryGetValue(context, out var value) ? value.ToString() : "N/A";
|
||||
}
|
||||
<span>
|
||||
@(i)
|
||||
</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Image" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/servers/images/edit/@(context.Id)">
|
||||
@(SmartTranslateService.Translate("Edit"))
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Image" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Delete"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Deleting"))"
|
||||
CssClasses="btn-danger"
|
||||
OnClick="() => Delete(context)">
|
||||
</WButton>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<TL>No images found</TL>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<TL>No images found</TL>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -4,66 +4,69 @@
|
|||
@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">
|
||||
<div class="card">
|
||||
<div class="card-header border-0 pt-5">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold fs-3 mb-1"><TL>Servers</TL></span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/servers/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-layer-plus"></i>
|
||||
<TL>New server</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body pt-0">
|
||||
@if (Servers.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="Server" Items="Servers" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<a href="/server/@(context.Uuid)">@(context.Name)</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Cores"))" Field="@(x => x.Cpu)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Memory"))" Field="@(x => x.Memory)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Disk"))" Field="@(x => x.Disk)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<a href="/admin/users/view/@(context.Owner.Id)/">@context.Owner.Email</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Server" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/servers/view/@(context.Id)">
|
||||
@(SmartTranslateService.Translate("Manage"))
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<TL>No servers found</TL>
|
||||
</div>
|
||||
}
|
||||
@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>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/servers/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-layer-plus"></i>
|
||||
<TL>New server</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
<div class="card-body pt-0">
|
||||
@if (Servers.Any())
|
||||
{
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="Server" Items="Servers" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<a href="/server/@(context.Uuid)">@(context.Name)</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Cores"))" Field="@(x => x.Cpu)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Memory"))" Field="@(x => x.Memory)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Disk"))" Field="@(x => x.Disk)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Server" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<a href="/admin/users/view/@(context.Owner.Id)/">@context.Owner.Email</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Server" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/servers/view/@(context.Id)">
|
||||
@(SmartTranslateService.Translate("Manage"))
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<TL>No servers found</TL>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
|
||||
@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;
|
||||
|
|
|
@ -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>: <TL>Currently scanning</TL> @(Node?.Name)</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span><TL>Status</TL>: <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
|
||||
{
|
||||
|
|
|
@ -17,163 +17,163 @@
|
|||
@inject NavigationManager NavigationManager
|
||||
@inject UserRepository UserRepository
|
||||
|
||||
<OnlyAdmin>
|
||||
<LazyLoader Load="Load">
|
||||
<SmartForm Model="Model" OnValidSubmit="Create">
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Server name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-purchase-tag-alt"></i>
|
||||
</span>
|
||||
<InputText @bind-Value="Model.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Server name"))"></InputText>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Owner</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<SmartDropdown
|
||||
@bind-Value="Model.Owner"
|
||||
Items="Users"
|
||||
DisplayFunc="@(x => x.Email)"
|
||||
SearchProp="@(x => x.Email)">
|
||||
</SmartDropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Cpu cores</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-chip"></i>
|
||||
</span>
|
||||
<InputNumber @bind-Value="Model.Cpu" class="form-control"></InputNumber>
|
||||
<span class="input-group-text">
|
||||
<TL>CPU Cores (100% = 1 Core)</TL>
|
||||
</span>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Memory</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-microchip"></i>
|
||||
</span>
|
||||
<InputNumber @bind-Value="Model.Memory" class="form-control"></InputNumber>
|
||||
<span class="input-group-text">
|
||||
MB
|
||||
</span>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Disk</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-hdd"></i>
|
||||
</span>
|
||||
<InputNumber @bind-Value="Model.Disk" class="form-control"></InputNumber>
|
||||
<span class="input-group-text">
|
||||
MB
|
||||
</span>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Node</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<SmartSelect @bind-Value="Model.Node"
|
||||
Items="Nodes"
|
||||
DisplayField="@(x => x.Name)">
|
||||
</SmartSelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Image</TL>
|
||||
</label>
|
||||
<div class="mb-5">
|
||||
<SmartSelect TField="Image" @bind-Value="Model.Image" Items="Images" DisplayField="@(x => x.Name)" OnChange="OnChange"></SmartSelect>
|
||||
</div>
|
||||
@if (Model.Image != null)
|
||||
{
|
||||
<label class="form-label">
|
||||
<TL>Override startup</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-terminal"></i>
|
||||
</span>
|
||||
<InputText @bind-Value="Model.OverrideStartup" type="text" class="form-control" placeholder="@(Model.Image.Startup)"></InputText>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Docker image</TL>
|
||||
</label>
|
||||
<InputSelect TValue="int" @bind-Value="Model.DockerImageIndex" class="form-control">
|
||||
@foreach (var image in Model.Image.DockerImages)
|
||||
{
|
||||
<option value="@(Model.Image.DockerImages.IndexOf(image))">@(image.Name)</option>
|
||||
}
|
||||
</InputSelect>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminNewServer))]
|
||||
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body">
|
||||
@if (Model.Image != null)
|
||||
{
|
||||
<div class="mt-9 row d-flex">
|
||||
@foreach (var vars in ServerVariables.Chunk(3))
|
||||
{
|
||||
<div class="row row-cols-3 mb-3">
|
||||
@foreach (var variable in vars)
|
||||
{
|
||||
<div class="col">
|
||||
<div class="card card-body border">
|
||||
<label class="form-label">
|
||||
<TL>Name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Key" type="text" class="form-control disabled" disabled="">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Value</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Value" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
<LazyLoader Load="Load">
|
||||
<SmartForm Model="Model" OnValidSubmit="Create">
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Server name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-purchase-tag-alt"></i>
|
||||
</span>
|
||||
<InputText @bind-Value="Model.Name" type="text" class="form-control" placeholder="@(SmartTranslateService.Translate("Server name"))"></InputText>
|
||||
</div>
|
||||
}
|
||||
<label class="form-label">
|
||||
<TL>Owner</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<SmartDropdown
|
||||
@bind-Value="Model.Owner"
|
||||
Items="Users"
|
||||
DisplayFunc="@(x => x.Email)"
|
||||
SearchProp="@(x => x.Email)">
|
||||
</SmartDropdown>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="card card-body">
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="/admin/servers" class="btn btn-danger me-3">
|
||||
<TL>Cancel</TL>
|
||||
</a>
|
||||
<button class="btn btn-success" type="submit">
|
||||
<TL>Create</TL>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SmartForm>
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Cpu cores</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-chip"></i>
|
||||
</span>
|
||||
<InputNumber @bind-Value="Model.Cpu" class="form-control"></InputNumber>
|
||||
<span class="input-group-text">
|
||||
<TL>CPU Cores (100% = 1 Core)</TL>
|
||||
</span>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Memory</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-microchip"></i>
|
||||
</span>
|
||||
<InputNumber @bind-Value="Model.Memory" class="form-control"></InputNumber>
|
||||
<span class="input-group-text">
|
||||
MB
|
||||
</span>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Disk</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-hdd"></i>
|
||||
</span>
|
||||
<InputNumber @bind-Value="Model.Disk" class="form-control"></InputNumber>
|
||||
<span class="input-group-text">
|
||||
MB
|
||||
</span>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Node</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<SmartSelect @bind-Value="Model.Node"
|
||||
Items="Nodes"
|
||||
DisplayField="@(x => x.Name)">
|
||||
</SmartSelect>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>Image</TL>
|
||||
</label>
|
||||
<div class="mb-5">
|
||||
<SmartSelect TField="Image" @bind-Value="Model.Image" Items="Images" DisplayField="@(x => x.Name)" OnChange="OnChange"></SmartSelect>
|
||||
</div>
|
||||
@if (Model.Image != null)
|
||||
{
|
||||
<label class="form-label">
|
||||
<TL>Override startup</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<span class="input-group-text">
|
||||
<i class="bx bx-terminal"></i>
|
||||
</span>
|
||||
<InputText @bind-Value="Model.OverrideStartup" type="text" class="form-control" placeholder="@(Model.Image.Startup)"></InputText>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Docker image</TL>
|
||||
</label>
|
||||
<InputSelect TValue="int" @bind-Value="Model.DockerImageIndex" class="form-control">
|
||||
@foreach (var image in Model.Image.DockerImages)
|
||||
{
|
||||
<option value="@(Model.Image.DockerImages.IndexOf(image))">@(image.Name)</option>
|
||||
}
|
||||
</InputSelect>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body">
|
||||
@if (Model.Image != null)
|
||||
{
|
||||
<div class="mt-9 row d-flex">
|
||||
@foreach (var vars in ServerVariables.Chunk(3))
|
||||
{
|
||||
<div class="row row-cols-3 mb-3">
|
||||
@foreach (var variable in vars)
|
||||
{
|
||||
<div class="col">
|
||||
<div class="card card-body border">
|
||||
<label class="form-label">
|
||||
<TL>Name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Key" type="text" class="form-control disabled" disabled="">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Value</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="variable.Value" type="text" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="card card-body">
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="/admin/servers" class="btn btn-danger me-3">
|
||||
<TL>Cancel</TL>
|
||||
</a>
|
||||
<button class="btn btn-success" type="submit">
|
||||
<TL>Create</TL>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</SmartForm>
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -10,78 +10,78 @@
|
|||
@inject StatisticsViewService StatisticsViewService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<OnlyAdmin>
|
||||
<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">
|
||||
<option value="1">
|
||||
<TL>Hour</TL>
|
||||
</option>
|
||||
<option value="24">
|
||||
<TL>Day</TL>
|
||||
</option>
|
||||
<option value="744">
|
||||
<TL>Month</TL>
|
||||
</option>
|
||||
<option value="8760">
|
||||
<TL>Year</TL>
|
||||
</option>
|
||||
<option value="867240">
|
||||
<TL>All time</TL>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
@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">
|
||||
<option value="1">
|
||||
<TL>Hour</TL>
|
||||
</option>
|
||||
<option value="24">
|
||||
<TL>Day</TL>
|
||||
</option>
|
||||
<option value="744">
|
||||
<TL>Month</TL>
|
||||
</option>
|
||||
<option value="8760">
|
||||
<TL>Year</TL>
|
||||
</option>
|
||||
<option value="867240">
|
||||
<TL>All time</TL>
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LazyLoader @ref="Loader" Load="Load">
|
||||
@foreach (var charts in Charts.Chunk(2))
|
||||
{
|
||||
<div class="row">
|
||||
@foreach (var chart in charts)
|
||||
{
|
||||
<div class="col-sm-6">
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<TL>@chart.Key</TL>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ApexChart TItem="StatisticsData"
|
||||
Options="GenerateOptions()"
|
||||
OnRendered="OnChartRendered">
|
||||
<ApexPointSeries TItem="StatisticsData"
|
||||
Items="chart.Value"
|
||||
SeriesType="SeriesType.Area"
|
||||
Name=""
|
||||
ShowDataLabels="false"
|
||||
XValue="@(e => Formatter.FormatDate(e.Date))"
|
||||
YValue="@(e => (decimal)Math.Round(e.Value))"/>
|
||||
</ApexChart>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<LazyLoader @ref="Loader" Load="Load">
|
||||
@foreach (var charts in Charts.Chunk(2))
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<TL>Active users</TL>
|
||||
@foreach (var chart in charts)
|
||||
{
|
||||
<div class="col-sm-6">
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<TL>@chart.Key</TL>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<ApexChart TItem="StatisticsData"
|
||||
Options="GenerateOptions()"
|
||||
OnRendered="OnChartRendered">
|
||||
<ApexPointSeries TItem="StatisticsData"
|
||||
Items="chart.Value"
|
||||
SeriesType="SeriesType.Area"
|
||||
Name=""
|
||||
ShowDataLabels="false"
|
||||
XValue="@(e => Formatter.FormatDate(e.Date))"
|
||||
YValue="@(e => (decimal)Math.Round(e.Value))"/>
|
||||
</ApexChart>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="fs-2">@(ActiveUsers)</span>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
<div class="row">
|
||||
<div class="col-sm-6">
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
<div class="card-title">
|
||||
<TL>Active users</TL>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="fs-2">@(ActiveUsers)</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -10,155 +10,155 @@
|
|||
@inject SubscriptionRepository SubscriptionRepository
|
||||
@inject SubscriptionService SubscriptionService
|
||||
|
||||
<OnlyAdmin>
|
||||
<div class="card card-body p-10">
|
||||
<LazyLoader Load="Load">
|
||||
@if (Subscription == null)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
No subscription with this id has been found
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<SmartForm Model="Model" OnValidSubmit="OnSubmit">
|
||||
<label class="form-label">
|
||||
<TL>Name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputText @bind-Value="Model.Name" class="form-control"></InputText>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Description</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputTextArea @bind-Value="Model.Description" class="form-control"></InputTextArea>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Price</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputNumber @bind-Value="Model.Price" class="form-control"></InputNumber>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Currency</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<select @bind="Model.Currency" class="form-select">
|
||||
@foreach (var currency in (Currency[])Enum.GetValues(typeof(Currency)))
|
||||
{
|
||||
if (Model.Currency == currency)
|
||||
{
|
||||
<option value="@(currency)" selected="">@(currency)</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@(currency)">@(currency)</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Duration</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputNumber @bind-Value="Model.Duration" class="form-control"></InputNumber>
|
||||
</div>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminSubscriptionEdit))]
|
||||
|
||||
<div>
|
||||
@foreach (var limitPart in Limits.Chunk(3))
|
||||
<div class="card card-body p-10">
|
||||
<LazyLoader Load="Load">
|
||||
@if (Subscription == null)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
No subscription with this id has been found
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<SmartForm Model="Model" OnValidSubmit="OnSubmit">
|
||||
<label class="form-label">
|
||||
<TL>Name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputText @bind-Value="Model.Name" class="form-control"></InputText>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Description</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputTextArea @bind-Value="Model.Description" class="form-control"></InputTextArea>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Price</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputNumber @bind-Value="Model.Price" class="form-control"></InputNumber>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Currency</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<select @bind="Model.Currency" class="form-select">
|
||||
@foreach (var currency in (Currency[])Enum.GetValues(typeof(Currency)))
|
||||
{
|
||||
<div class="row row-cols-3 mb-5">
|
||||
@foreach (var limit in limitPart)
|
||||
{
|
||||
<div class="col">
|
||||
<div class="card card-body border">
|
||||
<label class="form-label">
|
||||
<TL>Identifier</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="limit.Identifier" type="text" class="form-control">
|
||||
if (Model.Currency == currency)
|
||||
{
|
||||
<option value="@(currency)" selected="">@(currency)</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@(currency)">@(currency)</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Duration</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputNumber @bind-Value="Model.Duration" class="form-control"></InputNumber>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@foreach (var limitPart in Limits.Chunk(3))
|
||||
{
|
||||
<div class="row row-cols-3 mb-5">
|
||||
@foreach (var limit in limitPart)
|
||||
{
|
||||
<div class="col">
|
||||
<div class="card card-body border">
|
||||
<label class="form-label">
|
||||
<TL>Identifier</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="limit.Identifier" type="text" class="form-control">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Amount</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="limit.Amount" type="number" class="form-control">
|
||||
</div>
|
||||
<div class="d-flex flex-column mb-15 fv-row">
|
||||
<div class="fs-5 fw-bold form-label mb-3">
|
||||
<TL>Options</TL>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Amount</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="limit.Amount" type="number" class="form-control">
|
||||
</div>
|
||||
<div class="d-flex flex-column mb-15 fv-row">
|
||||
<div class="fs-5 fw-bold form-label mb-3">
|
||||
<TL>Options</TL>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<div class="dataTables_wrapper dt-bootstrap4 no-footer">
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle table-row-dashed fw-semibold fs-6 gy-5 dataTable no-footer">
|
||||
<thead>
|
||||
<tr class="text-start text-muted fw-bold fs-7 text-uppercase gs-0">
|
||||
<th class="pt-0 sorting_disabled">
|
||||
<TL>Key</TL>
|
||||
</th>
|
||||
<th class="pt-0 sorting_disabled">
|
||||
<TL>Value</TL>
|
||||
</th>
|
||||
<th class="pt-0 text-end sorting_disabled">
|
||||
<TL>Remove</TL>
|
||||
</th>
|
||||
<div class="table-responsive">
|
||||
<div class="dataTables_wrapper dt-bootstrap4 no-footer">
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle table-row-dashed fw-semibold fs-6 gy-5 dataTable no-footer">
|
||||
<thead>
|
||||
<tr class="text-start text-muted fw-bold fs-7 text-uppercase gs-0">
|
||||
<th class="pt-0 sorting_disabled">
|
||||
<TL>Key</TL>
|
||||
</th>
|
||||
<th class="pt-0 sorting_disabled">
|
||||
<TL>Value</TL>
|
||||
</th>
|
||||
<th class="pt-0 text-end sorting_disabled">
|
||||
<TL>Remove</TL>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var option in limit.Options)
|
||||
{
|
||||
<tr class="odd">
|
||||
<td>
|
||||
<input @bind="option.Key" type="text" class="form-control form-control-solid">
|
||||
</td>
|
||||
<td>
|
||||
<input @bind="option.Value" type="text" class="form-control form-control-solid">
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<button @onclick="() => limit.Options.Remove(option)" type="button" class="btn btn-icon btn-flex btn-active-light-primary w-30px h-30px me-3" data-kt-action="field_remove">
|
||||
<i class="bx bx-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var option in limit.Options)
|
||||
{
|
||||
<tr class="odd">
|
||||
<td>
|
||||
<input @bind="option.Key" type="text" class="form-control form-control-solid">
|
||||
</td>
|
||||
<td>
|
||||
<input @bind="option.Value" type="text" class="form-control form-control-solid">
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<button @onclick="() => limit.Options.Remove(option)" type="button" class="btn btn-icon btn-flex btn-active-light-primary w-30px h-30px me-3" data-kt-action="field_remove">
|
||||
<i class="bx bx-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-5 d-flex align-items-center justify-content-center justify-content-md-start"></div>
|
||||
<div class="col-sm-12 col-md-7 d-flex align-items-center justify-content-center justify-content-md-end"></div>
|
||||
</div>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-5 d-flex align-items-center justify-content-center justify-content-md-start"></div>
|
||||
<div class="col-sm-12 col-md-7 d-flex align-items-center justify-content-center justify-content-md-end"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group mt-5">
|
||||
<button @onclick:preventDefault @onclick="() => limit.Options.Add(new())" type="button" class="btn btn-light-primary me-auto">Add option</button>
|
||||
<button @onclick:preventDefault @onclick="() => Limits.Remove(limit)" class="btn btn-danger float-end">
|
||||
<i class="bx bx-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group mt-5">
|
||||
<button @onclick:preventDefault @onclick="() => limit.Options.Add(new())" type="button" class="btn btn-light-primary me-auto">Add option</button>
|
||||
<button @onclick:preventDefault @onclick="() => Limits.Remove(limit)" class="btn btn-danger float-end">
|
||||
<i class="bx bx-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="float-end">
|
||||
<button @onclick:preventDefault @onclick="() => Limits.Add(new())" class="btn btn-primary">
|
||||
<TL>Add new limit</TL>
|
||||
</button>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<TL>Save subscription</TL>
|
||||
</button>
|
||||
</div>
|
||||
</SmartForm>
|
||||
}
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
<div class="float-end">
|
||||
<button @onclick:preventDefault @onclick="() => Limits.Add(new())" class="btn btn-primary">
|
||||
<TL>Add new limit</TL>
|
||||
</button>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<TL>Save subscription</TL>
|
||||
</button>
|
||||
</div>
|
||||
</SmartForm>
|
||||
}
|
||||
</LazyLoader>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -9,52 +9,52 @@
|
|||
@inject SubscriptionRepository SubscriptionRepository
|
||||
@inject SubscriptionService SubscriptionService
|
||||
|
||||
<OnlyAdmin>
|
||||
<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">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<TL>Subscriptions</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/subscriptions/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-credit-card"></i>
|
||||
<TL>New subscription</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="Subscription" Items="Subscriptions" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Description"))" Field="@(x => x.Description)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Price"))" Field="@(x => x.Price)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Currency"))" Field="@(x => x.Currency)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Duration"))" Field="@(x => x.Duration)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Subscription" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/subscriptions/edit/@(context.Id)/">
|
||||
<TL>Manage</TL>
|
||||
@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">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<TL>Subscriptions</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/subscriptions/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-credit-card"></i>
|
||||
<TL>New subscription</TL>
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Subscription" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<DeleteButton Confirm="true"
|
||||
OnClick="() => Delete(context)">
|
||||
</DeleteButton>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="Subscription" Items="Subscriptions" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Description"))" Field="@(x => x.Description)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Price"))" Field="@(x => x.Price)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Currency"))" Field="@(x => x.Currency)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Subscription" Title="@(SmartTranslateService.Translate("Duration"))" Field="@(x => x.Duration)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="Subscription" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/subscriptions/edit/@(context.Id)/">
|
||||
<TL>Manage</TL>
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="Subscription" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<DeleteButton Confirm="true"
|
||||
OnClick="() => Delete(context)">
|
||||
</DeleteButton>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -8,144 +8,144 @@
|
|||
@inject SubscriptionRepository SubscriptionRepository
|
||||
@inject SubscriptionService SubscriptionService
|
||||
|
||||
<OnlyAdmin>
|
||||
<div class="card card-body p-10">
|
||||
<SmartForm Model="Model" OnValidSubmit="OnSubmit">
|
||||
<label class="form-label">
|
||||
<TL>Name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputText @bind-Value="Model.Name" class="form-control"></InputText>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Description</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputTextArea @bind-Value="Model.Description" class="form-control"></InputTextArea>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Price</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputNumber @bind-Value="Model.Price" class="form-control"></InputNumber>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Currency</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<select @bind="Model.Currency" class="form-select">
|
||||
@foreach (var currency in (Currency[])Enum.GetValues(typeof(Currency)))
|
||||
{
|
||||
if (Model.Currency == currency)
|
||||
{
|
||||
<option value="@(currency)" selected="">@(currency)</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@(currency)">@(currency)</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Duration</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputNumber @bind-Value="Model.Duration" class="form-control"></InputNumber>
|
||||
</div>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminNewSubscription))]
|
||||
|
||||
<div>
|
||||
@foreach (var limitPart in Limits.Chunk(3))
|
||||
{
|
||||
<div class="row row-cols-3 mb-5">
|
||||
@foreach (var limit in limitPart)
|
||||
{
|
||||
<div class="col">
|
||||
<div class="card card-body border">
|
||||
<label class="form-label">
|
||||
<TL>Identifier</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="limit.Identifier" type="text" class="form-control">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Amount</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="limit.Amount" type="number" class="form-control">
|
||||
</div>
|
||||
<div class="d-flex flex-column mb-15 fv-row">
|
||||
<div class="fs-5 fw-bold form-label mb-3">
|
||||
<TL>Options</TL>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<div class="dataTables_wrapper dt-bootstrap4 no-footer">
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle table-row-dashed fw-semibold fs-6 gy-5 dataTable no-footer">
|
||||
<thead>
|
||||
<tr class="text-start text-muted fw-bold fs-7 text-uppercase gs-0">
|
||||
<th class="pt-0 sorting_disabled">
|
||||
<TL>Key</TL>
|
||||
</th>
|
||||
<th class="pt-0 sorting_disabled">
|
||||
<TL>Value</TL>
|
||||
</th>
|
||||
<th class="pt-0 text-end sorting_disabled">
|
||||
<TL>Remove</TL>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var option in limit.Options)
|
||||
{
|
||||
<tr class="odd">
|
||||
<td>
|
||||
<input @bind="option.Key" type="text" class="form-control form-control-solid">
|
||||
</td>
|
||||
<td>
|
||||
<input @bind="option.Value" type="text" class="form-control form-control-solid">
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<button @onclick="() => limit.Options.Remove(option)" type="button" class="btn btn-icon btn-flex btn-active-light-primary w-30px h-30px me-3" data-kt-action="field_remove">
|
||||
<i class="bx bx-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
<div class="card card-body p-10">
|
||||
<SmartForm Model="Model" OnValidSubmit="OnSubmit">
|
||||
<label class="form-label">
|
||||
<TL>Name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputText @bind-Value="Model.Name" class="form-control"></InputText>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Description</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputTextArea @bind-Value="Model.Description" class="form-control"></InputTextArea>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Price</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputNumber @bind-Value="Model.Price" class="form-control"></InputNumber>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Currency</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<select @bind="Model.Currency" class="form-select">
|
||||
@foreach (var currency in (Currency[])Enum.GetValues(typeof(Currency)))
|
||||
{
|
||||
if (Model.Currency == currency)
|
||||
{
|
||||
<option value="@(currency)" selected="">@(currency)</option>
|
||||
}
|
||||
else
|
||||
{
|
||||
<option value="@(currency)">@(currency)</option>
|
||||
}
|
||||
}
|
||||
</select>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Duration</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputNumber @bind-Value="Model.Duration" class="form-control"></InputNumber>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
@foreach (var limitPart in Limits.Chunk(3))
|
||||
{
|
||||
<div class="row row-cols-3 mb-5">
|
||||
@foreach (var limit in limitPart)
|
||||
{
|
||||
<div class="col">
|
||||
<div class="card card-body border">
|
||||
<label class="form-label">
|
||||
<TL>Identifier</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="limit.Identifier" type="text" class="form-control">
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-5 d-flex align-items-center justify-content-center justify-content-md-start"></div>
|
||||
<div class="col-sm-12 col-md-7 d-flex align-items-center justify-content-center justify-content-md-end"></div>
|
||||
<label class="form-label">
|
||||
<TL>Amount</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="limit.Amount" type="number" class="form-control">
|
||||
</div>
|
||||
<div class="d-flex flex-column mb-15 fv-row">
|
||||
<div class="fs-5 fw-bold form-label mb-3">
|
||||
<TL>Options</TL>
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
<div class="dataTables_wrapper dt-bootstrap4 no-footer">
|
||||
<div class="table-responsive">
|
||||
<table class="table align-middle table-row-dashed fw-semibold fs-6 gy-5 dataTable no-footer">
|
||||
<thead>
|
||||
<tr class="text-start text-muted fw-bold fs-7 text-uppercase gs-0">
|
||||
<th class="pt-0 sorting_disabled">
|
||||
<TL>Key</TL>
|
||||
</th>
|
||||
<th class="pt-0 sorting_disabled">
|
||||
<TL>Value</TL>
|
||||
</th>
|
||||
<th class="pt-0 text-end sorting_disabled">
|
||||
<TL>Remove</TL>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@foreach (var option in limit.Options)
|
||||
{
|
||||
<tr class="odd">
|
||||
<td>
|
||||
<input @bind="option.Key" type="text" class="form-control form-control-solid">
|
||||
</td>
|
||||
<td>
|
||||
<input @bind="option.Value" type="text" class="form-control form-control-solid">
|
||||
</td>
|
||||
<td class="text-end">
|
||||
<button @onclick="() => limit.Options.Remove(option)" type="button" class="btn btn-icon btn-flex btn-active-light-primary w-30px h-30px me-3" data-kt-action="field_remove">
|
||||
<i class="bx bx-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-sm-12 col-md-5 d-flex align-items-center justify-content-center justify-content-md-start"></div>
|
||||
<div class="col-sm-12 col-md-7 d-flex align-items-center justify-content-center justify-content-md-end"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group mt-5">
|
||||
<button @onclick:preventDefault @onclick="() => limit.Options.Add(new())" type="button" class="btn btn-light-primary me-auto">Add option</button>
|
||||
<button @onclick:preventDefault @onclick="() => Limits.Remove(limit)" class="btn btn-danger float-end">
|
||||
<i class="bx bx-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group mt-5">
|
||||
<button @onclick:preventDefault @onclick="() => limit.Options.Add(new())" type="button" class="btn btn-light-primary me-auto">Add option</button>
|
||||
<button @onclick:preventDefault @onclick="() => Limits.Remove(limit)" class="btn btn-danger float-end">
|
||||
<i class="bx bx-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="float-end">
|
||||
<button @onclick:preventDefault @onclick="() => Limits.Add(new())" class="btn btn-primary">
|
||||
<TL>Add new limit</TL>
|
||||
</button>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<TL>Create subscription</TL>
|
||||
</button>
|
||||
</div>
|
||||
</SmartForm>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
}
|
||||
</div>
|
||||
|
||||
<div class="float-end">
|
||||
<button @onclick:preventDefault @onclick="() => Limits.Add(new())" class="btn btn-primary">
|
||||
<TL>Add new limit</TL>
|
||||
</button>
|
||||
<button type="submit" class="btn btn-success">
|
||||
<TL>Create subscription</TL>
|
||||
</button>
|
||||
</div>
|
||||
</SmartForm>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -14,213 +14,213 @@
|
|||
|
||||
@implements IDisposable
|
||||
|
||||
<OnlyAdmin>
|
||||
<LazyLoader Load="Load">
|
||||
@if (User == null)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<TL>User not found</TL>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="d-flex flex-column flex-xl-row p-7">
|
||||
<div class="flex-lg-row-fluid me-6 mb-20 mb-xl-0">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<LazyLoader Load="LoadMessages">
|
||||
<div class="scroll-y me-n5 pe-5" style="max-height: 55vh; display: flex; flex-direction: column-reverse;">
|
||||
@foreach (var message in Messages)
|
||||
{
|
||||
if (message.Sender == null || message.Sender.Id != User.Id)
|
||||
{
|
||||
<div class="d-flex justify-content-end mb-10 ">
|
||||
<div class="d-flex flex-column align-items-end">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<div class="me-3">
|
||||
<span class="text-muted fs-7 mb-1">@(Formatter.FormatAgoFromDateTime(message.CreatedAt, SmartTranslateService))</span>
|
||||
<a class="fs-5 fw-bold text-gray-900 text-hover-primary ms-1">
|
||||
@if (message.Sender != null)
|
||||
{
|
||||
<span>@(message.Sender.FirstName) @(message.Sender.LastName)</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>
|
||||
<TL>System</TL>
|
||||
</span>
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
<div class="symbol symbol-35px symbol-circle ">
|
||||
<img alt="Logo" src="@(ResourceService.Image("logo.svg"))">
|
||||
</div>
|
||||
</div>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminSupportView))]
|
||||
|
||||
<div class="p-5 rounded bg-light-primary text-dark fw-semibold mw-lg-400px text-end">
|
||||
@if (message.Sender == null)
|
||||
{
|
||||
<TL>@(message.Content)</TL>
|
||||
}
|
||||
else
|
||||
{
|
||||
foreach (var line in message.Content.Split("\n"))
|
||||
{
|
||||
@(line)<br/>
|
||||
}
|
||||
|
||||
if (message.Attachment != "")
|
||||
{
|
||||
<div class="mt-3">
|
||||
@if (Regex.IsMatch(message.Attachment, @"\.(jpg|jpeg|png|gif|bmp)$"))
|
||||
<LazyLoader Load="Load">
|
||||
@if (User == null)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<TL>User not found</TL>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="d-flex flex-column flex-xl-row p-7">
|
||||
<div class="flex-lg-row-fluid me-6 mb-20 mb-xl-0">
|
||||
<div class="card">
|
||||
<div class="card-body">
|
||||
<LazyLoader Load="LoadMessages">
|
||||
<div class="scroll-y me-n5 pe-5" style="max-height: 55vh; display: flex; flex-direction: column-reverse;">
|
||||
@foreach (var message in Messages)
|
||||
{
|
||||
if (message.Sender == null || message.Sender.Id != User.Id)
|
||||
{
|
||||
<div class="d-flex justify-content-end mb-10 ">
|
||||
<div class="d-flex flex-column align-items-end">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<div class="me-3">
|
||||
<span class="text-muted fs-7 mb-1">@(Formatter.FormatAgoFromDateTime(message.CreatedAt, SmartTranslateService))</span>
|
||||
<a class="fs-5 fw-bold text-gray-900 text-hover-primary ms-1">
|
||||
@if (message.Sender != null)
|
||||
{
|
||||
<span>@(message.Sender.FirstName) @(message.Sender.LastName)</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>
|
||||
<TL>System</TL>
|
||||
</span>
|
||||
}
|
||||
</a>
|
||||
</div>
|
||||
<div class="symbol symbol-35px symbol-circle ">
|
||||
<img alt="Logo" src="@(ResourceService.Image("logo.svg"))">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-5 rounded bg-light-primary text-dark fw-semibold mw-lg-400px text-end">
|
||||
@if (message.Sender == null)
|
||||
{
|
||||
<img src="@(ResourceService.BucketItem("supportChat", message.Attachment))" class="img-fluid" alt="Attachment"/>
|
||||
<TL>@(message.Content)</TL>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="btn btn-secondary" href="@(ResourceService.BucketItem("supportChat", message.Attachment))">
|
||||
<i class="me-2 bx bx-download"></i> @(message.Attachment)
|
||||
</a>
|
||||
foreach (var line in message.Content.Split("\n"))
|
||||
{
|
||||
@(line)<br/>
|
||||
}
|
||||
|
||||
if (message.Attachment != "")
|
||||
{
|
||||
<div class="mt-3">
|
||||
@if (Regex.IsMatch(message.Attachment, @"\.(jpg|jpeg|png|gif|bmp)$"))
|
||||
{
|
||||
<img src="@(ResourceService.BucketItem("supportChat", message.Attachment))" class="img-fluid" alt="Attachment"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="btn btn-secondary" href="@(ResourceService.BucketItem("supportChat", message.Attachment))">
|
||||
<i class="me-2 bx bx-download"></i> @(message.Attachment)
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="d-flex justify-content-start mb-10 ">
|
||||
<div class="d-flex flex-column align-items-start">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<div class="symbol symbol-35px symbol-circle ">
|
||||
<img alt="Avatar" src="@(ResourceService.Avatar(message.Sender))">
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<a class="fs-5 fw-bold text-gray-900 text-hover-primary me-1">
|
||||
<span>@(message.Sender.FirstName) @(message.Sender.LastName)</span>
|
||||
</a>
|
||||
<span class="text-muted fs-7 mb-1">@(Formatter.FormatAgoFromDateTime(message.CreatedAt, SmartTranslateService))</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-5 rounded bg-light-info text-dark fw-semibold mw-lg-400px text-start">
|
||||
@{
|
||||
int i = 0;
|
||||
var arr = message.Content.Split("\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);}
|
||||
@foreach (var line in arr)
|
||||
{
|
||||
@line
|
||||
if (i++ != arr.Length - 1)
|
||||
{
|
||||
<br />
|
||||
}
|
||||
}
|
||||
|
||||
@if (message.Attachment != "")
|
||||
{
|
||||
<div class="mt-3">
|
||||
@if (Regex.IsMatch(message.Attachment, @"\.(jpg|jpeg|png|gif|bmp)$"))
|
||||
{
|
||||
<img src="@(ResourceService.BucketItem("supportChat", message.Attachment))" class="img-fluid" alt="Attachment"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="btn btn-secondary" href="@(ResourceService.BucketItem("supportChat", message.Attachment))">
|
||||
<i class="me-2 bx bx-download"></i> @(message.Attachment)
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="d-flex justify-content-start mb-10 ">
|
||||
<div class="d-flex flex-column align-items-start">
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<div class="symbol symbol-35px symbol-circle ">
|
||||
<img alt="Avatar" src="@(ResourceService.Avatar(message.Sender))">
|
||||
</div>
|
||||
<div class="ms-3">
|
||||
<a class="fs-5 fw-bold text-gray-900 text-hover-primary me-1">
|
||||
<span>@(message.Sender.FirstName) @(message.Sender.LastName)</span>
|
||||
</a>
|
||||
<span class="text-muted fs-7 mb-1">@(Formatter.FormatAgoFromDateTime(message.CreatedAt, SmartTranslateService))</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="p-5 rounded bg-light-info text-dark fw-semibold mw-lg-400px text-start">
|
||||
@{
|
||||
int i = 0;
|
||||
var arr = message.Content.Split("\n", StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries);}
|
||||
@foreach (var line in arr)
|
||||
{
|
||||
@line
|
||||
if (i++ != arr.Length - 1)
|
||||
{
|
||||
<br />
|
||||
}
|
||||
}
|
||||
|
||||
@if (message.Attachment != "")
|
||||
{
|
||||
<div class="mt-3">
|
||||
@if (Regex.IsMatch(message.Attachment, @"\.(jpg|jpeg|png|gif|bmp)$"))
|
||||
{
|
||||
<img src="@(ResourceService.BucketItem("supportChat", message.Attachment))" class="img-fluid" alt="Attachment"/>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a class="btn btn-secondary" href="@(ResourceService.BucketItem("supportChat", message.Attachment))">
|
||||
<i class="me-2 bx bx-download"></i> @(message.Attachment)
|
||||
</a>
|
||||
}
|
||||
</div>
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
@if (Typing.Any())
|
||||
{
|
||||
<span class="mb-5 fs-5 d-flex flex-row">
|
||||
<div class="wave me-1">
|
||||
<div class="dot h-5px w-5px" style="margin-right: 1px;"></div>
|
||||
<div class="dot h-5px w-5px" style="margin-right: 1px;"></div>
|
||||
<div class="dot h-5px w-5px"></div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
@if (Typing.Length > 1)
|
||||
{
|
||||
<span>
|
||||
@(Typing.Aggregate((current, next) => current + ", " + next)) <TL>are typing</TL>
|
||||
<div class="card-footer">
|
||||
@if (Typing.Any())
|
||||
{
|
||||
<span class="mb-5 fs-5 d-flex flex-row">
|
||||
<div class="wave me-1">
|
||||
<div class="dot h-5px w-5px" style="margin-right: 1px;"></div>
|
||||
<div class="dot h-5px w-5px" style="margin-right: 1px;"></div>
|
||||
<div class="dot h-5px w-5px"></div>
|
||||
</div>
|
||||
@if (Typing.Length > 1)
|
||||
{
|
||||
<span>
|
||||
@(Typing.Aggregate((current, next) => current + ", " + next)) <TL>are typing</TL>
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>
|
||||
@(Typing.First()) <TL>is typing</TL>
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
|
||||
<div class="d-flex flex-stack">
|
||||
<table class="w-100">
|
||||
<tr>
|
||||
<td class="align-top">
|
||||
<SmartFileSelect @ref="SmartFileSelect"></SmartFileSelect>
|
||||
</td>
|
||||
<td class="w-100">
|
||||
<textarea @bind="Content" @oninput="OnTyping" class="form-control mb-3 form-control-flush" rows="1" placeholder="Type a message">
|
||||
</textarea>
|
||||
</td>
|
||||
<td class="align-top">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Send"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Sending"))"
|
||||
CssClasses="btn-primary ms-2"
|
||||
OnClick="Send">
|
||||
</WButton>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-column flex-lg-row-auto w-100 mw-lg-300px mw-xxl-350px">
|
||||
<div class="card p-10 mb-15 pb-8">
|
||||
<h2 class="text-dark fw-bold mb-2">
|
||||
<TL>User information</TL>
|
||||
</h2>
|
||||
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<span class="fw-semibold text-gray-800 fs-5 m-0">
|
||||
<TL>Name</TL>: @(User.FirstName) @User.LastName
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span>
|
||||
@(Typing.First()) <TL>is typing</TL>
|
||||
</div>
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="fw-semibold text-gray-800 fs-5 m-0">
|
||||
<TL>Email</TL>: <a href="/admin/users/view/@User.Id">@(User.Email)</a>
|
||||
</span>
|
||||
}
|
||||
</span>
|
||||
}
|
||||
|
||||
<div class="d-flex flex-stack">
|
||||
<table class="w-100">
|
||||
<tr>
|
||||
<td class="align-top">
|
||||
<SmartFileSelect @ref="SmartFileSelect"></SmartFileSelect>
|
||||
</td>
|
||||
<td class="w-100">
|
||||
<textarea @bind="Content" @oninput="OnTyping" class="form-control mb-3 form-control-flush" rows="1" placeholder="Type a message">
|
||||
</textarea>
|
||||
</td>
|
||||
<td class="align-top">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Send"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Sending"))"
|
||||
CssClasses="btn-primary ms-2"
|
||||
OnClick="Send">
|
||||
</div>
|
||||
<div class="align-items-center mt-3">
|
||||
<span class="fw-semibold text-gray-800 fs-5 m-0">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Close ticket"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Closing"))"
|
||||
CssClasses="btn-danger float-end"
|
||||
OnClick="CloseTicket">
|
||||
</WButton>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="flex-column flex-lg-row-auto w-100 mw-lg-300px mw-xxl-350px">
|
||||
<div class="card p-10 mb-15 pb-8">
|
||||
<h2 class="text-dark fw-bold mb-2">
|
||||
<TL>User information</TL>
|
||||
</h2>
|
||||
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<span class="fw-semibold text-gray-800 fs-5 m-0">
|
||||
<TL>Name</TL>: @(User.FirstName) @User.LastName
|
||||
</span>
|
||||
</div>
|
||||
<div class="d-flex align-items-center mb-2">
|
||||
<span class="fw-semibold text-gray-800 fs-5 m-0">
|
||||
<TL>Email</TL>: <a href="/admin/users/view/@User.Id">@(User.Email)</a>
|
||||
</span>
|
||||
</div>
|
||||
<div class="align-items-center mt-3">
|
||||
<span class="fw-semibold text-gray-800 fs-5 m-0">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Close ticket"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Closing"))"
|
||||
CssClasses="btn-danger float-end"
|
||||
OnClick="CloseTicket">
|
||||
</WButton>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
}
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -9,26 +9,26 @@
|
|||
@inject ToastService ToastService
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<OnlyAdmin>
|
||||
<AdminSystemNavigation Index="8"/>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminSysConfiguration))]
|
||||
|
||||
<LazyLoader Load="Load">
|
||||
<div class="card">
|
||||
<SmartForm Model="Config" OnValidSubmit="OnSubmit">
|
||||
<div class="card-body">
|
||||
<SmartFormClass Model="Config"/>
|
||||
<AdminSystemNavigation Index="8"/>
|
||||
|
||||
<LazyLoader Load="Load">
|
||||
<div class="card">
|
||||
<SmartForm Model="Config" OnValidSubmit="OnSubmit">
|
||||
<div class="card-body">
|
||||
<SmartFormClass Model="Config"/>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-success">
|
||||
<TL>Save</TL>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-success">
|
||||
<TL>Save</TL>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</SmartForm>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
</div>
|
||||
</SmartForm>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -4,34 +4,34 @@
|
|||
|
||||
@inject DiscordBotService DiscordBotService
|
||||
|
||||
<OnlyAdmin>
|
||||
<AdminSystemNavigation Index="6"/>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminSysDiscordBot))]
|
||||
|
||||
<div class="mt-3 card card-body">
|
||||
<WButton Text="Register commands"
|
||||
WorkingText="Working"
|
||||
CssClasses="btn-primary"
|
||||
OnClick="RegisterCommands">
|
||||
</WButton>
|
||||
<AdminSystemNavigation Index="6"/>
|
||||
|
||||
<WButton Text="Void commands"
|
||||
WorkingText="Working"
|
||||
CssClasses="mt-3 btn-danger"
|
||||
OnClick="VoidCommands">
|
||||
</WButton>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
<div class="mt-3 card card-body">
|
||||
<WButton Text="Register commands"
|
||||
WorkingText="Working"
|
||||
CssClasses="btn-primary"
|
||||
OnClick="RegisterCommands">
|
||||
</WButton>
|
||||
|
||||
<WButton Text="Void commands"
|
||||
WorkingText="Working"
|
||||
CssClasses="mt-3 btn-danger"
|
||||
OnClick="VoidCommands">
|
||||
</WButton>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
//ToDo: Ole muss ins Bett gehen
|
||||
//ToDo: Bot Info Card with Name, Bot Avatar, (RichPresence) Game Status, Activity Status
|
||||
|
||||
|
||||
private async Task RegisterCommands()
|
||||
{
|
||||
await DiscordBotService.CreateCommands();
|
||||
}
|
||||
|
||||
|
||||
private Task VoidCommands()
|
||||
{
|
||||
DiscordBotService.RemoveCommandsModule.VoidCommands();
|
||||
|
|
|
@ -7,92 +7,92 @@
|
|||
@inject HostSystemHelper HostSystemHelper
|
||||
@inject MoonlightService MoonlightService
|
||||
|
||||
<OnlyAdmin>
|
||||
<AdminSystemNavigation Index="0"/>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminSystem))]
|
||||
|
||||
<LazyLoader Load="Load">
|
||||
<div class="row">
|
||||
<div class="col-xxl-6 my-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Version</TL>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="fs-5">
|
||||
<TL>You are running moonlight version</TL>
|
||||
<span class="text-primary">@(MoonlightService.AppVersion)</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<AdminSystemNavigation Index="0"/>
|
||||
|
||||
<LazyLoader Load="Load">
|
||||
<div class="row">
|
||||
<div class="col-xxl-6 my-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Version</TL>
|
||||
</span>
|
||||
</div>
|
||||
<div class="col-xxl-6 my-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Operating system</TL>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="fs-5">
|
||||
<TL>Moonlight is running on</TL>
|
||||
<span class="text-primary">@(HostSystemHelper.GetOsName())</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xxl-6 my-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Memory usage</TL>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="fs-5">
|
||||
<TL>Moonlight is using</TL>
|
||||
<span class="text-primary">@(HostSystemHelper.GetMemoryUsage()) MB</span>
|
||||
<TL>of memory</TL>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xxl-6 my-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Cpu usage</TL>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="fs-5">
|
||||
<TL>Moonlight is using</TL>
|
||||
<span class="text-primary">@(HostSystemHelper.GetCpuUsage()) %</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xxl-6 my-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Uptime</TL>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="fs-5">
|
||||
<TL>Moonlight is since</TL>
|
||||
<span class="text-primary">
|
||||
@(Formatter.FormatUptime(DateTime.UtcNow - MoonlightService.StartTimestamp))
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="fs-5">
|
||||
<TL>You are running moonlight version</TL>
|
||||
<span class="text-primary">@(MoonlightService.AppVersion)</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
</div>
|
||||
<div class="col-xxl-6 my-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Operating system</TL>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="fs-5">
|
||||
<TL>Moonlight is running on</TL>
|
||||
<span class="text-primary">@(HostSystemHelper.GetOsName())</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xxl-6 my-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Memory usage</TL>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="fs-5">
|
||||
<TL>Moonlight is using</TL>
|
||||
<span class="text-primary">@(HostSystemHelper.GetMemoryUsage()) MB</span>
|
||||
<TL>of memory</TL>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xxl-6 my-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Cpu usage</TL>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="fs-5">
|
||||
<TL>Moonlight is using</TL>
|
||||
<span class="text-primary">@(HostSystemHelper.GetCpuUsage()) %</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-xxl-6 my-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Uptime</TL>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="fs-5">
|
||||
<TL>Moonlight is since</TL>
|
||||
<span class="text-primary">
|
||||
@(Formatter.FormatUptime(DateTime.UtcNow - MoonlightService.StartTimestamp))
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
|
@ -100,4 +100,4 @@
|
|||
{
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -5,95 +5,95 @@
|
|||
@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">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Actions</TL>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Test mail configuration"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Sending test mail"))"
|
||||
CssClasses="btn-primary"
|
||||
OnClick="SendTestMail">
|
||||
</WButton>
|
||||
</div>
|
||||
<AdminSystemNavigation Index="9"/>
|
||||
|
||||
<div class="card mb-3">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Actions</TL>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Test mail configuration"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Sending test mail"))"
|
||||
CssClasses="btn-primary"
|
||||
OnClick="SendTestMail">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
@if (CurrentMailTemplate == null)
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Mail templates</TL>
|
||||
</span>
|
||||
<div class="card-toolbar">
|
||||
<WButton Text="@(SmartTranslateService.Translate("New mail template"))"
|
||||
CssClasses="btn-sm btn-success"
|
||||
OnClick="CreateNewMailTemplate">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="MailTemplate" Items="MailTemplateFiles" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="MailTemplate" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
@{
|
||||
var name = context.Name.Replace(Path.GetExtension(context.Name), "");
|
||||
}
|
||||
|
||||
<span>@(name)</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="MailTemplate" Title="" Field="@(x => x.Name)" Filterable="false" Sortable="false">
|
||||
<Template>
|
||||
<div class="text-end">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Edit"))"
|
||||
OnClick="() => EditTemplate(context)">
|
||||
</WButton>
|
||||
<DeleteButton OnClick="() => DeleteTemplate(context)"
|
||||
Confirm="true">
|
||||
</DeleteButton>
|
||||
</div>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
@if (CurrentMailTemplate == null)
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Mail templates</TL>
|
||||
</span>
|
||||
<div class="card-toolbar">
|
||||
<WButton Text="@(SmartTranslateService.Translate("New mail template"))"
|
||||
CssClasses="btn-sm btn-success"
|
||||
OnClick="CreateNewMailTemplate">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<FileEditor Language="html"
|
||||
HideControls="false"
|
||||
InitialData="@(CurrentMailTemplateContent)"
|
||||
OnCancel="OnCancelTemplateEdit"
|
||||
OnSubmit="OnSubmitTemplateEdit"/>
|
||||
}
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="MailTemplate" Items="MailTemplateFiles" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="MailTemplate" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
@{
|
||||
var name = context.Name.Replace(Path.GetExtension(context.Name), "");
|
||||
}
|
||||
|
||||
<span>@(name)</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="MailTemplate" Title="" Field="@(x => x.Name)" Filterable="false" Sortable="false">
|
||||
<Template>
|
||||
<div class="text-end">
|
||||
<WButton Text="@(SmartTranslateService.Translate("Edit"))"
|
||||
OnClick="() => EditTemplate(context)">
|
||||
</WButton>
|
||||
<DeleteButton OnClick="() => DeleteTemplate(context)"
|
||||
Confirm="true">
|
||||
</DeleteButton>
|
||||
</div>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<FileEditor Language="html"
|
||||
HideControls="false"
|
||||
InitialData="@(CurrentMailTemplateContent)"
|
||||
OnCancel="OnCancelTemplateEdit"
|
||||
OnSubmit="OnSubmitTemplateEdit"/>
|
||||
}
|
||||
</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"));
|
||||
}
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -10,52 +10,52 @@
|
|||
@inject NavigationManager NavigationManager
|
||||
@inject NewsEntryRepository NewsEntryRepository
|
||||
|
||||
<OnlyAdmin>
|
||||
<LazyLoader Load="Load">
|
||||
@if (Entry == null)
|
||||
{
|
||||
<div class="alert bg-info d-flex flex-column flex-sm-row w-100 p-5">
|
||||
<div class="d-flex flex-column pe-0 pe-sm-10">
|
||||
<h4 class="fw-semibold">
|
||||
<TL>No entry found</TL>
|
||||
</h4>
|
||||
<span>
|
||||
<TL>We were not able to find the news entry with this id</TL>
|
||||
</span>
|
||||
@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">
|
||||
<div class="d-flex flex-column pe-0 pe-sm-10">
|
||||
<h4 class="fw-semibold">
|
||||
<TL>No entry found</TL>
|
||||
</h4>
|
||||
<span>
|
||||
<TL>We were not able to find the news entry with this id</TL>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card mb-6">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title w-75">
|
||||
<input type="text" @bind="Entry.Title" placeholder="@SmartTranslateService.Translate("Title...")" class="form-control form-control-flush"/>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<span class="text-gray-600 fw-semibold">@(Formatter.FormatDateOnly(Entry.Date))</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card mb-6">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title w-75">
|
||||
<input type="text" @bind="Entry.Title" placeholder="@SmartTranslateService.Translate("Title...")" class="form-control form-control-flush"/>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<span class="text-gray-600 fw-semibold">@(Formatter.FormatDateOnly(Entry.Date))</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<FileEditor @ref="FileEditor" Language="markdown" HideControls="true" InitialData="@(Entry.Markdown)"/>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<WButton CssClasses="btn btn-primary text-end"
|
||||
OnClick="Save"
|
||||
Text="@SmartTranslateService.Translate("Save")"
|
||||
WorkingText="@SmartTranslateService.Translate("Saving...")">
|
||||
</WButton>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<FileEditor @ref="FileEditor" Language="markdown" HideControls="true" InitialData="@(Entry.Markdown)"/>
|
||||
</div>
|
||||
}
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
<div class="card-footer">
|
||||
<WButton CssClasses="btn btn-primary text-end"
|
||||
OnClick="Save"
|
||||
Text="@SmartTranslateService.Translate("Save")"
|
||||
WorkingText="@SmartTranslateService.Translate("Saving...")">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
[Parameter]
|
||||
public int Id { get; set; }
|
||||
|
||||
|
||||
private NewsEntry? Entry;
|
||||
|
||||
private FileEditor FileEditor;
|
||||
|
@ -63,16 +63,16 @@
|
|||
private async Task Save()
|
||||
{
|
||||
Entry!.Markdown = await FileEditor.GetData();
|
||||
|
||||
|
||||
NewsEntryRepository.Update(Entry);
|
||||
|
||||
|
||||
NavigationManager.NavigateTo("/admin/system/news");
|
||||
}
|
||||
|
||||
private Task Load(LazyLoader arg)
|
||||
{
|
||||
Entry = NewsEntryRepository.Get().FirstOrDefault(x => x.Id == Id);
|
||||
|
||||
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
|
@ -12,50 +12,50 @@
|
|||
@inject SmartTranslateService SmartTranslateService
|
||||
@inject AlertService AlertService
|
||||
|
||||
<OnlyAdmin>
|
||||
<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>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminSysNews))]
|
||||
|
||||
<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>
|
||||
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
@foreach (var entry in Entries)
|
||||
{
|
||||
<div class="card mb-6">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">@entry.Title</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/system/news/edit/@(entry.Id)">
|
||||
<button class="btn btn-sm btn-light me-4">
|
||||
<TL>Edit</TL>
|
||||
</button>
|
||||
</a>
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
@foreach (var entry in Entries)
|
||||
{
|
||||
<div class="card mb-6">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">@entry.Title</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/system/news/edit/@(entry.Id)">
|
||||
<button class="btn btn-sm btn-light me-4">
|
||||
<TL>Edit</TL>
|
||||
</button>
|
||||
</a>
|
||||
|
||||
<WButton CssClasses="btn btn-sm btn-light me-4"
|
||||
Text="@SmartTranslateService.Translate("Delete")"
|
||||
WorkingText="@SmartTranslateService.Translate("Deleting...")"
|
||||
OnClick="() => Delete(entry)">
|
||||
</WButton>
|
||||
<WButton CssClasses="btn btn-sm btn-light me-4"
|
||||
Text="@SmartTranslateService.Translate("Delete")"
|
||||
WorkingText="@SmartTranslateService.Translate("Deleting...")"
|
||||
OnClick="() => Delete(entry)">
|
||||
</WButton>
|
||||
|
||||
<span class="text-gray-600 fw-semibold">@(Formatter.FormatDateOnly(entry.Date))</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
@{
|
||||
var html = (MarkupString)Markdown.ToHtml(entry.Markdown);
|
||||
}
|
||||
|
||||
@(html)
|
||||
<span class="text-gray-600 fw-semibold">@(Formatter.FormatDateOnly(entry.Date))</span>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
<div class="card-body">
|
||||
@{
|
||||
var html = (MarkupString)Markdown.ToHtml(entry.Markdown);
|
||||
}
|
||||
|
||||
@(html)
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
</LazyLoader>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -9,28 +9,28 @@
|
|||
@inject NavigationManager NavigationManager
|
||||
@inject NewsEntryRepository NewsEntryRepository
|
||||
|
||||
<OnlyAdmin>
|
||||
<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"/>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<span class="text-gray-600 fw-semibold">@(Formatter.FormatDateOnly(Model.Date))</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<FileEditor @ref="FileEditor" Language="markdown" HideControls="true" InitialData=""/>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<WButton CssClasses="btn btn-primary text-end"
|
||||
OnClick="Save"
|
||||
Text="@SmartTranslateService.Translate("Save")"
|
||||
WorkingText="@SmartTranslateService.Translate("Saving...")">
|
||||
</WButton>
|
||||
@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"/>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<span class="text-gray-600 fw-semibold">@(Formatter.FormatDateOnly(Model.Date))</span>
|
||||
</div>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
<div class="card-body">
|
||||
<FileEditor @ref="FileEditor" Language="markdown" HideControls="true" InitialData=""/>
|
||||
</div>
|
||||
<div class="card-footer">
|
||||
<WButton CssClasses="btn btn-primary text-end"
|
||||
OnClick="Save"
|
||||
Text="@SmartTranslateService.Translate("Save")"
|
||||
WorkingText="@SmartTranslateService.Translate("Saving...")">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
|
@ -44,9 +44,9 @@
|
|||
private async Task Save()
|
||||
{
|
||||
Model.Markdown = await FileEditor.GetData();
|
||||
|
||||
|
||||
NewsEntryRepository.Add(Model);
|
||||
|
||||
|
||||
NavigationManager.NavigateTo("/admin/system/news");
|
||||
}
|
||||
}
|
|
@ -11,26 +11,26 @@
|
|||
@inject ConfigService ConfigService
|
||||
@inject AlertService AlertService
|
||||
|
||||
<OnlyAdmin>
|
||||
<AdminSystemNavigation Index="5"/>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminSysResources))]
|
||||
|
||||
<div class="card card-body mb-6">
|
||||
<div class="text-end">
|
||||
<WButton CssClasses="btn btn-primary"
|
||||
Text="@(SmartTranslateService.Translate("Reload config"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Reloading"))"
|
||||
OnClick="ReloadConfig">
|
||||
</WButton>
|
||||
</div>
|
||||
<AdminSystemNavigation Index="5"/>
|
||||
|
||||
<div class="card card-body mb-6">
|
||||
<div class="text-end">
|
||||
<WButton CssClasses="btn btn-primary"
|
||||
Text="@(SmartTranslateService.Translate("Reload config"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Reloading"))"
|
||||
OnClick="ReloadConfig">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card card-body">
|
||||
<FileManager Access="@(new HostFileAccess(PathBuilder.Dir("storage")))">
|
||||
</FileManager>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
<div class="card card-body">
|
||||
<FileManager Access="@(new HostFileAccess(PathBuilder.Dir("storage")))">
|
||||
</FileManager>
|
||||
</div>
|
||||
|
||||
@code
|
||||
@code
|
||||
{
|
||||
private async Task ReloadConfig()
|
||||
{
|
||||
|
|
|
@ -3,33 +3,33 @@
|
|||
@using Moonlight.Shared.Components.Navigations
|
||||
@using global::Sentry
|
||||
|
||||
<OnlyAdmin>
|
||||
<AdminSystemNavigation Index="1"/>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminSysSentry))]
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xxl-6 my-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Status</TL>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="fs-5">
|
||||
@if (SentrySdk.IsEnabled)
|
||||
{
|
||||
<TL>Sentry is enabled</TL>
|
||||
}
|
||||
else
|
||||
{
|
||||
<TL>Sentry is disabled</TL>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
<AdminSystemNavigation Index="1"/>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-xxl-6 my-3">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<span class="card-title">
|
||||
<TL>Status</TL>
|
||||
</span>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<span class="fs-5">
|
||||
@if (SentrySdk.IsEnabled)
|
||||
{
|
||||
<TL>Sentry is enabled</TL>
|
||||
}
|
||||
else
|
||||
{
|
||||
<TL>Sentry is disabled</TL>
|
||||
}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -1,37 +1,42 @@
|
|||
@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">
|
||||
@if (User == null)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<TL>No user with this id found</TL>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-header border-0 py-0">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold fs-3">
|
||||
<TL>Manage user </TL> <span class="text-primary">@(User.Email)</span>
|
||||
</span>
|
||||
</h3>
|
||||
</div>
|
||||
</div>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminUserEdit))]
|
||||
|
||||
<LazyLoader Load="Load">
|
||||
@if (User == null)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<TL>No user with this id found</TL>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="card">
|
||||
<div class="card-header border-0 py-0">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold fs-3">
|
||||
<TL>Manage user </TL> <span class="text-primary">@(User.Email)</span>
|
||||
</span>
|
||||
</h3>
|
||||
</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>
|
||||
}
|
||||
</LazyLoader>
|
||||
</OnlyAdmin>
|
||||
</SmartForm>
|
||||
}
|
||||
|
||||
<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"));
|
||||
|
@ -190,9 +223,25 @@
|
|||
{
|
||||
await UserService.ChangePassword(User!, NewPassword, true);
|
||||
NewPassword = "";
|
||||
|
||||
|
||||
await SessionServerService.ReloadUserSessions(User!);
|
||||
|
||||
|
||||
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"));
|
||||
}
|
||||
}
|
|
@ -9,50 +9,50 @@
|
|||
@inject UserRepository UserRepository
|
||||
@inject SmartTranslateService SmartTranslateService
|
||||
|
||||
<OnlyAdmin>
|
||||
<AdminSessionNavigation Index="0"/>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminUsers))]
|
||||
|
||||
<div class="card">
|
||||
<LazyLoader Load="Load">
|
||||
<div class="card-header border-0 pt-5">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<TL>Users</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/users/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-user-plus"></i>
|
||||
<TL>New user</TL>
|
||||
</a>
|
||||
</div>
|
||||
<AdminSessionNavigation Index="0"/>
|
||||
|
||||
<div class="card">
|
||||
<LazyLoader Load="Load">
|
||||
<div class="card-header border-0 pt-5">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<TL>Users</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/users/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-user-plus"></i>
|
||||
<TL>New user</TL>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="User" Items="Users" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Email"))" Field="@(x => x.Email)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<a href="/admin/users/view/@(context.Id)">@(context.Email)</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("First name"))" Field="@(x => x.FirstName)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Last name"))" Field="@(x => x.LastName)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Created at"))" Field="@(x => x.CreatedAt)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/users/edit/@(context.Id)/">
|
||||
<TL>Manage</TL>
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="User" Items="Users" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Email"))" Field="@(x => x.Email)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<a href="/admin/users/view/@(context.Id)">@(context.Email)</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("First name"))" Field="@(x => x.FirstName)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Last name"))" Field="@(x => x.LastName)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Created at"))" Field="@(x => x.CreatedAt)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="User" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/users/edit/@(context.Id)/">
|
||||
<TL>Manage</TL>
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -8,50 +8,50 @@
|
|||
@inject ToastService ToastService
|
||||
@inject UserService UserService
|
||||
|
||||
<OnlyAdmin>
|
||||
<div class="row mb-5">
|
||||
<div class="card card-body p-10">
|
||||
<label class="form-label">
|
||||
<TL>First name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="User.FirstName" type="text" class="form-control">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Last name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="User.LastName" type="text" class="form-control">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Email</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="User.Email" type="email" class="form-control">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Password</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="User.Password" type="password" class="form-control">
|
||||
</div>
|
||||
@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>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="User.FirstName" type="text" class="form-control">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Last name</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="User.LastName" type="text" class="form-control">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Email</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="User.Email" type="email" class="form-control">
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Password</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<input @bind="User.Password" type="password" class="form-control">
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="card card-body">
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="/admin/users" class="btn btn-danger me-3">
|
||||
<TL>Cancel</TL>
|
||||
</a>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Create"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Creating"))"
|
||||
CssClasses="btn-success"
|
||||
OnClick="Create">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="card card-body">
|
||||
<div class="d-flex justify-content-end">
|
||||
<a href="/admin/users" class="btn btn-danger me-3">
|
||||
<TL>Cancel</TL>
|
||||
</a>
|
||||
<WButton Text="@(SmartTranslateService.Translate("Create"))"
|
||||
WorkingText="@(SmartTranslateService.Translate("Creating"))"
|
||||
CssClasses="btn-success"
|
||||
OnClick="Create">
|
||||
</WButton>
|
||||
</div>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -13,91 +13,91 @@
|
|||
@inject AlertService AlertService
|
||||
@inject ToastService ToastService
|
||||
|
||||
<OnlyAdmin>
|
||||
<AdminSessionNavigation Index="1"/>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminUserSessions))]
|
||||
|
||||
<div class="card">
|
||||
<LazyLoader Load="Load">
|
||||
<div class="card-header border-0 pt-5">
|
||||
<h3 class="card-title align-items-start flex-column">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<TL>Sessions</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<button class="btn btn-sm btn-primary me-3" @onclick="Refresh">
|
||||
<i class="bx bx-revision"></i>
|
||||
<TL>Refresh</TL>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-warning" @onclick="MessageAll">
|
||||
<i class="bx bx-message-square-dots"></i>
|
||||
<TL>Send a message to all users</TL>
|
||||
</button>
|
||||
<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">
|
||||
<span class="card-label fw-bold fs-3 mb-1">
|
||||
<TL>Sessions</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<button class="btn btn-sm btn-primary me-3" @onclick="Refresh">
|
||||
<i class="bx bx-revision"></i>
|
||||
<TL>Refresh</TL>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-warning" @onclick="MessageAll">
|
||||
<i class="bx bx-message-square-dots"></i>
|
||||
<TL>Send a message to all users</TL>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body pt-0">
|
||||
@if (AllSessions == null)
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<span class="spinner-border spinner-border-sm align-middle me-2"></span>
|
||||
<TL>Loading sessions</TL>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body pt-0">
|
||||
@if (AllSessions == null)
|
||||
{
|
||||
<div class="alert alert-info">
|
||||
<span class="spinner-border spinner-border-sm align-middle me-2"></span>
|
||||
<TL>Loading sessions</TL>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Table TableItem="SessionClientService" Items="AllSessions" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Email"))" Field="@(x => x.User.Email)" Sortable="true" Filterable="true" Width="20%">
|
||||
<Template>
|
||||
@if (context.User == null)
|
||||
{
|
||||
<TL>Guest</TL>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="/admin/users/view/@(context.User.Id)">@(context.User.Email)</a>
|
||||
}
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("IP"))" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
@(context.Ip)
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("URL"))" Field="@(x => x.NavigationManager.Uri)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Device"))" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
@(context.Device)
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Time"))" Field="@(x => x.CreateTimestamp)" Sortable="true" Filterable="true" Width="10%">
|
||||
<Template>
|
||||
@{
|
||||
var time = Formatter.FormatUptime((DateTime.UtcNow - context.CreateTimestamp).TotalMilliseconds);
|
||||
}
|
||||
<span>@(time)</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Actions"))" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<button @onclick="() => Navigate(context)" class="btn btn-sm btn-primary">
|
||||
<TL>Change url</TL>
|
||||
</button>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="SessionClientService" Title="" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<button @onclick="() => Message(context)" class="btn btn-sm btn-warning">
|
||||
<TL>Message</TL>
|
||||
</button>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
}
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
}
|
||||
else
|
||||
{
|
||||
<Table TableItem="SessionClientService" Items="AllSessions" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Email"))" Field="@(x => x.User.Email)" Sortable="true" Filterable="true" Width="20%">
|
||||
<Template>
|
||||
@if (context.User == null)
|
||||
{
|
||||
<TL>Guest</TL>
|
||||
}
|
||||
else
|
||||
{
|
||||
<a href="/admin/users/view/@(context.User.Id)">@(context.User.Email)</a>
|
||||
}
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("IP"))" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
@(context.Ip)
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("URL"))" Field="@(x => x.NavigationManager.Uri)" Sortable="true" Filterable="true" Width="10%"/>
|
||||
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Device"))" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
@(context.Device)
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Time"))" Field="@(x => x.CreateTimestamp)" Sortable="true" Filterable="true" Width="10%">
|
||||
<Template>
|
||||
@{
|
||||
var time = Formatter.FormatUptime((DateTime.UtcNow - context.CreateTimestamp).TotalMilliseconds);
|
||||
}
|
||||
<span>@(time)</span>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="SessionClientService" Title="@(SmartTranslateService.Translate("Actions"))" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<button @onclick="() => Navigate(context)" class="btn btn-sm btn-primary">
|
||||
<TL>Change url</TL>
|
||||
</button>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="SessionClientService" Title="" Field="@(x => x.Uuid)" Sortable="false" Filterable="false" Width="10%">
|
||||
<Template>
|
||||
<button @onclick="() => Message(context)" class="btn btn-sm btn-warning">
|
||||
<TL>Message</TL>
|
||||
</button>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
}
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -1,245 +1,199 @@
|
|||
@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)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<TL>No user with this id found</TL>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<div class="row">
|
||||
<div class="col-md-4">
|
||||
<div class="card card-body mb-5">
|
||||
<div class="d-flex flex-column align-items-center text-center">
|
||||
<img src="/api/moonlight/avatar/@(User.Id)" class="rounded-circle" alt="Profile picture" width="150">
|
||||
</div>
|
||||
@if (User == null)
|
||||
{
|
||||
<div class="alert alert-danger">
|
||||
<TL>No user with this id found</TL>
|
||||
</div>
|
||||
<div class="card card-body mb-5">
|
||||
<div class="btn-group">
|
||||
<a class="btn btn-primary" href="/admin/users/edit/@(User.Id)">
|
||||
<TL>Edit</TL>
|
||||
</a>
|
||||
<a class="btn btn-secondary" href="/admin/users">
|
||||
<TL>Back to list</TL>
|
||||
</a>
|
||||
<a class="btn btn-primary" href="/admin/support/view/@(User.Id)">
|
||||
<TL>Open support</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card-xl-stretch mb-5">
|
||||
<div class="card-header border-0">
|
||||
<h3 class="card-title fw-bold text-dark">
|
||||
<TL>Servers</TL>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body pt-2">
|
||||
@foreach (var server in Servers)
|
||||
{
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<a href="/server/@(server.Uuid)" class="fs-6">@(server.Name) - @(server.Image.Name)</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
if (server != Servers.Last())
|
||||
{
|
||||
<div class="separator my-4"></div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
<div class="card card-xl-stretch">
|
||||
<div class="card-header border-0">
|
||||
<h3 class="card-title fw-bold text-dark">
|
||||
<TL>Domains</TL>
|
||||
</h3>
|
||||
</div>
|
||||
<div class="card-body pt-2">
|
||||
@foreach (var domain in Domains)
|
||||
{
|
||||
<div class="d-flex align-items-center">
|
||||
<div class="flex-grow-1">
|
||||
<a href="/domain/@(domain.Id)" class="fs-6">@(domain.Name).@(domain.SharedDomain.Name)</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
if (domain != Domains.Last())
|
||||
{
|
||||
<div class="separator my-4"></div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-8">
|
||||
<div class="card mb-3">
|
||||
<div class="card-body fs-6">
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>First name</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.FirstName)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>Last name</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.LastName)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>Email</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.Email)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>Address</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.Address)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>City</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.City)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>State</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.State)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>Country</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">@(User.Country)</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="separator my-4"></div>
|
||||
<div class="row">
|
||||
<label class="col-lg-4 fw-semibold text-muted">
|
||||
<TL>Admin</TL>
|
||||
</label>
|
||||
<div class="col-lg-8">
|
||||
<span class="fw-bold fs-6 text-gray-800">
|
||||
}
|
||||
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)
|
||||
{
|
||||
<span>✅</span>
|
||||
<div class="mb-5">
|
||||
<div class="badge badge-lg badge-light-primary d-inline">
|
||||
<TL>Admin</TL>
|
||||
</div>
|
||||
</div>
|
||||
}
|
||||
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">
|
||||
</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>
|
||||
|
||||
</span>
|
||||
<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 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 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>
|
||||
</div>
|
||||
</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 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>
|
||||
</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">
|
||||
<div class="flex-grow-1">
|
||||
<a href="/server/@(server.Uuid)" class="fs-6">@(server.Name) - @(server.Image.Name)</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
if (server != Servers.Last())
|
||||
{
|
||||
<div class="separator my-4"></div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</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 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">
|
||||
<div class="flex-grow-1">
|
||||
<a href="/domain/@(domain.Id)" class="fs-6">@(domain.Name).@(domain.SharedDomain.Name)</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
if (domain != Domains.Last())
|
||||
{
|
||||
<div class="separator my-4"></div>
|
||||
}
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<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>
|
||||
</div>
|
||||
</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;
|
||||
|
|
|
@ -11,62 +11,62 @@
|
|||
@inject Repository<WebSpace> WebSpaceRepository
|
||||
@inject WebSpaceService WebSpaceService
|
||||
|
||||
<OnlyAdmin>
|
||||
<AdminWebspacesNavigation Index="0"/>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminWebspaces))]
|
||||
|
||||
<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>Webspaces</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/webspaces/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-user-plus"></i>
|
||||
<TL>New webspace</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="WebSpace" Items="WebSpaces" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="WebSpace" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="WebSpace" Title="@(SmartTranslateService.Translate("Domain"))" Field="@(x => x.Domain)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<a href="/webspace/@(context.Id)/">
|
||||
@(context.Domain)
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="WebSpace" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/users/view/@(context.Owner.Id)">
|
||||
@(context.Owner.Email)
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="WebSpace" Title="@(SmartTranslateService.Translate("Cloud panel"))" Field="@(x => x.CloudPanel.Name)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<a href="/admin/webspaces/servers/edit/@(context.CloudPanel.Id)/">
|
||||
@(context.CloudPanel.Name)
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="WebSpace" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<DeleteButton Confirm="true" OnClick="() => Delete(context)">
|
||||
</DeleteButton>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
<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">
|
||||
<TL>Webspaces</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/webspaces/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-user-plus"></i>
|
||||
<TL>New webspace</TL>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
<div class="card-body">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="WebSpace" Items="WebSpaces" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="WebSpace" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="WebSpace" Title="@(SmartTranslateService.Translate("Domain"))" Field="@(x => x.Domain)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<a href="/webspace/@(context.Id)/">
|
||||
@(context.Domain)
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="WebSpace" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/users/view/@(context.Owner.Id)">
|
||||
@(context.Owner.Email)
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="WebSpace" Title="@(SmartTranslateService.Translate("Cloud panel"))" Field="@(x => x.CloudPanel.Name)" Sortable="true" Filterable="true">
|
||||
<Template>
|
||||
<a href="/admin/webspaces/servers/edit/@(context.CloudPanel.Id)/">
|
||||
@(context.CloudPanel.Name)
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="WebSpace" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<DeleteButton Confirm="true" OnClick="() => Delete(context)">
|
||||
</DeleteButton>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -9,35 +9,35 @@
|
|||
@inject UserRepository UserRepository
|
||||
@inject NavigationManager NavigationManager
|
||||
|
||||
<OnlyAdmin>
|
||||
<div class="card card-body p-10">
|
||||
<LazyLoader Load="Load">
|
||||
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
|
||||
<label class="form-label">
|
||||
<TL>Base domain</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputText @bind-Value="Model.BaseDomain" class="form-control"></InputText>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Owner</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<SmartDropdown @bind-Value="Model.User"
|
||||
Items="Users"
|
||||
DisplayFunc="@(x => x.Email)"
|
||||
SearchProp="@(x => x.Email)">
|
||||
</SmartDropdown>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn btn-primary float-end">
|
||||
<TL>Create</TL>
|
||||
</button>
|
||||
</div>
|
||||
</SmartForm>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminNewWebspace))]
|
||||
|
||||
<div class="card card-body p-10">
|
||||
<LazyLoader Load="Load">
|
||||
<SmartForm Model="Model" OnValidSubmit="OnValidSubmit">
|
||||
<label class="form-label">
|
||||
<TL>Base domain</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<InputText @bind-Value="Model.BaseDomain" class="form-control"></InputText>
|
||||
</div>
|
||||
<label class="form-label">
|
||||
<TL>Owner</TL>
|
||||
</label>
|
||||
<div class="input-group mb-5">
|
||||
<SmartDropdown @bind-Value="Model.User"
|
||||
Items="Users"
|
||||
DisplayFunc="@(x => x.Email)"
|
||||
SearchProp="@(x => x.Email)">
|
||||
</SmartDropdown>
|
||||
</div>
|
||||
<div>
|
||||
<button type="submit" class="btn btn-primary float-end">
|
||||
<TL>Create</TL>
|
||||
</button>
|
||||
</div>
|
||||
</SmartForm>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
|
@ -47,7 +47,7 @@
|
|||
private async Task OnValidSubmit()
|
||||
{
|
||||
await WebSpaceService.Create(Model.BaseDomain, Model.User);
|
||||
|
||||
|
||||
NavigationManager.NavigateTo("/admin/webspaces");
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -10,76 +10,76 @@
|
|||
@inject Repository<CloudPanel> CloudPanelRepository
|
||||
@inject WebSpaceService WebSpaceService
|
||||
|
||||
<OnlyAdmin>
|
||||
<AdminWebspacesNavigation Index="1"/>
|
||||
@attribute [PermissionRequired(nameof(Permissions.AdminWebspacesServers))]
|
||||
|
||||
<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>Cloud panels</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/webspaces/servers/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-user-plus"></i>
|
||||
<TL>New cloud panel</TL>
|
||||
</a>
|
||||
</div>
|
||||
<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">
|
||||
<TL>Cloud panels</TL>
|
||||
</span>
|
||||
</h3>
|
||||
<div class="card-toolbar">
|
||||
<a href="/admin/webspaces/servers/new" class="btn btn-sm btn-light-success">
|
||||
<i class="bx bx-user-plus"></i>
|
||||
<TL>New cloud panel</TL>
|
||||
</a>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="CloudPanel" Items="CloudPanels" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Host)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Api url"))" Field="@(x => x.ApiUrl)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
@if (OnlineCache.TryGetValue(context, out var value))
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||
<div class="table-responsive">
|
||||
<Table TableItem="CloudPanel" Items="CloudPanels" PageSize="25" TableClass="table table-row-bordered table-row-gray-100 align-middle gs-0 gy-3" TableHeadClass="fw-bold text-muted">
|
||||
<Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Host)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Api url"))" Field="@(x => x.ApiUrl)" Sortable="true" Filterable="true"/>
|
||||
<Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
@if (OnlineCache.TryGetValue(context, out var value))
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
if (value)
|
||||
{
|
||||
<span class="text-success">
|
||||
<TL>Online</TL>
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-danger">
|
||||
<TL>Offline</TL>
|
||||
</span>
|
||||
}
|
||||
<span class="text-success">
|
||||
<TL>Online</TL>
|
||||
</span>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">
|
||||
<TL>Loading</TL>
|
||||
<span class="text-danger">
|
||||
<TL>Offline</TL>
|
||||
</span>
|
||||
}
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Edit"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/webspaces/servers/edit/@(context.Id)/">
|
||||
<TL>Manage</TL>
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<DeleteButton Confirm="true" OnClick="() => OnClick(context)">
|
||||
</DeleteButton>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
}
|
||||
else
|
||||
{
|
||||
<span class="text-muted">
|
||||
<TL>Loading</TL>
|
||||
</span>
|
||||
}
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Edit"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<a href="/admin/webspaces/servers/edit/@(context.Id)/">
|
||||
<TL>Manage</TL>
|
||||
</a>
|
||||
</Template>
|
||||
</Column>
|
||||
<Column TableItem="CloudPanel" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false">
|
||||
<Template>
|
||||
<DeleteButton Confirm="true" OnClick="() => OnClick(context)">
|
||||
</DeleteButton>
|
||||
</Template>
|
||||
</Column>
|
||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||
</Table>
|
||||
</div>
|
||||
</LazyLoader>
|
||||
</div>
|
||||
</OnlyAdmin>
|
||||
</div>
|
||||
|
||||
@code
|
||||
{
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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
Loading…
Reference in a new issue