Compare commits
45 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
11708fbc3b | ||
|
|
daeb4dd5b9 | ||
|
|
daba4cba04 | ||
|
|
1cd0f0f96f | ||
|
|
6a30db07a7 | ||
|
|
6c8754d008 | ||
|
|
356ba94592 | ||
|
|
d3b55d155b | ||
|
|
0015001d7c | ||
|
|
0a86aa8aa4 | ||
|
|
74d4ee729d | ||
|
|
178ff36e86 | ||
|
|
f852df5807 | ||
|
|
90f4b04857 | ||
|
|
244e87ed18 | ||
|
|
80ea5a543f | ||
|
|
5baba05f5f | ||
|
|
591da6de5c | ||
|
|
c1ddff4ae3 | ||
|
|
67d78d7104 | ||
|
|
52f4b00f84 | ||
|
|
8f028e2ac6 | ||
|
|
5bd6f15203 | ||
|
|
4c39ad6170 | ||
|
|
12392d4f47 | ||
|
|
b75147e4c0 | ||
|
|
8f9508f30b | ||
|
|
428e2668d3 | ||
|
|
c1cfb35c86 | ||
|
|
d6777c463e | ||
|
|
f9126bffe0 | ||
|
|
0488e83a38 | ||
|
|
d87ddc90e3 | ||
|
|
151bc82998 | ||
|
|
e4c21c74a5 | ||
|
|
13741a2be9 | ||
|
|
c866e89b72 | ||
|
|
8be93bc53c | ||
|
|
384b6a3e7d | ||
|
|
ba2de54c60 | ||
|
|
bd5567e24f | ||
|
|
b8e39824b5 | ||
|
|
d8c9bdbd8d | ||
|
|
80eb210af0 | ||
|
|
c0df8ac507 |
9
.gitattributes
vendored
9
.gitattributes
vendored
@@ -1,3 +1,10 @@
|
|||||||
# Auto detect text files and perform LF normalization
|
# Auto detect text files and perform LF normalization
|
||||||
* text=auto
|
* text=auto
|
||||||
Moonlight/wwwroot/* linguist-vendored
|
Moonlight/wwwroot/** linguist-vendored
|
||||||
|
Moonlight/wwwroot/assets/js/scripts.bundle.js linguist-vendored
|
||||||
|
Moonlight/wwwroot/assets/js/widgets.bundle.js linguist-vendored
|
||||||
|
Moonlight/wwwroot/assets/js/theme.js linguist-vendored
|
||||||
|
Moonlight/wwwroot/assets/css/boxicons.min.css linguist-vendored
|
||||||
|
Moonlight/wwwroot/assets/css/style.bundle.css linguist-vendored
|
||||||
|
Moonlight/wwwroot/assets/plugins/** linguist-vendored
|
||||||
|
Moonlight/wwwroot/assets/fonts/** linguist-vendored
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ public class ConfigV1
|
|||||||
[Description("The url moonlight is accesible with from the internet")]
|
[Description("The url moonlight is accesible with from the internet")]
|
||||||
public string AppUrl { get; set; } = "http://your-moonlight-url-without-slash";
|
public string AppUrl { get; set; } = "http://your-moonlight-url-without-slash";
|
||||||
|
|
||||||
|
[JsonProperty("Auth")] public AuthData Auth { get; set; } = new();
|
||||||
|
|
||||||
[JsonProperty("Database")] public DatabaseData Database { get; set; } = new();
|
[JsonProperty("Database")] public DatabaseData Database { get; set; } = new();
|
||||||
|
|
||||||
[JsonProperty("DiscordBotApi")] public DiscordBotApiData DiscordBotApi { get; set; } = new();
|
[JsonProperty("DiscordBotApi")] public DiscordBotApiData DiscordBotApi { get; set; } = new();
|
||||||
@@ -37,10 +39,7 @@ public class ConfigV1
|
|||||||
|
|
||||||
[JsonProperty("Cleanup")] public CleanupData Cleanup { get; set; } = new();
|
[JsonProperty("Cleanup")] public CleanupData Cleanup { get; set; } = new();
|
||||||
|
|
||||||
[JsonProperty("Subscriptions")] public SubscriptionsData Subscriptions { get; set; } = new();
|
[JsonProperty("DiscordNotifications")] public DiscordNotificationsData DiscordNotifications { get; set; } = new();
|
||||||
|
|
||||||
[JsonProperty("DiscordNotifications")]
|
|
||||||
public DiscordNotificationsData DiscordNotifications { get; set; } = new();
|
|
||||||
|
|
||||||
[JsonProperty("Statistics")] public StatisticsData Statistics { get; set; } = new();
|
[JsonProperty("Statistics")] public StatisticsData Statistics { get; set; } = new();
|
||||||
|
|
||||||
@@ -49,6 +48,26 @@ public class ConfigV1
|
|||||||
[JsonProperty("SmartDeploy")] public SmartDeployData SmartDeploy { get; set; } = new();
|
[JsonProperty("SmartDeploy")] public SmartDeployData SmartDeploy { get; set; } = new();
|
||||||
|
|
||||||
[JsonProperty("Sentry")] public SentryData Sentry { get; set; } = new();
|
[JsonProperty("Sentry")] public SentryData Sentry { get; set; } = new();
|
||||||
|
|
||||||
|
[JsonProperty("Stripe")] public StripeData Stripe { get; set; } = new();
|
||||||
|
}
|
||||||
|
|
||||||
|
public class StripeData
|
||||||
|
{
|
||||||
|
[JsonProperty("ApiKey")]
|
||||||
|
[Description("Put here your stripe api key if you add subscriptions. Currently the only billing option is stripe which is enabled by default and cannot be turned off. This feature is still experimental")]
|
||||||
|
public string ApiKey { get; set; } = "";
|
||||||
|
}
|
||||||
|
|
||||||
|
public class AuthData
|
||||||
|
{
|
||||||
|
[JsonProperty("DenyLogin")]
|
||||||
|
[Description("Prevent every new login")]
|
||||||
|
public bool DenyLogin { get; set; } = false;
|
||||||
|
|
||||||
|
[JsonProperty("DenyRegister")]
|
||||||
|
[Description("Prevent every new user to register")]
|
||||||
|
public bool DenyRegister { get; set; } = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class CleanupData
|
public class CleanupData
|
||||||
@@ -306,11 +325,6 @@ public class ConfigV1
|
|||||||
[JsonProperty("Wait")] public long Wait { get; set; } = 15;
|
[JsonProperty("Wait")] public long Wait { get; set; } = 15;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class SubscriptionsData
|
|
||||||
{
|
|
||||||
[JsonProperty("SellPass")] public SellPassData SellPass { get; set; } = new();
|
|
||||||
}
|
|
||||||
|
|
||||||
public class SellPassData
|
public class SellPassData
|
||||||
{
|
{
|
||||||
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
|
[JsonProperty("Enable")] public bool Enable { get; set; } = false;
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Database.Entities.LogsEntries;
|
|
||||||
using Moonlight.App.Database.Entities.Notification;
|
using Moonlight.App.Database.Entities.Notification;
|
||||||
using Moonlight.App.Database.Interceptors;
|
using Moonlight.App.Database.Interceptors;
|
||||||
using Moonlight.App.Models.Misc;
|
|
||||||
using Moonlight.App.Services;
|
using Moonlight.App.Services;
|
||||||
|
|
||||||
namespace Moonlight.App.Database;
|
namespace Moonlight.App.Database;
|
||||||
@@ -27,10 +25,6 @@ public class DataContext : DbContext
|
|||||||
public DbSet<ServerVariable> ServerVariables { get; set; }
|
public DbSet<ServerVariable> ServerVariables { get; set; }
|
||||||
public DbSet<User> Users { get; set; }
|
public DbSet<User> Users { get; set; }
|
||||||
public DbSet<LoadingMessage> LoadingMessages { get; set; }
|
public DbSet<LoadingMessage> LoadingMessages { get; set; }
|
||||||
public DbSet<AuditLogEntry> AuditLog { get; set; }
|
|
||||||
public DbSet<ErrorLogEntry> ErrorLog { get; set; }
|
|
||||||
public DbSet<SecurityLogEntry> SecurityLog { get; set; }
|
|
||||||
|
|
||||||
public DbSet<SharedDomain> SharedDomains { get; set; }
|
public DbSet<SharedDomain> SharedDomains { get; set; }
|
||||||
public DbSet<Domain> Domains { get; set; }
|
public DbSet<Domain> Domains { get; set; }
|
||||||
public DbSet<Revoke> Revokes { get; set; }
|
public DbSet<Revoke> Revokes { get; set; }
|
||||||
@@ -46,6 +40,8 @@ public class DataContext : DbContext
|
|||||||
public DbSet<WebSpace> WebSpaces { get; set; }
|
public DbSet<WebSpace> WebSpaces { get; set; }
|
||||||
public DbSet<SupportChatMessage> SupportChatMessages { get; set; }
|
public DbSet<SupportChatMessage> SupportChatMessages { get; set; }
|
||||||
public DbSet<IpBan> IpBans { get; set; }
|
public DbSet<IpBan> IpBans { get; set; }
|
||||||
|
public DbSet<PermissionGroup> PermissionGroups { get; set; }
|
||||||
|
public DbSet<SecurityLog> SecurityLogs { get; set; }
|
||||||
|
|
||||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
using Moonlight.App.Models.Misc;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Database.Entities.LogsEntries;
|
|
||||||
|
|
||||||
public class AuditLogEntry
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public AuditLogType Type { get; set; }
|
|
||||||
public string JsonData { get; set; } = "";
|
|
||||||
public bool System { get; set; }
|
|
||||||
public string Ip { get; set; } = "";
|
|
||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
namespace Moonlight.App.Database.Entities.LogsEntries;
|
|
||||||
|
|
||||||
public class ErrorLogEntry
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public string Stacktrace { get; set; } = "";
|
|
||||||
public bool System { get; set; }
|
|
||||||
public string JsonData { get; set; } = "";
|
|
||||||
public string Ip { get; set; } = "";
|
|
||||||
public string Class { get; set; } = "";
|
|
||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
using Moonlight.App.Models.Misc;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Database.Entities.LogsEntries;
|
|
||||||
|
|
||||||
public class SecurityLogEntry
|
|
||||||
{
|
|
||||||
public int Id { get; set; }
|
|
||||||
public bool System { get; set; }
|
|
||||||
public string Ip { get; set; } = "";
|
|
||||||
public SecurityLogType Type { get; set; }
|
|
||||||
public string JsonData { get; set; } = "";
|
|
||||||
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
|
||||||
}
|
|
||||||
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>();
|
||||||
|
}
|
||||||
8
Moonlight/App/Database/Entities/SecurityLog.cs
Normal file
8
Moonlight/App/Database/Entities/SecurityLog.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
namespace Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
|
public class SecurityLog
|
||||||
|
{
|
||||||
|
public int Id { get; set; }
|
||||||
|
public string Text { get; set; } = "";
|
||||||
|
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
|
||||||
|
}
|
||||||
@@ -1,9 +1,16 @@
|
|||||||
namespace Moonlight.App.Database.Entities;
|
using Moonlight.App.Models.Misc;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
public class Subscription
|
public class Subscription
|
||||||
{
|
{
|
||||||
public int Id { get; set; }
|
public int Id { get; set; }
|
||||||
public string Name { get; set; } = "";
|
public string Name { get; set; } = "";
|
||||||
public string Description { get; set; } = "";
|
public string Description { get; set; } = "";
|
||||||
|
public Currency Currency { get; set; } = Currency.USD;
|
||||||
|
public double Price { get; set; }
|
||||||
|
public string StripeProductId { get; set; } = "";
|
||||||
|
public string StripePriceId { get; set; } = "";
|
||||||
public string LimitsJson { get; set; } = "";
|
public string LimitsJson { get; set; } = "";
|
||||||
|
public int Duration { get; set; } = 30;
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
using Moonlight.App.Models.Misc;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Moonlight.App.Models.Misc;
|
||||||
|
|
||||||
namespace Moonlight.App.Database.Entities;
|
namespace Moonlight.App.Database.Entities;
|
||||||
|
|
||||||
@@ -39,6 +40,8 @@ public class User
|
|||||||
public bool TotpEnabled { get; set; } = false;
|
public bool TotpEnabled { get; set; } = false;
|
||||||
public string TotpSecret { get; set; } = "";
|
public string TotpSecret { get; set; } = "";
|
||||||
public DateTime TokenValidTime { get; set; } = DateTime.UtcNow;
|
public DateTime TokenValidTime { get; set; } = DateTime.UtcNow;
|
||||||
|
public byte[] Permissions { get; set; } = Array.Empty<byte>();
|
||||||
|
public PermissionGroup? PermissionGroup { get; set; }
|
||||||
|
|
||||||
// Discord
|
// Discord
|
||||||
public ulong DiscordId { get; set; }
|
public ulong DiscordId { get; set; }
|
||||||
@@ -51,8 +54,8 @@ public class User
|
|||||||
// Subscriptions
|
// Subscriptions
|
||||||
|
|
||||||
public Subscription? CurrentSubscription { get; set; } = null;
|
public Subscription? CurrentSubscription { get; set; } = null;
|
||||||
public DateTime SubscriptionSince { get; set; } = DateTime.Now;
|
public DateTime SubscriptionSince { get; set; } = DateTime.UtcNow;
|
||||||
public int SubscriptionDuration { get; set; }
|
public DateTime SubscriptionExpires { get; set; } = DateTime.UtcNow;
|
||||||
|
|
||||||
// Ip logs
|
// Ip logs
|
||||||
public string RegisterIp { get; set; } = "";
|
public string RegisterIp { get; set; } = "";
|
||||||
|
|||||||
1105
Moonlight/App/Database/Migrations/20230705171914_AddedStripeIntegration.Designer.cs
generated
Normal file
1105
Moonlight/App/Database/Migrations/20230705171914_AddedStripeIntegration.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,96 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class AddedStripeIntegration : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "SubscriptionDuration",
|
||||||
|
table: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<DateTime>(
|
||||||
|
name: "SubscriptionExpires",
|
||||||
|
table: "Users",
|
||||||
|
type: "datetime(6)",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: new DateTime(1, 1, 1, 0, 0, 0, 0, DateTimeKind.Unspecified));
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "Currency",
|
||||||
|
table: "Subscriptions",
|
||||||
|
type: "int",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "Duration",
|
||||||
|
table: "Subscriptions",
|
||||||
|
type: "int",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<double>(
|
||||||
|
name: "Price",
|
||||||
|
table: "Subscriptions",
|
||||||
|
type: "double",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0.0);
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "StripePriceId",
|
||||||
|
table: "Subscriptions",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<string>(
|
||||||
|
name: "StripeProductId",
|
||||||
|
table: "Subscriptions",
|
||||||
|
type: "longtext",
|
||||||
|
nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "SubscriptionExpires",
|
||||||
|
table: "Users");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Currency",
|
||||||
|
table: "Subscriptions");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Duration",
|
||||||
|
table: "Subscriptions");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "Price",
|
||||||
|
table: "Subscriptions");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "StripePriceId",
|
||||||
|
table: "Subscriptions");
|
||||||
|
|
||||||
|
migrationBuilder.DropColumn(
|
||||||
|
name: "StripeProductId",
|
||||||
|
table: "Subscriptions");
|
||||||
|
|
||||||
|
migrationBuilder.AddColumn<int>(
|
||||||
|
name: "SubscriptionDuration",
|
||||||
|
table: "Users",
|
||||||
|
type: "int",
|
||||||
|
nullable: false,
|
||||||
|
defaultValue: 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
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");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1068
Moonlight/App/Database/Migrations/20230718123232_RemovedOldLogsAndAddedErrorLog.Designer.cs
generated
Normal file
1068
Moonlight/App/Database/Migrations/20230718123232_RemovedOldLogsAndAddedErrorLog.Designer.cs
generated
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,111 @@
|
|||||||
|
using System;
|
||||||
|
using Microsoft.EntityFrameworkCore.Metadata;
|
||||||
|
using Microsoft.EntityFrameworkCore.Migrations;
|
||||||
|
|
||||||
|
#nullable disable
|
||||||
|
|
||||||
|
namespace Moonlight.App.Database.Migrations
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
public partial class RemovedOldLogsAndAddedErrorLog : Migration
|
||||||
|
{
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Up(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "AuditLog");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "ErrorLog");
|
||||||
|
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "SecurityLog");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "SecurityLogs",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Text = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_SecurityLogs", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc />
|
||||||
|
protected override void Down(MigrationBuilder migrationBuilder)
|
||||||
|
{
|
||||||
|
migrationBuilder.DropTable(
|
||||||
|
name: "SecurityLogs");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "AuditLog",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||||
|
Ip = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
JsonData = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
System = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||||
|
Type = table.Column<int>(type: "int", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_AuditLog", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "ErrorLog",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
Class = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||||
|
Ip = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
JsonData = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
Stacktrace = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
System = table.Column<bool>(type: "tinyint(1)", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_ErrorLog", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
|
||||||
|
migrationBuilder.CreateTable(
|
||||||
|
name: "SecurityLog",
|
||||||
|
columns: table => new
|
||||||
|
{
|
||||||
|
Id = table.Column<int>(type: "int", nullable: false)
|
||||||
|
.Annotation("MySql:ValueGenerationStrategy", MySqlValueGenerationStrategy.IdentityColumn),
|
||||||
|
CreatedAt = table.Column<DateTime>(type: "datetime(6)", nullable: false),
|
||||||
|
Ip = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
JsonData = table.Column<string>(type: "longtext", nullable: false)
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4"),
|
||||||
|
System = table.Column<bool>(type: "tinyint(1)", nullable: false),
|
||||||
|
Type = table.Column<int>(type: "int", nullable: false)
|
||||||
|
},
|
||||||
|
constraints: table =>
|
||||||
|
{
|
||||||
|
table.PrimaryKey("PK_SecurityLog", x => x.Id);
|
||||||
|
})
|
||||||
|
.Annotation("MySql:CharSet", "utf8mb4");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -241,95 +241,6 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.ToTable("LoadingMessages");
|
b.ToTable("LoadingMessages");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.AuditLogEntry", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
|
||||||
.HasColumnType("datetime(6)");
|
|
||||||
|
|
||||||
b.Property<string>("Ip")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<string>("JsonData")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<bool>("System")
|
|
||||||
.HasColumnType("tinyint(1)");
|
|
||||||
|
|
||||||
b.Property<int>("Type")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("AuditLog");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.ErrorLogEntry", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<string>("Class")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
|
||||||
.HasColumnType("datetime(6)");
|
|
||||||
|
|
||||||
b.Property<string>("Ip")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<string>("JsonData")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<string>("Stacktrace")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<bool>("System")
|
|
||||||
.HasColumnType("tinyint(1)");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("ErrorLog");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.LogsEntries.SecurityLogEntry", b =>
|
|
||||||
{
|
|
||||||
b.Property<int>("Id")
|
|
||||||
.ValueGeneratedOnAdd()
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.Property<DateTime>("CreatedAt")
|
|
||||||
.HasColumnType("datetime(6)");
|
|
||||||
|
|
||||||
b.Property<string>("Ip")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<string>("JsonData")
|
|
||||||
.IsRequired()
|
|
||||||
.HasColumnType("longtext");
|
|
||||||
|
|
||||||
b.Property<bool>("System")
|
|
||||||
.HasColumnType("tinyint(1)");
|
|
||||||
|
|
||||||
b.Property<int>("Type")
|
|
||||||
.HasColumnType("int");
|
|
||||||
|
|
||||||
b.HasKey("Id");
|
|
||||||
|
|
||||||
b.ToTable("SecurityLog");
|
|
||||||
});
|
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.MySqlDatabase", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -475,6 +386,25 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.ToTable("NotificationClients");
|
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 =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.Revoke", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -490,6 +420,24 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.ToTable("Revokes");
|
b.ToTable("Revokes");
|
||||||
});
|
});
|
||||||
|
|
||||||
|
modelBuilder.Entity("Moonlight.App.Database.Entities.SecurityLog", b =>
|
||||||
|
{
|
||||||
|
b.Property<int>("Id")
|
||||||
|
.ValueGeneratedOnAdd()
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<DateTime>("CreatedAt")
|
||||||
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
|
b.Property<string>("Text")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.HasKey("Id");
|
||||||
|
|
||||||
|
b.ToTable("SecurityLogs");
|
||||||
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b =>
|
modelBuilder.Entity("Moonlight.App.Database.Entities.Server", b =>
|
||||||
{
|
{
|
||||||
b.Property<int>("Id")
|
b.Property<int>("Id")
|
||||||
@@ -663,10 +611,16 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
.ValueGeneratedOnAdd()
|
.ValueGeneratedOnAdd()
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<int>("Currency")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<string>("Description")
|
b.Property<string>("Description")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int>("Duration")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
b.Property<string>("LimitsJson")
|
b.Property<string>("LimitsJson")
|
||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
@@ -675,6 +629,17 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<double>("Price")
|
||||||
|
.HasColumnType("double");
|
||||||
|
|
||||||
|
b.Property<string>("StripePriceId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<string>("StripeProductId")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
b.HasKey("Id");
|
b.HasKey("Id");
|
||||||
|
|
||||||
b.ToTable("Subscriptions");
|
b.ToTable("Subscriptions");
|
||||||
@@ -781,6 +746,13 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
.IsRequired()
|
.IsRequired()
|
||||||
.HasColumnType("longtext");
|
.HasColumnType("longtext");
|
||||||
|
|
||||||
|
b.Property<int?>("PermissionGroupId")
|
||||||
|
.HasColumnType("int");
|
||||||
|
|
||||||
|
b.Property<byte[]>("Permissions")
|
||||||
|
.IsRequired()
|
||||||
|
.HasColumnType("longblob");
|
||||||
|
|
||||||
b.Property<int>("Rating")
|
b.Property<int>("Rating")
|
||||||
.HasColumnType("int");
|
.HasColumnType("int");
|
||||||
|
|
||||||
@@ -802,8 +774,8 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
b.Property<bool>("StreamerMode")
|
b.Property<bool>("StreamerMode")
|
||||||
.HasColumnType("tinyint(1)");
|
.HasColumnType("tinyint(1)");
|
||||||
|
|
||||||
b.Property<int>("SubscriptionDuration")
|
b.Property<DateTime>("SubscriptionExpires")
|
||||||
.HasColumnType("int");
|
.HasColumnType("datetime(6)");
|
||||||
|
|
||||||
b.Property<DateTime>("SubscriptionSince")
|
b.Property<DateTime>("SubscriptionSince")
|
||||||
.HasColumnType("datetime(6)");
|
.HasColumnType("datetime(6)");
|
||||||
@@ -828,6 +800,8 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
|
|
||||||
b.HasIndex("CurrentSubscriptionId");
|
b.HasIndex("CurrentSubscriptionId");
|
||||||
|
|
||||||
|
b.HasIndex("PermissionGroupId");
|
||||||
|
|
||||||
b.ToTable("Users");
|
b.ToTable("Users");
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1032,7 +1006,13 @@ namespace Moonlight.App.Database.Migrations
|
|||||||
.WithMany()
|
.WithMany()
|
||||||
.HasForeignKey("CurrentSubscriptionId");
|
.HasForeignKey("CurrentSubscriptionId");
|
||||||
|
|
||||||
|
b.HasOne("Moonlight.App.Database.Entities.PermissionGroup", "PermissionGroup")
|
||||||
|
.WithMany()
|
||||||
|
.HasForeignKey("PermissionGroupId");
|
||||||
|
|
||||||
b.Navigation("CurrentSubscription");
|
b.Navigation("CurrentSubscription");
|
||||||
|
|
||||||
|
b.Navigation("PermissionGroup");
|
||||||
});
|
});
|
||||||
|
|
||||||
modelBuilder.Entity("Moonlight.App.Database.Entities.WebSpace", b =>
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
71
Moonlight/App/Helpers/EggConverter.cs
Normal file
71
Moonlight/App/Helpers/EggConverter.cs
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
using System.Text;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Newtonsoft.Json;
|
||||||
|
using Newtonsoft.Json.Linq;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
public static class EggConverter
|
||||||
|
{
|
||||||
|
public static Image Convert(string json)
|
||||||
|
{
|
||||||
|
var result = new Image();
|
||||||
|
|
||||||
|
var data = new ConfigurationBuilder().AddJsonStream(
|
||||||
|
new MemoryStream(Encoding.ASCII.GetBytes(json))
|
||||||
|
).Build();
|
||||||
|
|
||||||
|
result.Allocations = 1;
|
||||||
|
result.Description = data.GetValue<string>("description") ?? "";
|
||||||
|
result.Uuid = Guid.NewGuid();
|
||||||
|
result.Startup = data.GetValue<string>("startup") ?? "";
|
||||||
|
result.Name = data.GetValue<string>("name") ?? "Ptero Egg";
|
||||||
|
|
||||||
|
foreach (var variable in data.GetSection("variables").GetChildren())
|
||||||
|
{
|
||||||
|
result.Variables.Add(new()
|
||||||
|
{
|
||||||
|
Key = variable.GetValue<string>("env_variable") ?? "",
|
||||||
|
DefaultValue = variable.GetValue<string>("default_value") ?? ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
var configData = data.GetSection("config");
|
||||||
|
|
||||||
|
result.ConfigFiles = configData.GetValue<string>("files") ?? "{}";
|
||||||
|
|
||||||
|
var dImagesData = JObject.Parse(json);
|
||||||
|
var dImages = (JObject)dImagesData["docker_images"]!;
|
||||||
|
|
||||||
|
foreach (var dockerImage in dImages)
|
||||||
|
{
|
||||||
|
var di = new DockerImage()
|
||||||
|
{
|
||||||
|
Default = dockerImage.Key == dImages.Properties().Last().Name,
|
||||||
|
Name = dockerImage.Value!.ToString()
|
||||||
|
};
|
||||||
|
|
||||||
|
result.DockerImages.Add(di);
|
||||||
|
}
|
||||||
|
|
||||||
|
var installSection = data.GetSection("scripts").GetSection("installation");
|
||||||
|
|
||||||
|
result.InstallEntrypoint = installSection.GetValue<string>("entrypoint") ?? "bash";
|
||||||
|
result.InstallScript = installSection.GetValue<string>("script") ?? "";
|
||||||
|
result.InstallDockerImage = installSection.GetValue<string>("container") ?? "";
|
||||||
|
|
||||||
|
var rawJson = configData.GetValue<string>("startup");
|
||||||
|
|
||||||
|
var startupData = new ConfigurationBuilder().AddJsonStream(
|
||||||
|
new MemoryStream(Encoding.ASCII.GetBytes(rawJson!))
|
||||||
|
).Build();
|
||||||
|
|
||||||
|
result.StartupDetection = startupData.GetValue<string>("done", "") ?? "";
|
||||||
|
result.StopCommand = configData.GetValue<string>("stop") ?? "";
|
||||||
|
|
||||||
|
result.TagsJson = "[]";
|
||||||
|
result.BackgroundImageUrl = "";
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,46 +1,70 @@
|
|||||||
using System.Diagnostics;
|
using System.Diagnostics;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
|
using Moonlight.App.Database;
|
||||||
|
using Moonlight.App.Services;
|
||||||
|
using Moonlight.App.Services.Files;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
|
|
||||||
namespace Moonlight.App.Helpers;
|
namespace Moonlight.App.Helpers;
|
||||||
|
|
||||||
public static class Logger
|
public static class Logger
|
||||||
{
|
{
|
||||||
|
// The private static instance of the config service, because we have no di here
|
||||||
|
private static ConfigService ConfigService = new(new StorageService());
|
||||||
|
|
||||||
#region String method calls
|
#region String method calls
|
||||||
public static void Verbose(string message, string channel = "default")
|
public static void Verbose(string message, string channel = "default")
|
||||||
{
|
{
|
||||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
.Verbose("{Message}", message);
|
.Verbose("{Message}", message);
|
||||||
|
|
||||||
|
if(channel == "security")
|
||||||
|
LogSecurityInDb(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Info(string message, string channel = "default")
|
public static void Info(string message, string channel = "default")
|
||||||
{
|
{
|
||||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
.Information("{Message}", message);
|
.Information("{Message}", message);
|
||||||
|
|
||||||
|
if(channel == "security")
|
||||||
|
LogSecurityInDb(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Debug(string message, string channel = "default")
|
public static void Debug(string message, string channel = "default")
|
||||||
{
|
{
|
||||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
.Debug("{Message}", message);
|
.Debug("{Message}", message);
|
||||||
|
|
||||||
|
if(channel == "security")
|
||||||
|
LogSecurityInDb(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Error(string message, string channel = "default")
|
public static void Error(string message, string channel = "default")
|
||||||
{
|
{
|
||||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
.Error("{Message}", message);
|
.Error("{Message}", message);
|
||||||
|
|
||||||
|
if(channel == "security")
|
||||||
|
LogSecurityInDb(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Warn(string message, string channel = "default")
|
public static void Warn(string message, string channel = "default")
|
||||||
{
|
{
|
||||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
.Warning("{Message}", message);
|
.Warning("{Message}", message);
|
||||||
|
|
||||||
|
if(channel == "security")
|
||||||
|
LogSecurityInDb(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Fatal(string message, string channel = "default")
|
public static void Fatal(string message, string channel = "default")
|
||||||
{
|
{
|
||||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
.Fatal("{Message}", message);
|
.Fatal("{Message}", message);
|
||||||
|
|
||||||
|
if(channel == "security")
|
||||||
|
LogSecurityInDb(message);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -49,36 +73,54 @@ public static class Logger
|
|||||||
{
|
{
|
||||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
.Verbose(exception, "");
|
.Verbose(exception, "");
|
||||||
|
|
||||||
|
if(channel == "security")
|
||||||
|
LogSecurityInDb(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Info(Exception exception, string channel = "default")
|
public static void Info(Exception exception, string channel = "default")
|
||||||
{
|
{
|
||||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
.Information(exception, "");
|
.Information(exception, "");
|
||||||
|
|
||||||
|
if(channel == "security")
|
||||||
|
LogSecurityInDb(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Debug(Exception exception, string channel = "default")
|
public static void Debug(Exception exception, string channel = "default")
|
||||||
{
|
{
|
||||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
.Debug(exception, "");
|
.Debug(exception, "");
|
||||||
|
|
||||||
|
if(channel == "security")
|
||||||
|
LogSecurityInDb(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Error(Exception exception, string channel = "default")
|
public static void Error(Exception exception, string channel = "default")
|
||||||
{
|
{
|
||||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
.Error(exception, "");
|
.Error(exception, "");
|
||||||
|
|
||||||
|
if(channel == "security")
|
||||||
|
LogSecurityInDb(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Warn(Exception exception, string channel = "default")
|
public static void Warn(Exception exception, string channel = "default")
|
||||||
{
|
{
|
||||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
.Warning(exception, "");
|
.Warning(exception, "");
|
||||||
|
|
||||||
|
if(channel == "security")
|
||||||
|
LogSecurityInDb(exception);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void Fatal(Exception exception, string channel = "default")
|
public static void Fatal(Exception exception, string channel = "default")
|
||||||
{
|
{
|
||||||
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
Log.ForContext("SourceContext", GetNameOfCallingClass())
|
||||||
.Fatal(exception, "");
|
.Fatal(exception, "");
|
||||||
|
|
||||||
|
if(channel == "security")
|
||||||
|
LogSecurityInDb(exception);
|
||||||
}
|
}
|
||||||
#endregion
|
#endregion
|
||||||
|
|
||||||
@@ -105,4 +147,25 @@ public static class Logger
|
|||||||
|
|
||||||
return fullName;
|
return fullName;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private static void LogSecurityInDb(Exception exception)
|
||||||
|
{
|
||||||
|
LogSecurityInDb(exception.ToStringDemystified());
|
||||||
|
}
|
||||||
|
private static void LogSecurityInDb(string text)
|
||||||
|
{
|
||||||
|
Task.Run(() =>
|
||||||
|
{
|
||||||
|
var dataContext = new DataContext(ConfigService);
|
||||||
|
|
||||||
|
dataContext.SecurityLogs.Add(new()
|
||||||
|
{
|
||||||
|
Text = text
|
||||||
|
});
|
||||||
|
|
||||||
|
dataContext.SaveChanges();
|
||||||
|
dataContext.Dispose();
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -25,7 +25,7 @@ public class AvatarController : Controller
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var url = GravatarController.GetImageUrl(user.Email, 100);
|
var url = GravatarController.GetImageUrl(user.Email.ToLower(), 100);
|
||||||
|
|
||||||
using var client = new HttpClient();
|
using var client = new HttpClient();
|
||||||
var res = await client.GetByteArrayAsync(url);
|
var res = await client.GetByteArrayAsync(url);
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
using Microsoft.AspNetCore.Mvc;
|
||||||
|
using Moonlight.App.Services;
|
||||||
|
using Moonlight.App.Services.Sessions;
|
||||||
|
using Stripe;
|
||||||
|
using Stripe.Checkout;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Http.Controllers.Api.Moonlight;
|
||||||
|
|
||||||
|
[ApiController]
|
||||||
|
[Route("api/moonlight/billing")]
|
||||||
|
public class BillingController : Controller
|
||||||
|
{
|
||||||
|
private readonly IdentityService IdentityService;
|
||||||
|
private readonly BillingService BillingService;
|
||||||
|
|
||||||
|
public BillingController(
|
||||||
|
IdentityService identityService,
|
||||||
|
BillingService billingService)
|
||||||
|
{
|
||||||
|
IdentityService = identityService;
|
||||||
|
BillingService = billingService;
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("cancel")]
|
||||||
|
public async Task<ActionResult> Cancel()
|
||||||
|
{
|
||||||
|
var user = IdentityService.User;
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
return Redirect("/login");
|
||||||
|
|
||||||
|
return Redirect("/profile/subscriptions/close");
|
||||||
|
}
|
||||||
|
|
||||||
|
[HttpGet("success")]
|
||||||
|
public async Task<ActionResult> Success()
|
||||||
|
{
|
||||||
|
var user = IdentityService.User;
|
||||||
|
|
||||||
|
if (user == null)
|
||||||
|
return Redirect("/login");
|
||||||
|
|
||||||
|
await BillingService.CompleteCheckout(user);
|
||||||
|
|
||||||
|
return Redirect("/profile/subscriptions/close");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -25,7 +25,7 @@ public class RegisterController : Controller
|
|||||||
[HttpGet]
|
[HttpGet]
|
||||||
public async Task<ActionResult<TokenRegister>> Register()
|
public async Task<ActionResult<TokenRegister>> Register()
|
||||||
{
|
{
|
||||||
var user = await IdentityService.Get();
|
var user = IdentityService.User;
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
return NotFound();
|
return NotFound();
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ public class OAuth2Controller : Controller
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
var currentUser = await IdentityService.Get();
|
var currentUser = IdentityService.User;
|
||||||
|
|
||||||
if (currentUser != null)
|
if (currentUser != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.ComponentModel.DataAnnotations;
|
using System.ComponentModel.DataAnnotations;
|
||||||
|
using Moonlight.App.Models.Misc;
|
||||||
|
|
||||||
namespace Moonlight.App.Models.Forms;
|
namespace Moonlight.App.Models.Forms;
|
||||||
|
|
||||||
@@ -10,4 +11,8 @@ public class SubscriptionDataModel
|
|||||||
|
|
||||||
[Required(ErrorMessage = "You need to enter a description")]
|
[Required(ErrorMessage = "You need to enter a description")]
|
||||||
public string Description { get; set; } = "";
|
public string Description { get; set; } = "";
|
||||||
|
|
||||||
|
public double Price { get; set; } = 0;
|
||||||
|
public Currency Currency { get; set; } = Currency.USD;
|
||||||
|
public int Duration { get; set; } = 30;
|
||||||
}
|
}
|
||||||
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; }
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
namespace Moonlight.App.Models.Misc;
|
|
||||||
|
|
||||||
public enum AuditLogType
|
|
||||||
{
|
|
||||||
Login,
|
|
||||||
Register,
|
|
||||||
ChangePassword,
|
|
||||||
ChangePowerState,
|
|
||||||
CreateBackup,
|
|
||||||
RestoreBackup,
|
|
||||||
DeleteBackup,
|
|
||||||
DownloadBackup,
|
|
||||||
CreateServer,
|
|
||||||
ReinstallServer,
|
|
||||||
CancelSubscription,
|
|
||||||
ApplySubscriptionCode,
|
|
||||||
EnableTotp,
|
|
||||||
DisableTotp,
|
|
||||||
AddDomainRecord,
|
|
||||||
UpdateDomainRecord,
|
|
||||||
DeleteDomainRecord,
|
|
||||||
PasswordReset,
|
|
||||||
CleanupEnabled,
|
|
||||||
CleanupDisabled,
|
|
||||||
CleanupTriggered,
|
|
||||||
PasswordChange,
|
|
||||||
}
|
|
||||||
7
Moonlight/App/Models/Misc/Currency.cs
Normal file
7
Moonlight/App/Models/Misc/Currency.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Moonlight.App.Models.Misc;
|
||||||
|
|
||||||
|
public enum Currency
|
||||||
|
{
|
||||||
|
USD = 1,
|
||||||
|
EUR = 2
|
||||||
|
}
|
||||||
9
Moonlight/App/Models/Misc/MailTemplate.cs
Normal file
9
Moonlight/App/Models/Misc/MailTemplate.cs
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
using Moonlight.App.Helpers.Files;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Models.Misc;
|
||||||
|
|
||||||
|
public class MailTemplate // This is just for the blazor table at /admin/system/mail
|
||||||
|
{
|
||||||
|
public string Name { get; set; } = "";
|
||||||
|
public FileData File { get; set; }
|
||||||
|
}
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
namespace Moonlight.App.Models.Misc;
|
|
||||||
|
|
||||||
public enum SecurityLogType
|
|
||||||
{
|
|
||||||
ManipulatedJwt,
|
|
||||||
PathTransversal,
|
|
||||||
SftpBruteForce,
|
|
||||||
LoginFail
|
|
||||||
}
|
|
||||||
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
431
Moonlight/App/Perms/Permissions.cs
Normal file
431
Moonlight/App/Perms/Permissions.cs
Normal file
@@ -0,0 +1,431 @@
|
|||||||
|
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 AdminSecurityLogs = new()
|
||||||
|
{
|
||||||
|
Index = 58,
|
||||||
|
Name = "Admin security logs",
|
||||||
|
Description = "View the security logs"
|
||||||
|
};
|
||||||
|
|
||||||
|
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();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Moonlight.App.Database;
|
|
||||||
using Moonlight.App.Database.Entities.LogsEntries;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Repositories.LogEntries;
|
|
||||||
|
|
||||||
public class AuditLogEntryRepository : IDisposable
|
|
||||||
{
|
|
||||||
private readonly DataContext DataContext;
|
|
||||||
|
|
||||||
public AuditLogEntryRepository(DataContext dataContext)
|
|
||||||
{
|
|
||||||
DataContext = dataContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public AuditLogEntry Add(AuditLogEntry entry)
|
|
||||||
{
|
|
||||||
var x = DataContext.AuditLog.Add(entry);
|
|
||||||
DataContext.SaveChanges();
|
|
||||||
return x.Entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbSet<AuditLogEntry> Get()
|
|
||||||
{
|
|
||||||
return DataContext.AuditLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
DataContext.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Moonlight.App.Database;
|
|
||||||
using Moonlight.App.Database.Entities.LogsEntries;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Repositories.LogEntries;
|
|
||||||
|
|
||||||
public class ErrorLogEntryRepository : IDisposable
|
|
||||||
{
|
|
||||||
private readonly DataContext DataContext;
|
|
||||||
|
|
||||||
public ErrorLogEntryRepository(DataContext dataContext)
|
|
||||||
{
|
|
||||||
DataContext = dataContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public ErrorLogEntry Add(ErrorLogEntry errorLogEntry)
|
|
||||||
{
|
|
||||||
var x = DataContext.ErrorLog.Add(errorLogEntry);
|
|
||||||
DataContext.SaveChanges();
|
|
||||||
return x.Entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbSet<ErrorLogEntry> Get()
|
|
||||||
{
|
|
||||||
return DataContext.ErrorLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
DataContext.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
|
||||||
using Moonlight.App.Database;
|
|
||||||
using Moonlight.App.Database.Entities.LogsEntries;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Repositories.LogEntries;
|
|
||||||
|
|
||||||
public class SecurityLogEntryRepository : IDisposable
|
|
||||||
{
|
|
||||||
private readonly DataContext DataContext;
|
|
||||||
|
|
||||||
public SecurityLogEntryRepository(DataContext dataContext)
|
|
||||||
{
|
|
||||||
DataContext = dataContext;
|
|
||||||
}
|
|
||||||
|
|
||||||
public SecurityLogEntry Add(SecurityLogEntry securityLogEntry)
|
|
||||||
{
|
|
||||||
var x = DataContext.SecurityLog.Add(securityLogEntry);
|
|
||||||
DataContext.SaveChanges();
|
|
||||||
return x.Entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
public DbSet<SecurityLogEntry> Get()
|
|
||||||
{
|
|
||||||
return DataContext.SecurityLog;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void Dispose()
|
|
||||||
{
|
|
||||||
DataContext.Dispose();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -35,6 +35,7 @@ public class DiscordNotificationService
|
|||||||
Event.On<SupportChatMessage>("supportChat.message", this, OnSupportChatMessage);
|
Event.On<SupportChatMessage>("supportChat.message", this, OnSupportChatMessage);
|
||||||
Event.On<User>("supportChat.close", this, OnSupportChatClose);
|
Event.On<User>("supportChat.close", this, OnSupportChatClose);
|
||||||
Event.On<User>("user.rating", this, OnUserRated);
|
Event.On<User>("user.rating", this, OnUserRated);
|
||||||
|
Event.On<User>("billing.completed", this, OnBillingCompleted);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -42,6 +43,21 @@ public class DiscordNotificationService
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async Task OnBillingCompleted(User user)
|
||||||
|
{
|
||||||
|
await SendNotification("", builder =>
|
||||||
|
{
|
||||||
|
builder.Color = Color.Red;
|
||||||
|
builder.Title = "New payment received";
|
||||||
|
|
||||||
|
builder.AddField("User", user.Email);
|
||||||
|
builder.AddField("Firstname", user.FirstName);
|
||||||
|
builder.AddField("Lastname", user.LastName);
|
||||||
|
builder.AddField("Amount", user.CurrentSubscription!.Price);
|
||||||
|
builder.AddField("Currency", user.CurrentSubscription!.Currency);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
private async Task OnUserRated(User user)
|
private async Task OnUserRated(User user)
|
||||||
{
|
{
|
||||||
await SendNotification("", builder =>
|
await SendNotification("", builder =>
|
||||||
|
|||||||
37
Moonlight/App/Services/Background/TempMailService.cs
Normal file
37
Moonlight/App/Services/Background/TempMailService.cs
Normal file
@@ -0,0 +1,37 @@
|
|||||||
|
using System.Net.Mail;
|
||||||
|
using Moonlight.App.Helpers;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.Background;
|
||||||
|
|
||||||
|
public class TempMailService
|
||||||
|
{
|
||||||
|
private string[] Domains = Array.Empty<string>();
|
||||||
|
|
||||||
|
public TempMailService()
|
||||||
|
{
|
||||||
|
Task.Run(Init);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task Init()
|
||||||
|
{
|
||||||
|
var client = new HttpClient();
|
||||||
|
var text = await client.GetStringAsync("https://raw.githubusercontent.com/disposable-email-domains/disposable-email-domains/master/disposable_email_blocklist.conf");
|
||||||
|
|
||||||
|
Domains = text
|
||||||
|
.Split("\n")
|
||||||
|
.Select(x => x.Trim())
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
Logger.Info($"Fetched {Domains.Length} temp mail domains");
|
||||||
|
}
|
||||||
|
|
||||||
|
public Task<bool> IsTempMail(string mail)
|
||||||
|
{
|
||||||
|
var address = new MailAddress(mail);
|
||||||
|
|
||||||
|
if (Domains.Contains(address.Host))
|
||||||
|
return Task.FromResult(true);
|
||||||
|
|
||||||
|
return Task.FromResult(false);
|
||||||
|
}
|
||||||
|
}
|
||||||
127
Moonlight/App/Services/BillingService.cs
Normal file
127
Moonlight/App/Services/BillingService.cs
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
using System.Globalization;
|
||||||
|
using Moonlight.App.Database.Entities;
|
||||||
|
using Moonlight.App.Events;
|
||||||
|
using Moonlight.App.Exceptions;
|
||||||
|
using Moonlight.App.Repositories;
|
||||||
|
using Moonlight.App.Services.Mail;
|
||||||
|
using Moonlight.App.Services.Sessions;
|
||||||
|
using Stripe.Checkout;
|
||||||
|
using Subscription = Moonlight.App.Database.Entities.Subscription;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services;
|
||||||
|
|
||||||
|
public class BillingService
|
||||||
|
{
|
||||||
|
private readonly ConfigService ConfigService;
|
||||||
|
private readonly SubscriptionService SubscriptionService;
|
||||||
|
private readonly Repository<Subscription> SubscriptionRepository;
|
||||||
|
private readonly SessionServerService SessionServerService;
|
||||||
|
private readonly EventSystem Event;
|
||||||
|
private readonly MailService MailService;
|
||||||
|
|
||||||
|
public BillingService(
|
||||||
|
ConfigService configService,
|
||||||
|
SubscriptionService subscriptionService,
|
||||||
|
Repository<Subscription> subscriptionRepository,
|
||||||
|
EventSystem eventSystem,
|
||||||
|
SessionServerService sessionServerService,
|
||||||
|
MailService mailService)
|
||||||
|
{
|
||||||
|
ConfigService = configService;
|
||||||
|
SubscriptionService = subscriptionService;
|
||||||
|
SubscriptionRepository = subscriptionRepository;
|
||||||
|
Event = eventSystem;
|
||||||
|
SessionServerService = sessionServerService;
|
||||||
|
MailService = mailService;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task<string> StartCheckout(User user, Subscription subscription)
|
||||||
|
{
|
||||||
|
var appUrl = ConfigService.Get().Moonlight.AppUrl;
|
||||||
|
var controllerUrl = appUrl + "/api/moonlight/billing";
|
||||||
|
|
||||||
|
var options = new SessionCreateOptions()
|
||||||
|
{
|
||||||
|
LineItems = new()
|
||||||
|
{
|
||||||
|
new()
|
||||||
|
{
|
||||||
|
Price = subscription.StripePriceId,
|
||||||
|
Quantity = 1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Mode = "payment",
|
||||||
|
SuccessUrl = controllerUrl + "/success",
|
||||||
|
CancelUrl = controllerUrl + "/cancel",
|
||||||
|
AutomaticTax = new SessionAutomaticTaxOptions()
|
||||||
|
{
|
||||||
|
Enabled = true
|
||||||
|
},
|
||||||
|
CustomerEmail = user.Email.ToLower(),
|
||||||
|
Metadata = new()
|
||||||
|
{
|
||||||
|
{
|
||||||
|
"productId",
|
||||||
|
subscription.StripeProductId
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
var service = new SessionService();
|
||||||
|
|
||||||
|
var session = await service.CreateAsync(options);
|
||||||
|
|
||||||
|
return session.Url;
|
||||||
|
}
|
||||||
|
public async Task CompleteCheckout(User user)
|
||||||
|
{
|
||||||
|
var sessionService = new SessionService();
|
||||||
|
|
||||||
|
var sessionsPerUser = await sessionService.ListAsync(new SessionListOptions()
|
||||||
|
{
|
||||||
|
CustomerDetails = new()
|
||||||
|
{
|
||||||
|
Email = user.Email
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
var latestCompletedSession = sessionsPerUser
|
||||||
|
.Where(x => x.Status == "complete")
|
||||||
|
.Where(x => x.PaymentStatus == "paid")
|
||||||
|
.MaxBy(x => x.Created);
|
||||||
|
|
||||||
|
if (latestCompletedSession == null)
|
||||||
|
throw new DisplayException("No completed session found");
|
||||||
|
|
||||||
|
var productId = latestCompletedSession.Metadata["productId"];
|
||||||
|
|
||||||
|
var subscription = SubscriptionRepository
|
||||||
|
.Get()
|
||||||
|
.FirstOrDefault(x => x.StripeProductId == productId);
|
||||||
|
|
||||||
|
if (subscription == null)
|
||||||
|
throw new DisplayException("No subscription for this product found");
|
||||||
|
|
||||||
|
// if (await SubscriptionService.GetActiveSubscription(user) != null)
|
||||||
|
// {
|
||||||
|
// return;
|
||||||
|
// }
|
||||||
|
|
||||||
|
await SubscriptionService.SetActiveSubscription(user, subscription);
|
||||||
|
|
||||||
|
await MailService.SendMail(user, "checkoutComplete", values =>
|
||||||
|
{
|
||||||
|
values.Add("SubscriptionName", subscription.Name);
|
||||||
|
values.Add("SubscriptionPrice", subscription.Price
|
||||||
|
.ToString(CultureInfo.InvariantCulture));
|
||||||
|
values.Add("SubscriptionCurrency", subscription.Currency
|
||||||
|
.ToString());
|
||||||
|
values.Add("SubscriptionDuration", subscription.Duration
|
||||||
|
.ToString(CultureInfo.InvariantCulture));
|
||||||
|
});
|
||||||
|
|
||||||
|
await Event.Emit("billing.completed", user);
|
||||||
|
|
||||||
|
await SessionServerService.ReloadUserSessions(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ namespace Moonlight.App.Services;
|
|||||||
public class ConfigService
|
public class ConfigService
|
||||||
{
|
{
|
||||||
private readonly StorageService StorageService;
|
private readonly StorageService StorageService;
|
||||||
|
private readonly string Path;
|
||||||
private ConfigV1 Configuration;
|
private ConfigV1 Configuration;
|
||||||
|
|
||||||
public bool DebugMode { get; private set; } = false;
|
public bool DebugMode { get; private set; } = false;
|
||||||
@@ -18,6 +19,11 @@ public class ConfigService
|
|||||||
StorageService = storageService;
|
StorageService = storageService;
|
||||||
StorageService.EnsureCreated();
|
StorageService.EnsureCreated();
|
||||||
|
|
||||||
|
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("ML_CONFIG_PATH")))
|
||||||
|
Path = Environment.GetEnvironmentVariable("ML_CONFIG_PATH")!;
|
||||||
|
else
|
||||||
|
Path = PathBuilder.File("storage", "configs", "config.json");
|
||||||
|
|
||||||
Reload();
|
Reload();
|
||||||
|
|
||||||
// Env vars
|
// Env vars
|
||||||
@@ -40,18 +46,16 @@ public class ConfigService
|
|||||||
|
|
||||||
public void Reload()
|
public void Reload()
|
||||||
{
|
{
|
||||||
var path = PathBuilder.File("storage", "configs", "config.json");
|
if (!File.Exists(Path))
|
||||||
|
|
||||||
if (!File.Exists(path))
|
|
||||||
{
|
{
|
||||||
File.WriteAllText(path, "{}");
|
File.WriteAllText(Path, "{}");
|
||||||
}
|
}
|
||||||
|
|
||||||
Configuration = JsonConvert.DeserializeObject<ConfigV1>(
|
Configuration = JsonConvert.DeserializeObject<ConfigV1>(
|
||||||
File.ReadAllText(path)
|
File.ReadAllText(Path)
|
||||||
) ?? new ConfigV1();
|
) ?? new ConfigV1();
|
||||||
|
|
||||||
File.WriteAllText(path, JsonConvert.SerializeObject(Configuration, Formatting.Indented));
|
File.WriteAllText(Path, JsonConvert.SerializeObject(Configuration, Formatting.Indented));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void Save(ConfigV1 configV1)
|
public void Save(ConfigV1 configV1)
|
||||||
@@ -62,14 +66,12 @@ public class ConfigService
|
|||||||
|
|
||||||
public void Save()
|
public void Save()
|
||||||
{
|
{
|
||||||
var path = PathBuilder.File("storage", "configs", "config.json");
|
if (!File.Exists(Path))
|
||||||
|
|
||||||
if (!File.Exists(path))
|
|
||||||
{
|
{
|
||||||
File.WriteAllText(path, "{}");
|
File.WriteAllText(Path, "{}");
|
||||||
}
|
}
|
||||||
|
|
||||||
File.WriteAllText(path, JsonConvert.SerializeObject(Configuration, Formatting.Indented));
|
File.WriteAllText(Path, JsonConvert.SerializeObject(Configuration, Formatting.Indented));
|
||||||
|
|
||||||
Reload();
|
Reload();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -20,6 +20,7 @@ namespace Moonlight.App.Services;
|
|||||||
public class DomainService
|
public class DomainService
|
||||||
{
|
{
|
||||||
private readonly DomainRepository DomainRepository;
|
private readonly DomainRepository DomainRepository;
|
||||||
|
private readonly ConfigService ConfigService;
|
||||||
private readonly SharedDomainRepository SharedDomainRepository;
|
private readonly SharedDomainRepository SharedDomainRepository;
|
||||||
private readonly CloudFlareClient Client;
|
private readonly CloudFlareClient Client;
|
||||||
private readonly string AccountId;
|
private readonly string AccountId;
|
||||||
@@ -29,6 +30,7 @@ public class DomainService
|
|||||||
DomainRepository domainRepository,
|
DomainRepository domainRepository,
|
||||||
SharedDomainRepository sharedDomainRepository)
|
SharedDomainRepository sharedDomainRepository)
|
||||||
{
|
{
|
||||||
|
ConfigService = configService;
|
||||||
DomainRepository = domainRepository;
|
DomainRepository = domainRepository;
|
||||||
SharedDomainRepository = sharedDomainRepository;
|
SharedDomainRepository = sharedDomainRepository;
|
||||||
|
|
||||||
@@ -48,6 +50,9 @@ public class DomainService
|
|||||||
|
|
||||||
public Task<Domain> Create(string domain, SharedDomain sharedDomain, User user)
|
public Task<Domain> Create(string domain, SharedDomain sharedDomain, User user)
|
||||||
{
|
{
|
||||||
|
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||||
|
throw new DisplayException("This operation is disabled");
|
||||||
|
|
||||||
if (DomainRepository.Get().Where(x => x.SharedDomain.Id == sharedDomain.Id).Any(x => x.Name == domain))
|
if (DomainRepository.Get().Where(x => x.SharedDomain.Id == sharedDomain.Id).Any(x => x.Name == domain))
|
||||||
throw new DisplayException("A domain with this name does already exist for this shared domain");
|
throw new DisplayException("A domain with this name does already exist for this shared domain");
|
||||||
|
|
||||||
@@ -63,6 +68,9 @@ public class DomainService
|
|||||||
|
|
||||||
public Task Delete(Domain domain)
|
public Task Delete(Domain domain)
|
||||||
{
|
{
|
||||||
|
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||||
|
throw new DisplayException("This operation is disabled");
|
||||||
|
|
||||||
DomainRepository.Delete(domain);
|
DomainRepository.Delete(domain);
|
||||||
|
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
@@ -71,6 +79,9 @@ public class DomainService
|
|||||||
public async Task<Zone[]>
|
public async Task<Zone[]>
|
||||||
GetAvailableDomains() // This method returns all available domains which are not added as a shared domain
|
GetAvailableDomains() // This method returns all available domains which are not added as a shared domain
|
||||||
{
|
{
|
||||||
|
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||||
|
return Array.Empty<Zone>();
|
||||||
|
|
||||||
var domains = GetData(
|
var domains = GetData(
|
||||||
await Client.Zones.GetAsync(new()
|
await Client.Zones.GetAsync(new()
|
||||||
{
|
{
|
||||||
@@ -93,6 +104,9 @@ public class DomainService
|
|||||||
|
|
||||||
public async Task<DnsRecord[]> GetDnsRecords(Domain d)
|
public async Task<DnsRecord[]> GetDnsRecords(Domain d)
|
||||||
{
|
{
|
||||||
|
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||||
|
return Array.Empty<DnsRecord>();
|
||||||
|
|
||||||
var domain = EnsureData(d);
|
var domain = EnsureData(d);
|
||||||
|
|
||||||
var records = new List<CloudFlare.Client.Api.Zones.DnsRecord.DnsRecord>();
|
var records = new List<CloudFlare.Client.Api.Zones.DnsRecord.DnsRecord>();
|
||||||
@@ -146,7 +160,7 @@ public class DomainService
|
|||||||
Type = record.Type
|
Type = record.Type
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
else if (record.Name.EndsWith(rname))
|
else if (record.Name == rname)
|
||||||
{
|
{
|
||||||
result.Add(new()
|
result.Add(new()
|
||||||
{
|
{
|
||||||
@@ -166,58 +180,72 @@ public class DomainService
|
|||||||
|
|
||||||
public async Task AddDnsRecord(Domain d, DnsRecord dnsRecord)
|
public async Task AddDnsRecord(Domain d, DnsRecord dnsRecord)
|
||||||
{
|
{
|
||||||
var domain = EnsureData(d);
|
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||||
|
throw new DisplayException("This operation is disabled");
|
||||||
|
|
||||||
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
|
try
|
||||||
var dname = $".{rname}";
|
|
||||||
|
|
||||||
if (dnsRecord.Type == DnsRecordType.Srv)
|
|
||||||
{
|
{
|
||||||
var parts = dnsRecord.Name.Split(".");
|
var domain = EnsureData(d);
|
||||||
|
|
||||||
Protocol protocol = Protocol.Tcp;
|
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
|
||||||
|
var dname = $".{rname}";
|
||||||
|
|
||||||
if (parts[1].Contains("udp"))
|
if (dnsRecord.Type == DnsRecordType.Srv)
|
||||||
protocol = Protocol.Udp;
|
|
||||||
|
|
||||||
var valueParts = dnsRecord.Content.Split(" ");
|
|
||||||
|
|
||||||
var nameWithoutProt = dnsRecord.Name.Replace($"{parts[0]}.{parts[1]}.", "");
|
|
||||||
nameWithoutProt = nameWithoutProt.Replace($"{parts[0]}.{parts[1]}", "");
|
|
||||||
var name = nameWithoutProt == "" ? rname : nameWithoutProt + dname;
|
|
||||||
|
|
||||||
var srv = new NewDnsRecord<Srv>()
|
|
||||||
{
|
{
|
||||||
Type = dnsRecord.Type,
|
var parts = dnsRecord.Name.Split(".");
|
||||||
Data = new()
|
|
||||||
|
Protocol protocol = Protocol.Tcp;
|
||||||
|
|
||||||
|
if (parts[1].Contains("udp"))
|
||||||
|
protocol = Protocol.Udp;
|
||||||
|
|
||||||
|
var valueParts = dnsRecord.Content.Split(" ");
|
||||||
|
|
||||||
|
var nameWithoutProt = dnsRecord.Name.Replace($"{parts[0]}.{parts[1]}.", "");
|
||||||
|
nameWithoutProt = nameWithoutProt.Replace($"{parts[0]}.{parts[1]}", "");
|
||||||
|
var name = nameWithoutProt == "" ? rname : nameWithoutProt + dname;
|
||||||
|
|
||||||
|
var srv = new NewDnsRecord<Srv>()
|
||||||
{
|
{
|
||||||
Service = parts[0],
|
Type = dnsRecord.Type,
|
||||||
Protocol = protocol,
|
Data = new()
|
||||||
Name = name,
|
{
|
||||||
Weight = int.Parse(valueParts[0]),
|
Service = parts[0],
|
||||||
Port = int.Parse(valueParts[1]),
|
Protocol = protocol,
|
||||||
Target = valueParts[2],
|
Name = name,
|
||||||
Priority = dnsRecord.Priority
|
Weight = int.Parse(valueParts[0]),
|
||||||
},
|
Port = int.Parse(valueParts[1]),
|
||||||
Proxied = dnsRecord.Proxied,
|
Target = valueParts[2],
|
||||||
Ttl = dnsRecord.Ttl,
|
Priority = dnsRecord.Priority
|
||||||
};
|
},
|
||||||
|
Proxied = dnsRecord.Proxied,
|
||||||
|
Ttl = dnsRecord.Ttl,
|
||||||
|
};
|
||||||
|
|
||||||
GetData(await Client.Zones.DnsRecords.AddAsync(d.SharedDomain.CloudflareId, srv));
|
GetData(await Client.Zones.DnsRecords.AddAsync(d.SharedDomain.CloudflareId, srv));
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
|
||||||
var name = string.IsNullOrEmpty(dnsRecord.Name) ? rname : dnsRecord.Name + dname;
|
|
||||||
|
|
||||||
GetData(await Client.Zones.DnsRecords.AddAsync(d.SharedDomain.CloudflareId, new NewDnsRecord()
|
|
||||||
{
|
{
|
||||||
Type = dnsRecord.Type,
|
var name = string.IsNullOrEmpty(dnsRecord.Name) ? rname : dnsRecord.Name + dname;
|
||||||
Priority = dnsRecord.Priority,
|
|
||||||
Content = dnsRecord.Content,
|
GetData(await Client.Zones.DnsRecords.AddAsync(d.SharedDomain.CloudflareId, new NewDnsRecord()
|
||||||
Proxied = dnsRecord.Proxied,
|
{
|
||||||
Ttl = dnsRecord.Ttl,
|
Type = dnsRecord.Type,
|
||||||
Name = name
|
Priority = dnsRecord.Priority,
|
||||||
}));
|
Content = dnsRecord.Content,
|
||||||
|
Proxied = dnsRecord.Proxied,
|
||||||
|
Ttl = dnsRecord.Ttl,
|
||||||
|
Name = name
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (OverflowException)
|
||||||
|
{
|
||||||
|
throw new DisplayException("Invalid dns record values");
|
||||||
|
}
|
||||||
|
catch (FormatException)
|
||||||
|
{
|
||||||
|
throw new DisplayException("Invalid dns record values");
|
||||||
}
|
}
|
||||||
|
|
||||||
//TODO: AuditLog
|
//TODO: AuditLog
|
||||||
@@ -225,6 +253,9 @@ public class DomainService
|
|||||||
|
|
||||||
public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord)
|
public async Task UpdateDnsRecord(Domain d, DnsRecord dnsRecord)
|
||||||
{
|
{
|
||||||
|
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||||
|
throw new DisplayException("This operation is disabled");
|
||||||
|
|
||||||
var domain = EnsureData(d);
|
var domain = EnsureData(d);
|
||||||
|
|
||||||
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
|
var rname = $"{domain.Name}.{domain.SharedDomain.Name}";
|
||||||
@@ -255,6 +286,9 @@ public class DomainService
|
|||||||
|
|
||||||
public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord)
|
public async Task DeleteDnsRecord(Domain d, DnsRecord dnsRecord)
|
||||||
{
|
{
|
||||||
|
if (!ConfigService.Get().Moonlight.Domains.Enable)
|
||||||
|
throw new DisplayException("This operation is disabled");
|
||||||
|
|
||||||
var domain = EnsureData(d);
|
var domain = EnsureData(d);
|
||||||
|
|
||||||
GetData(
|
GetData(
|
||||||
|
|||||||
18
Moonlight/App/Services/Interop/PopupService.cs
Normal file
18
Moonlight/App/Services/Interop/PopupService.cs
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
using Microsoft.JSInterop;
|
||||||
|
|
||||||
|
namespace Moonlight.App.Services.Interop;
|
||||||
|
|
||||||
|
public class PopupService
|
||||||
|
{
|
||||||
|
private readonly IJSRuntime JsRuntime;
|
||||||
|
|
||||||
|
public PopupService(IJSRuntime jsRuntime)
|
||||||
|
{
|
||||||
|
JsRuntime = jsRuntime;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task ShowCentered(string url, string title, int width = 500, int height = 500)
|
||||||
|
{
|
||||||
|
await JsRuntime.InvokeVoidAsync("moonlight.popup.showCentered", url, title, width, height);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Exceptions;
|
using Moonlight.App.Exceptions;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
|
using Moonlight.App.Repositories;
|
||||||
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
using SmtpClient = MailKit.Net.Smtp.SmtpClient;
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Mail;
|
namespace Moonlight.App.Services.Mail;
|
||||||
@@ -14,8 +15,14 @@ public class MailService
|
|||||||
private readonly int Port;
|
private readonly int Port;
|
||||||
private readonly bool Ssl;
|
private readonly bool Ssl;
|
||||||
|
|
||||||
public MailService(ConfigService configService)
|
private readonly Repository<User> UserRepository;
|
||||||
|
|
||||||
|
public MailService(
|
||||||
|
ConfigService configService,
|
||||||
|
Repository<User> userRepository)
|
||||||
{
|
{
|
||||||
|
UserRepository = userRepository;
|
||||||
|
|
||||||
var mailConfig = configService
|
var mailConfig = configService
|
||||||
.Get()
|
.Get()
|
||||||
.Moonlight.Mail;
|
.Moonlight.Mail;
|
||||||
@@ -27,28 +34,8 @@ public class MailService
|
|||||||
Ssl = mailConfig.Ssl;
|
Ssl = mailConfig.Ssl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task SendMail(
|
public Task SendMailRaw(User user, string html)
|
||||||
User user,
|
|
||||||
string name,
|
|
||||||
Action<Dictionary<string, string>> values
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
if (!File.Exists(PathBuilder.File("storage", "resources", "mail", $"{name}.html")))
|
|
||||||
{
|
|
||||||
Logger.Warn($"Mail template '{name}' not found. Make sure to place one in the resources folder");
|
|
||||||
throw new DisplayException("Mail template not found");
|
|
||||||
}
|
|
||||||
|
|
||||||
var rawHtml = await File.ReadAllTextAsync(PathBuilder.File("storage", "resources", "mail", $"{name}.html"));
|
|
||||||
|
|
||||||
var val = new Dictionary<string, string>();
|
|
||||||
values.Invoke(val);
|
|
||||||
|
|
||||||
val.Add("FirstName", user.FirstName);
|
|
||||||
val.Add("LastName", user.LastName);
|
|
||||||
|
|
||||||
var parsed = ParseMail(rawHtml, val);
|
|
||||||
|
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -62,17 +49,15 @@ public class MailService
|
|||||||
|
|
||||||
var body = new BodyBuilder
|
var body = new BodyBuilder
|
||||||
{
|
{
|
||||||
HtmlBody = parsed
|
HtmlBody = html
|
||||||
};
|
};
|
||||||
mailMessage.Body = body.ToMessageBody();
|
mailMessage.Body = body.ToMessageBody();
|
||||||
|
|
||||||
using (var smtpClient = new SmtpClient())
|
using var smtpClient = new SmtpClient();
|
||||||
{
|
await smtpClient.ConnectAsync(Server, Port, Ssl);
|
||||||
await smtpClient.ConnectAsync(Server, Port, Ssl);
|
await smtpClient.AuthenticateAsync(Email, Password);
|
||||||
await smtpClient.AuthenticateAsync(Email, Password);
|
await smtpClient.SendAsync(mailMessage);
|
||||||
await smtpClient.SendAsync(mailMessage);
|
await smtpClient.DisconnectAsync(true);
|
||||||
await smtpClient.DisconnectAsync(true);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
@@ -80,6 +65,54 @@ public class MailService
|
|||||||
Logger.Warn(e);
|
Logger.Warn(e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendMail(User user, string template, Action<Dictionary<string, string>> values)
|
||||||
|
{
|
||||||
|
if (!File.Exists(PathBuilder.File("storage", "resources", "mail", $"{template}.html")))
|
||||||
|
{
|
||||||
|
Logger.Warn($"Mail template '{template}' not found. Make sure to place one in the resources folder");
|
||||||
|
throw new DisplayException("Mail template not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
var rawHtml = await File.ReadAllTextAsync(PathBuilder.File("storage", "resources", "mail", $"{template}.html"));
|
||||||
|
|
||||||
|
var val = new Dictionary<string, string>();
|
||||||
|
values.Invoke(val);
|
||||||
|
|
||||||
|
val.Add("FirstName", user.FirstName);
|
||||||
|
val.Add("LastName", user.LastName);
|
||||||
|
|
||||||
|
var parsed = ParseMail(rawHtml, val);
|
||||||
|
|
||||||
|
await SendMailRaw(user, parsed);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendEmailToAll(string template, Action<Dictionary<string, string>> values)
|
||||||
|
{
|
||||||
|
var users = UserRepository
|
||||||
|
.Get()
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var user in users)
|
||||||
|
{
|
||||||
|
await SendMail(user, template, values);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public async Task SendEmailToAllAdmins(string template, Action<Dictionary<string, string>> values)
|
||||||
|
{
|
||||||
|
var users = UserRepository
|
||||||
|
.Get()
|
||||||
|
.Where(x => x.Admin)
|
||||||
|
.ToArray();
|
||||||
|
|
||||||
|
foreach (var user in users)
|
||||||
|
{
|
||||||
|
await SendMail(user, template, values);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private string ParseMail(string html, Dictionary<string, string> values)
|
private string ParseMail(string html, Dictionary<string, string> values)
|
||||||
|
|||||||
@@ -1,46 +0,0 @@
|
|||||||
using System.Net;
|
|
||||||
using Moonlight.App.Helpers;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services.Mail;
|
|
||||||
|
|
||||||
public class TrashMailDetectorService
|
|
||||||
{
|
|
||||||
private string[] Domains;
|
|
||||||
|
|
||||||
public TrashMailDetectorService()
|
|
||||||
{
|
|
||||||
Logger.Info("Fetching trash mail list from github repository");
|
|
||||||
|
|
||||||
using var wc = new WebClient();
|
|
||||||
|
|
||||||
var lines = wc
|
|
||||||
.DownloadString("https://raw.githubusercontent.com/Endelon-Hosting/TrashMailDomainDetector/main/trashmail_domains.md")
|
|
||||||
.Replace("\r\n", "\n")
|
|
||||||
.Split(new [] { "\n" }, StringSplitOptions.RemoveEmptyEntries);
|
|
||||||
|
|
||||||
Domains = GetDomains(lines).ToArray();
|
|
||||||
}
|
|
||||||
|
|
||||||
private IEnumerable<string> GetDomains(string[] lines)
|
|
||||||
{
|
|
||||||
foreach (var line in lines)
|
|
||||||
{
|
|
||||||
if (!string.IsNullOrWhiteSpace(line))
|
|
||||||
{
|
|
||||||
if (line.Contains("."))
|
|
||||||
{
|
|
||||||
var domain = line.Remove(0, line.IndexOf(".", StringComparison.Ordinal) + 1).Trim();
|
|
||||||
if (domain.Contains("."))
|
|
||||||
{
|
|
||||||
yield return domain;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public bool IsTrashEmail(string mail)
|
|
||||||
{
|
|
||||||
return Domains.Contains(mail.Split('@')[1]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -39,7 +39,7 @@ public class RatingService
|
|||||||
if (!Enabled)
|
if (!Enabled)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var user = await IdentityService.Get();
|
var user = IdentityService.User;
|
||||||
|
|
||||||
if (user == null)
|
if (user == null)
|
||||||
return false;
|
return false;
|
||||||
@@ -62,7 +62,7 @@ public class RatingService
|
|||||||
|
|
||||||
public async Task<bool> Rate(int rate)
|
public async Task<bool> Rate(int rate)
|
||||||
{
|
{
|
||||||
var user = await IdentityService.Get();
|
var user = IdentityService.User;
|
||||||
|
|
||||||
// Double check states:
|
// Double check states:
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,10 @@
|
|||||||
using JWT.Algorithms;
|
using JWT.Algorithms;
|
||||||
using JWT.Builder;
|
using JWT.Builder;
|
||||||
using JWT.Exceptions;
|
using JWT.Exceptions;
|
||||||
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Perms;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using UAParser;
|
using UAParser;
|
||||||
|
|
||||||
@@ -12,16 +13,21 @@ namespace Moonlight.App.Services.Sessions;
|
|||||||
|
|
||||||
public class IdentityService
|
public class IdentityService
|
||||||
{
|
{
|
||||||
private readonly UserRepository UserRepository;
|
private readonly Repository<User> UserRepository;
|
||||||
private readonly CookieService CookieService;
|
private readonly CookieService CookieService;
|
||||||
private readonly IHttpContextAccessor HttpContextAccessor;
|
private readonly IHttpContextAccessor HttpContextAccessor;
|
||||||
private readonly string Secret;
|
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(
|
public IdentityService(
|
||||||
CookieService cookieService,
|
CookieService cookieService,
|
||||||
UserRepository userRepository,
|
Repository<User> userRepository,
|
||||||
IHttpContextAccessor httpContextAccessor,
|
IHttpContextAccessor httpContextAccessor,
|
||||||
ConfigService configService)
|
ConfigService configService)
|
||||||
{
|
{
|
||||||
@@ -34,13 +40,17 @@ public class IdentityService
|
|||||||
.Moonlight.Security.Token;
|
.Moonlight.Security.Token;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<User?> Get()
|
public async Task Load()
|
||||||
|
{
|
||||||
|
await LoadIp();
|
||||||
|
await LoadDevice();
|
||||||
|
await LoadUser();
|
||||||
|
}
|
||||||
|
|
||||||
|
private async Task LoadUser()
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (UserCache != null)
|
|
||||||
return UserCache;
|
|
||||||
|
|
||||||
var token = "none";
|
var token = "none";
|
||||||
|
|
||||||
// Load token via http context if available
|
// Load token via http context if available
|
||||||
@@ -60,13 +70,13 @@ public class IdentityService
|
|||||||
|
|
||||||
if (token == "none")
|
if (token == "none")
|
||||||
{
|
{
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(token))
|
if (string.IsNullOrEmpty(token))
|
||||||
return null;
|
return;
|
||||||
|
|
||||||
var json = "";
|
string json;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -77,18 +87,18 @@ public class IdentityService
|
|||||||
}
|
}
|
||||||
catch (TokenExpiredException)
|
catch (TokenExpiredException)
|
||||||
{
|
{
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
catch (SignatureVerificationException)
|
catch (SignatureVerificationException)
|
||||||
{
|
{
|
||||||
Logger.Warn($"Detected a manipulated JWT: {token}", "security");
|
Logger.Warn($"Detected a manipulated JWT: {token}", "security");
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Error("Error reading jwt");
|
Logger.Error("Error reading jwt");
|
||||||
Logger.Error(e);
|
Logger.Error(e);
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// To make it easier to use the json data
|
// To make it easier to use the json data
|
||||||
@@ -101,8 +111,9 @@ public class IdentityService
|
|||||||
|
|
||||||
if (user == null)
|
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");
|
Logger.Warn(
|
||||||
return null;
|
$"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);
|
var iat = data.GetValue<long>("iat", -1);
|
||||||
@@ -110,46 +121,54 @@ public class IdentityService
|
|||||||
if (iat == -1)
|
if (iat == -1)
|
||||||
{
|
{
|
||||||
Logger.Debug("Legacy token found (without the time the token has been issued at)");
|
Logger.Debug("Legacy token found (without the time the token has been issued at)");
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
var iatD = DateTimeOffset.FromUnixTimeSeconds(iat).ToUniversalTime().DateTime;
|
var iatD = DateTimeOffset.FromUnixTimeSeconds(iat).ToUniversalTime().DateTime;
|
||||||
|
|
||||||
if (iatD < user.TokenValidTime)
|
if (iatD < user.TokenValidTime)
|
||||||
return null;
|
return;
|
||||||
|
|
||||||
UserCache = user;
|
User = user;
|
||||||
|
|
||||||
user.LastIp = GetIp();
|
ConstructPermissions();
|
||||||
UserRepository.Update(user);
|
|
||||||
|
|
||||||
return UserCache;
|
User.LastIp = Ip;
|
||||||
|
UserRepository.Update(User);
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
catch (Exception e)
|
||||||
{
|
{
|
||||||
Logger.Error("Unexpected error while processing token");
|
Logger.Error("Unexpected error while processing token");
|
||||||
Logger.Error(e);
|
Logger.Error(e);
|
||||||
return null;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public string GetIp()
|
private Task LoadIp()
|
||||||
{
|
{
|
||||||
if (HttpContextAccessor.HttpContext == null)
|
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)
|
if (HttpContextAccessor.HttpContext == null)
|
||||||
return "N/A";
|
{
|
||||||
|
Device = "N/A";
|
||||||
|
return Task.CompletedTask;
|
||||||
|
}
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
@@ -159,17 +178,86 @@ public class IdentityService
|
|||||||
{
|
{
|
||||||
var version = userAgent.Remove(0, "Moonlight.App/".Length).Split(' ').FirstOrDefault();
|
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 uaParser = Parser.GetDefault();
|
||||||
var info = uaParser.Parse(userAgent);
|
var info = uaParser.Parse(userAgent);
|
||||||
|
|
||||||
return $"{info.OS} - {info.Device}";
|
Device = $"{info.OS} - {info.Device}";
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
catch (Exception e)
|
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()
|
public Task<bool> IsBanned()
|
||||||
{
|
{
|
||||||
var ip = IdentityService.GetIp();
|
var ip = IdentityService.Ip;
|
||||||
|
|
||||||
return Task.FromResult(
|
return Task.FromResult(
|
||||||
IpBanRepository
|
IpBanRepository
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class IpLocateService
|
|||||||
|
|
||||||
public async Task<string> GetLocation()
|
public async Task<string> GetLocation()
|
||||||
{
|
{
|
||||||
var ip = IdentityService.GetIp();
|
var ip = IdentityService.Ip;
|
||||||
var location = "N/A";
|
var location = "N/A";
|
||||||
|
|
||||||
if (ip != "N/A")
|
if (ip != "N/A")
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ public class SessionClientService
|
|||||||
public readonly Guid Uuid = Guid.NewGuid();
|
public readonly Guid Uuid = Guid.NewGuid();
|
||||||
public readonly DateTime CreateTimestamp = DateTime.UtcNow;
|
public readonly DateTime CreateTimestamp = DateTime.UtcNow;
|
||||||
public User? User { get; private set; }
|
public User? User { get; private set; }
|
||||||
|
public string Ip { get; private set; } = "N/A";
|
||||||
|
public string Device { get; private set; } = "N/A";
|
||||||
|
|
||||||
public readonly IdentityService IdentityService;
|
public readonly IdentityService IdentityService;
|
||||||
public readonly AlertService AlertService;
|
public readonly AlertService AlertService;
|
||||||
@@ -38,7 +40,9 @@ public class SessionClientService
|
|||||||
|
|
||||||
public async Task Start()
|
public async Task Start()
|
||||||
{
|
{
|
||||||
User = await IdentityService.Get();
|
User = IdentityService.User;
|
||||||
|
Ip = IdentityService.Ip;
|
||||||
|
Device = IdentityService.Device;
|
||||||
|
|
||||||
if (User != null) // Track users last visit
|
if (User != null) // Track users last visit
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -1,45 +0,0 @@
|
|||||||
using Moonlight.App.Database.Entities;
|
|
||||||
using Moonlight.App.Models.Misc;
|
|
||||||
using Moonlight.App.Repositories;
|
|
||||||
using Newtonsoft.Json;
|
|
||||||
|
|
||||||
namespace Moonlight.App.Services;
|
|
||||||
|
|
||||||
public class SubscriptionAdminService
|
|
||||||
{
|
|
||||||
private readonly SubscriptionRepository SubscriptionRepository;
|
|
||||||
private readonly OneTimeJwtService OneTimeJwtService;
|
|
||||||
|
|
||||||
public SubscriptionAdminService(OneTimeJwtService oneTimeJwtService, SubscriptionRepository subscriptionRepository)
|
|
||||||
{
|
|
||||||
OneTimeJwtService = oneTimeJwtService;
|
|
||||||
SubscriptionRepository = subscriptionRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<SubscriptionLimit[]> GetLimits(Subscription subscription)
|
|
||||||
{
|
|
||||||
return Task.FromResult(
|
|
||||||
JsonConvert.DeserializeObject<SubscriptionLimit[]>(subscription.LimitsJson)
|
|
||||||
?? Array.Empty<SubscriptionLimit>()
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task SaveLimits(Subscription subscription, SubscriptionLimit[] limits)
|
|
||||||
{
|
|
||||||
subscription.LimitsJson = JsonConvert.SerializeObject(limits);
|
|
||||||
SubscriptionRepository.Update(subscription);
|
|
||||||
|
|
||||||
return Task.CompletedTask;
|
|
||||||
}
|
|
||||||
|
|
||||||
public Task<string> GenerateCode(Subscription subscription, int duration)
|
|
||||||
{
|
|
||||||
return Task.FromResult(
|
|
||||||
OneTimeJwtService.Generate(data =>
|
|
||||||
{
|
|
||||||
data.Add("subscription", subscription.Id.ToString());
|
|
||||||
data.Add("duration", duration.ToString());
|
|
||||||
}, TimeSpan.FromDays(10324))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,151 +1,200 @@
|
|||||||
using Microsoft.EntityFrameworkCore;
|
using Microsoft.EntityFrameworkCore;
|
||||||
using Moonlight.App.Database.Entities;
|
using Moonlight.App.Database.Entities;
|
||||||
using Moonlight.App.Exceptions;
|
|
||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Services.Sessions;
|
|
||||||
using Newtonsoft.Json;
|
using Newtonsoft.Json;
|
||||||
|
using Stripe;
|
||||||
|
using File = System.IO.File;
|
||||||
|
using Subscription = Moonlight.App.Database.Entities.Subscription;
|
||||||
|
|
||||||
namespace Moonlight.App.Services;
|
namespace Moonlight.App.Services;
|
||||||
|
|
||||||
public class SubscriptionService
|
public class SubscriptionService
|
||||||
{
|
{
|
||||||
private readonly SubscriptionRepository SubscriptionRepository;
|
private readonly Repository<Subscription> SubscriptionRepository;
|
||||||
private readonly OneTimeJwtService OneTimeJwtService;
|
private readonly Repository<User> UserRepository;
|
||||||
private readonly IdentityService IdentityService;
|
|
||||||
private readonly UserRepository UserRepository;
|
|
||||||
|
|
||||||
public SubscriptionService(
|
public SubscriptionService(
|
||||||
SubscriptionRepository subscriptionRepository,
|
Repository<Subscription> subscriptionRepository,
|
||||||
OneTimeJwtService oneTimeJwtService,
|
Repository<User> userRepository)
|
||||||
IdentityService identityService,
|
|
||||||
UserRepository userRepository
|
|
||||||
)
|
|
||||||
{
|
{
|
||||||
SubscriptionRepository = subscriptionRepository;
|
SubscriptionRepository = subscriptionRepository;
|
||||||
OneTimeJwtService = oneTimeJwtService;
|
|
||||||
IdentityService = identityService;
|
|
||||||
UserRepository = userRepository;
|
UserRepository = userRepository;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<Subscription?> GetCurrent()
|
public async Task<Subscription> Create(string name, string description, Currency currency, double price, int duration)
|
||||||
{
|
{
|
||||||
var user = await GetCurrentUser();
|
var optionsProduct = new ProductCreateOptions
|
||||||
|
|
||||||
if (user == null || user.CurrentSubscription == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var subscriptionEnd = user.SubscriptionSince.ToUniversalTime().AddDays(user.SubscriptionDuration);
|
|
||||||
|
|
||||||
if (subscriptionEnd > DateTime.UtcNow)
|
|
||||||
{
|
{
|
||||||
return user.CurrentSubscription;
|
Name = name,
|
||||||
}
|
Description = description,
|
||||||
|
DefaultPriceData = new()
|
||||||
|
{
|
||||||
|
UnitAmount = (long)(price * 100),
|
||||||
|
Currency = currency.ToString().ToLower()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return null;
|
var productService = new ProductService();
|
||||||
|
var product = await productService.CreateAsync(optionsProduct);
|
||||||
|
|
||||||
|
var subscription = new Subscription()
|
||||||
|
{
|
||||||
|
Name = name,
|
||||||
|
Description = description,
|
||||||
|
Currency = currency,
|
||||||
|
Price = price,
|
||||||
|
Duration = duration,
|
||||||
|
LimitsJson = "[]",
|
||||||
|
StripeProductId = product.Id,
|
||||||
|
StripePriceId = product.DefaultPriceId
|
||||||
|
};
|
||||||
|
|
||||||
|
return SubscriptionRepository.Add(subscription);
|
||||||
|
}
|
||||||
|
public async Task Update(Subscription subscription)
|
||||||
|
{
|
||||||
|
// Create the new price object
|
||||||
|
|
||||||
|
var optionsPrice = new PriceCreateOptions
|
||||||
|
{
|
||||||
|
UnitAmount = (long)(subscription.Price * 100),
|
||||||
|
Currency = subscription.Currency.ToString().ToLower(),
|
||||||
|
Product = subscription.StripeProductId
|
||||||
|
};
|
||||||
|
|
||||||
|
var servicePrice = new PriceService();
|
||||||
|
var price = await servicePrice.CreateAsync(optionsPrice);
|
||||||
|
|
||||||
|
// Update the product
|
||||||
|
|
||||||
|
var productService = new ProductService();
|
||||||
|
var product = await productService.UpdateAsync(subscription.StripeProductId, new()
|
||||||
|
{
|
||||||
|
Name = subscription.Name,
|
||||||
|
Description = subscription.Description,
|
||||||
|
DefaultPrice = price.Id
|
||||||
|
});
|
||||||
|
|
||||||
|
// Disable old price
|
||||||
|
await servicePrice.UpdateAsync(subscription.StripePriceId, new()
|
||||||
|
{
|
||||||
|
Active = false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update the model
|
||||||
|
|
||||||
|
subscription.StripeProductId = product.Id;
|
||||||
|
subscription.StripePriceId = product.DefaultPriceId;
|
||||||
|
|
||||||
|
SubscriptionRepository.Update(subscription);
|
||||||
|
}
|
||||||
|
public async Task Delete(Subscription subscription)
|
||||||
|
{
|
||||||
|
var productService = new ProductService();
|
||||||
|
await productService.DeleteAsync(subscription.StripeProductId);
|
||||||
|
|
||||||
|
SubscriptionRepository.Delete(subscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task ApplyCode(string code)
|
public Task UpdateLimits(Subscription subscription, SubscriptionLimit[] limits)
|
||||||
{
|
{
|
||||||
var data = await OneTimeJwtService.Validate(code);
|
subscription.LimitsJson = JsonConvert.SerializeObject(limits);
|
||||||
|
SubscriptionRepository.Update(subscription);
|
||||||
|
|
||||||
if (data == null)
|
return Task.CompletedTask;
|
||||||
throw new DisplayException("Invalid or expired subscription code");
|
}
|
||||||
|
public Task<SubscriptionLimit[]> GetLimits(Subscription subscription)
|
||||||
|
{
|
||||||
|
var limits =
|
||||||
|
JsonConvert.DeserializeObject<SubscriptionLimit[]>(subscription.LimitsJson) ?? Array.Empty<SubscriptionLimit>();
|
||||||
|
return Task.FromResult(limits);
|
||||||
|
}
|
||||||
|
|
||||||
var id = int.Parse(data["subscription"]);
|
public async Task<Subscription?> GetActiveSubscription(User u)
|
||||||
var duration = int.Parse(data["duration"]);
|
{
|
||||||
|
var user = await EnsureData(u);
|
||||||
|
|
||||||
var subscription = SubscriptionRepository
|
if (user.CurrentSubscription != null)
|
||||||
.Get()
|
{
|
||||||
.FirstOrDefault(x => x.Id == id);
|
if (user.SubscriptionExpires < DateTime.UtcNow)
|
||||||
|
{
|
||||||
|
user.CurrentSubscription = null;
|
||||||
|
UserRepository.Update(user);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (subscription == null)
|
return user.CurrentSubscription;
|
||||||
throw new DisplayException("The subscription the code is associated with does not exist");
|
}
|
||||||
|
public async Task CancelSubscription(User u)
|
||||||
|
{
|
||||||
|
var user = await EnsureData(u);
|
||||||
|
|
||||||
var user = await GetCurrentUser();
|
user.CurrentSubscription = null;
|
||||||
|
UserRepository.Update(user);
|
||||||
|
}
|
||||||
|
public async Task SetActiveSubscription(User u, Subscription subscription)
|
||||||
|
{
|
||||||
|
var user = await EnsureData(u);
|
||||||
|
|
||||||
if (user == null)
|
|
||||||
throw new DisplayException("Unable to determine current user");
|
|
||||||
|
|
||||||
user.CurrentSubscription = subscription;
|
|
||||||
user.SubscriptionDuration = duration;
|
|
||||||
user.SubscriptionSince = DateTime.UtcNow;
|
user.SubscriptionSince = DateTime.UtcNow;
|
||||||
|
user.SubscriptionExpires = DateTime.UtcNow.AddDays(subscription.Duration);
|
||||||
|
user.CurrentSubscription = subscription;
|
||||||
|
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
|
|
||||||
await OneTimeJwtService.Revoke(code);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Cancel()
|
public async Task<SubscriptionLimit[]> GetDefaultLimits()
|
||||||
{
|
{
|
||||||
if (await GetCurrent() != null)
|
var defaultSubscriptionJson = "[]";
|
||||||
|
var path = PathBuilder.File("storage", "configs", "default_subscription.json");
|
||||||
|
|
||||||
|
if (File.Exists(path))
|
||||||
{
|
{
|
||||||
var user = await GetCurrentUser();
|
defaultSubscriptionJson =
|
||||||
|
await File.ReadAllTextAsync(path);
|
||||||
user.CurrentSubscription = null;
|
|
||||||
|
|
||||||
UserRepository.Update(user);
|
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
public async Task<SubscriptionLimit> GetLimit(string identifier) // Cache, optimize sql code
|
return JsonConvert.DeserializeObject<SubscriptionLimit[]>(defaultSubscriptionJson)
|
||||||
|
?? Array.Empty<SubscriptionLimit>();
|
||||||
|
}
|
||||||
|
public async Task<SubscriptionLimit> GetLimit(User u, string identifier)
|
||||||
{
|
{
|
||||||
var subscription = await GetCurrent();
|
var subscription = await GetActiveSubscription(u);
|
||||||
var defaultLimits = await GetDefaultLimits();
|
var defaultLimits = await GetDefaultLimits();
|
||||||
|
|
||||||
if (subscription == null)
|
if (subscription != null) // User has a active subscriptions
|
||||||
{
|
{
|
||||||
// If the default subscription limit with identifier is found, return it. if not, return empty
|
var subscriptionLimits = await GetLimits(subscription);
|
||||||
return defaultLimits.FirstOrDefault(x => x.Identifier == identifier) ?? new()
|
|
||||||
{
|
|
||||||
Identifier = identifier,
|
|
||||||
Amount = 0
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
var subscriptionLimits =
|
var subscriptionLimit = subscriptionLimits
|
||||||
JsonConvert.DeserializeObject<SubscriptionLimit[]>(subscription.LimitsJson)
|
.FirstOrDefault(x => x.Identifier == identifier);
|
||||||
?? Array.Empty<SubscriptionLimit>();
|
|
||||||
|
|
||||||
var foundLimit = subscriptionLimits.FirstOrDefault(x => x.Identifier == identifier);
|
if (subscriptionLimit != null) // Found subscription limit for the user's subscription
|
||||||
|
return subscriptionLimit;
|
||||||
|
} // If were are here, the user's subscription has no limit for this identifier, so we fallback to default
|
||||||
|
|
||||||
if (foundLimit != null)
|
var defaultSubscriptionLimit = defaultLimits
|
||||||
return foundLimit;
|
.FirstOrDefault(x => x.Identifier == identifier);
|
||||||
|
|
||||||
// If the default subscription limit with identifier is found, return it. if not, return empty
|
if (defaultSubscriptionLimit != null)
|
||||||
return defaultLimits.FirstOrDefault(x => x.Identifier == identifier) ?? new()
|
return defaultSubscriptionLimit; // Default subscription limit found
|
||||||
|
|
||||||
|
return new() // No default subscription limit found
|
||||||
{
|
{
|
||||||
Identifier = identifier,
|
Identifier = identifier,
|
||||||
Amount = 0
|
Amount = 0
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private async Task<User?> GetCurrentUser()
|
private Task<User> EnsureData(User u)
|
||||||
{
|
{
|
||||||
var user = await IdentityService.Get();
|
var user = UserRepository
|
||||||
|
|
||||||
if (user == null)
|
|
||||||
return null;
|
|
||||||
|
|
||||||
var userWithData = UserRepository
|
|
||||||
.Get()
|
.Get()
|
||||||
.Include(x => x.CurrentSubscription)
|
.Include(x => x.CurrentSubscription)
|
||||||
.First(x => x.Id == user.Id);
|
.First(x => x.Id == u.Id);
|
||||||
|
|
||||||
return userWithData;
|
return Task.FromResult(user);
|
||||||
}
|
|
||||||
|
|
||||||
private async Task<SubscriptionLimit[]> GetDefaultLimits() // Add cache and reload option
|
|
||||||
{
|
|
||||||
var defaultSubscriptionJson = "[]";
|
|
||||||
|
|
||||||
if (File.Exists(PathBuilder.File("storage", "configs", "default_subscription.json")))
|
|
||||||
{
|
|
||||||
defaultSubscriptionJson =
|
|
||||||
await File.ReadAllTextAsync(PathBuilder.File("storage", "configs", "default_subscription.json"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return JsonConvert.DeserializeObject<SubscriptionLimit[]>(defaultSubscriptionJson) ?? Array.Empty<SubscriptionLimit>();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -34,7 +34,7 @@ public class SupportChatAdminService : IDisposable
|
|||||||
|
|
||||||
public async Task Start(User recipient)
|
public async Task Start(User recipient)
|
||||||
{
|
{
|
||||||
User = await IdentityService.Get();
|
User = IdentityService.User;
|
||||||
Recipient = recipient;
|
Recipient = recipient;
|
||||||
|
|
||||||
if (User != null)
|
if (User != null)
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ public class SupportChatClientService : IDisposable
|
|||||||
|
|
||||||
public async Task Start()
|
public async Task Start()
|
||||||
{
|
{
|
||||||
User = await IdentityService.Get();
|
User = IdentityService.User;
|
||||||
|
|
||||||
if (User != null)
|
if (User != null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -25,32 +25,30 @@ public class TotpService
|
|||||||
return Task.FromResult(codeserver == code);
|
return Task.FromResult(codeserver == code);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<bool> GetEnabled()
|
public Task<bool> GetEnabled()
|
||||||
{
|
{
|
||||||
var user = await IdentityService.Get();
|
return Task.FromResult(IdentityService.User.TotpEnabled);
|
||||||
|
|
||||||
return user!.TotpEnabled;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task<string> GetSecret()
|
public Task<string> GetSecret()
|
||||||
{
|
{
|
||||||
var user = await IdentityService.Get();
|
return Task.FromResult(IdentityService.User.TotpSecret);
|
||||||
|
|
||||||
return 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));;
|
user.TotpSecret = Base32Encoding.ToString(KeyGeneration.GenerateRandomKey(20));;
|
||||||
|
|
||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Enable(string code)
|
public async Task Enable(string code)
|
||||||
{
|
{
|
||||||
var user = (await IdentityService.Get())!;
|
var user = IdentityService.User;
|
||||||
|
|
||||||
if (!await Verify(user.TotpSecret, code))
|
if (!await Verify(user.TotpSecret, code))
|
||||||
{
|
{
|
||||||
@@ -61,9 +59,9 @@ public class TotpService
|
|||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
}
|
}
|
||||||
|
|
||||||
public async Task Disable()
|
public Task Disable()
|
||||||
{
|
{
|
||||||
var user = (await IdentityService.Get())!;
|
var user = IdentityService.User;
|
||||||
|
|
||||||
user.TotpEnabled = false;
|
user.TotpEnabled = false;
|
||||||
user.TotpSecret = "";
|
user.TotpSecret = "";
|
||||||
@@ -71,5 +69,7 @@ public class TotpService
|
|||||||
UserRepository.Update(user);
|
UserRepository.Update(user);
|
||||||
|
|
||||||
//TODO: AuditLog
|
//TODO: AuditLog
|
||||||
|
|
||||||
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -5,6 +5,7 @@ using Moonlight.App.Exceptions;
|
|||||||
using Moonlight.App.Helpers;
|
using Moonlight.App.Helpers;
|
||||||
using Moonlight.App.Models.Misc;
|
using Moonlight.App.Models.Misc;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
|
using Moonlight.App.Services.Background;
|
||||||
using Moonlight.App.Services.Mail;
|
using Moonlight.App.Services.Mail;
|
||||||
using Moonlight.App.Services.Sessions;
|
using Moonlight.App.Services.Sessions;
|
||||||
|
|
||||||
@@ -18,6 +19,8 @@ public class UserService
|
|||||||
private readonly IdentityService IdentityService;
|
private readonly IdentityService IdentityService;
|
||||||
private readonly IpLocateService IpLocateService;
|
private readonly IpLocateService IpLocateService;
|
||||||
private readonly DateTimeService DateTimeService;
|
private readonly DateTimeService DateTimeService;
|
||||||
|
private readonly ConfigService ConfigService;
|
||||||
|
private readonly TempMailService TempMailService;
|
||||||
|
|
||||||
private readonly string JwtSecret;
|
private readonly string JwtSecret;
|
||||||
|
|
||||||
@@ -28,14 +31,17 @@ public class UserService
|
|||||||
MailService mailService,
|
MailService mailService,
|
||||||
IdentityService identityService,
|
IdentityService identityService,
|
||||||
IpLocateService ipLocateService,
|
IpLocateService ipLocateService,
|
||||||
DateTimeService dateTimeService)
|
DateTimeService dateTimeService,
|
||||||
|
TempMailService tempMailService)
|
||||||
{
|
{
|
||||||
UserRepository = userRepository;
|
UserRepository = userRepository;
|
||||||
TotpService = totpService;
|
TotpService = totpService;
|
||||||
|
ConfigService = configService;
|
||||||
MailService = mailService;
|
MailService = mailService;
|
||||||
IdentityService = identityService;
|
IdentityService = identityService;
|
||||||
IpLocateService = ipLocateService;
|
IpLocateService = ipLocateService;
|
||||||
DateTimeService = dateTimeService;
|
DateTimeService = dateTimeService;
|
||||||
|
TempMailService = tempMailService;
|
||||||
|
|
||||||
JwtSecret = configService
|
JwtSecret = configService
|
||||||
.Get()
|
.Get()
|
||||||
@@ -44,6 +50,15 @@ public class UserService
|
|||||||
|
|
||||||
public async Task<string> Register(string email, string password, string firstname, string lastname)
|
public async Task<string> Register(string email, string password, string firstname, string lastname)
|
||||||
{
|
{
|
||||||
|
if (ConfigService.Get().Moonlight.Auth.DenyRegister)
|
||||||
|
throw new DisplayException("This operation was disabled");
|
||||||
|
|
||||||
|
if (await TempMailService.IsTempMail(email))
|
||||||
|
{
|
||||||
|
Logger.Warn($"A user tried to use a blacklisted domain to register. Email: '{email}'", "security");
|
||||||
|
throw new DisplayException("This email is blacklisted");
|
||||||
|
}
|
||||||
|
|
||||||
// Check if the email is already taken
|
// Check if the email is already taken
|
||||||
var emailTaken = UserRepository.Get().FirstOrDefault(x => x.Email == email) != null;
|
var emailTaken = UserRepository.Get().FirstOrDefault(x => x.Email == email) != null;
|
||||||
|
|
||||||
@@ -73,8 +88,8 @@ public class UserService
|
|||||||
TotpSecret = "",
|
TotpSecret = "",
|
||||||
UpdatedAt = DateTimeService.GetCurrent(),
|
UpdatedAt = DateTimeService.GetCurrent(),
|
||||||
TokenValidTime = DateTimeService.GetCurrent().AddDays(-5),
|
TokenValidTime = DateTimeService.GetCurrent().AddDays(-5),
|
||||||
LastIp = IdentityService.GetIp(),
|
LastIp = IdentityService.Ip,
|
||||||
RegisterIp = IdentityService.GetIp()
|
RegisterIp = IdentityService.Ip
|
||||||
});
|
});
|
||||||
|
|
||||||
await MailService.SendMail(user!, "register", values => {});
|
await MailService.SendMail(user!, "register", values => {});
|
||||||
@@ -108,6 +123,9 @@ public class UserService
|
|||||||
|
|
||||||
public async Task<string> Login(string email, string password, string totpCode = "")
|
public async Task<string> Login(string email, string password, string totpCode = "")
|
||||||
{
|
{
|
||||||
|
if (ConfigService.Get().Moonlight.Auth.DenyLogin)
|
||||||
|
throw new DisplayException("This operation was disabled");
|
||||||
|
|
||||||
// First password check and check if totp is enabled
|
// First password check and check if totp is enabled
|
||||||
var needTotp = await CheckTotp(email, password);
|
var needTotp = await CheckTotp(email, password);
|
||||||
|
|
||||||
@@ -159,8 +177,8 @@ public class UserService
|
|||||||
|
|
||||||
await MailService.SendMail(user!, "passwordChange", values =>
|
await MailService.SendMail(user!, "passwordChange", values =>
|
||||||
{
|
{
|
||||||
values.Add("Ip", IdentityService.GetIp());
|
values.Add("Ip", IdentityService.Ip);
|
||||||
values.Add("Device", IdentityService.GetDevice());
|
values.Add("Device", IdentityService.Device);
|
||||||
values.Add("Location", location);
|
values.Add("Location", location);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -197,8 +215,8 @@ public class UserService
|
|||||||
{
|
{
|
||||||
await MailService.SendMail(user!, "login", values =>
|
await MailService.SendMail(user!, "login", values =>
|
||||||
{
|
{
|
||||||
values.Add("Ip", IdentityService.GetIp());
|
values.Add("Ip", IdentityService.Ip);
|
||||||
values.Add("Device", IdentityService.GetDevice());
|
values.Add("Device", IdentityService.Device);
|
||||||
values.Add("Location", location);
|
values.Add("Location", location);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -234,8 +252,8 @@ public class UserService
|
|||||||
|
|
||||||
await MailService.SendMail(user, "passwordReset", values =>
|
await MailService.SendMail(user, "passwordReset", values =>
|
||||||
{
|
{
|
||||||
values.Add("Ip", IdentityService.GetIp());
|
values.Add("Ip", IdentityService.Ip);
|
||||||
values.Add("Device", IdentityService.GetDevice());
|
values.Add("Device", IdentityService.Device);
|
||||||
values.Add("Location", location);
|
values.Add("Location", location);
|
||||||
values.Add("Password", newPassword);
|
values.Add("Password", newPassword);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -2,12 +2,19 @@
|
|||||||
|
|
||||||
<Router AppAssembly="@typeof(BlazorApp).Assembly">
|
<Router AppAssembly="@typeof(BlazorApp).Assembly">
|
||||||
<Found Context="routeData">
|
<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>
|
</Found>
|
||||||
<NotFound>
|
<NotFound>
|
||||||
<PageTitle>Not found</PageTitle>
|
<PageTitle>Not found</PageTitle>
|
||||||
<LayoutView Layout="@typeof(NotFoundLayout)">
|
<LayoutView Layout="@typeof(NotFoundLayout)">
|
||||||
<p role="alert">Sorry, there's nothing at this address.</p>
|
<NotFoundAlert />
|
||||||
</LayoutView>
|
</LayoutView>
|
||||||
</NotFound>
|
</NotFound>
|
||||||
</Router>
|
</Router>
|
||||||
|
|
||||||
|
@code
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -53,6 +53,7 @@
|
|||||||
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.1-dev-00910" />
|
<PackageReference Include="Serilog.Sinks.Console" Version="4.1.1-dev-00910" />
|
||||||
<PackageReference Include="Serilog.Sinks.File" Version="5.0.1-dev-00947" />
|
<PackageReference Include="Serilog.Sinks.File" Version="5.0.1-dev-00947" />
|
||||||
<PackageReference Include="SSH.NET" Version="2020.0.2" />
|
<PackageReference Include="SSH.NET" Version="2020.0.2" />
|
||||||
|
<PackageReference Include="Stripe.net" Version="41.23.0-beta.1" />
|
||||||
<PackageReference Include="UAParser" Version="3.1.47" />
|
<PackageReference Include="UAParser" Version="3.1.47" />
|
||||||
<PackageReference Include="XtermBlazor" Version="1.8.1" />
|
<PackageReference Include="XtermBlazor" Version="1.8.1" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
@@ -76,6 +77,13 @@
|
|||||||
<_ContentIncludedByDefault Remove="Shared\Views\Admin\Servers\Cleanup\Exceptions\Edit.razor" />
|
<_ContentIncludedByDefault Remove="Shared\Views\Admin\Servers\Cleanup\Exceptions\Edit.razor" />
|
||||||
<_ContentIncludedByDefault Remove="Shared\Components\News\NewsEditor.razor" />
|
<_ContentIncludedByDefault Remove="Shared\Components\News\NewsEditor.razor" />
|
||||||
<_ContentIncludedByDefault Remove="Shared\News\Edit.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" />
|
||||||
|
<_ContentIncludedByDefault Remove="Shared\Components\AuditLogEntrys\AuditLogEntryChangePassword.razor" />
|
||||||
|
<_ContentIncludedByDefault Remove="Shared\Components\AuditLogEntrys\AuditLogEntryChangePowerState.razor" />
|
||||||
|
<_ContentIncludedByDefault Remove="Shared\Components\AuditLogEntrys\AuditLogEntryLogin.razor" />
|
||||||
|
<_ContentIncludedByDefault Remove="Shared\Components\AuditLogEntrys\AuditLogEntryRegister.razor" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
using BlazorDownloadFile;
|
using BlazorDownloadFile;
|
||||||
using BlazorTable;
|
using BlazorTable;
|
||||||
using CurrieTechnologies.Razor.SweetAlert2;
|
|
||||||
using HealthChecks.UI.Client;
|
using HealthChecks.UI.Client;
|
||||||
using Moonlight.App.ApiClients.CloudPanel;
|
using Moonlight.App.ApiClients.CloudPanel;
|
||||||
using Moonlight.App.ApiClients.Daemon;
|
using Moonlight.App.ApiClients.Daemon;
|
||||||
@@ -16,7 +15,6 @@ using Moonlight.App.Helpers.Wings;
|
|||||||
using Moonlight.App.LogMigrator;
|
using Moonlight.App.LogMigrator;
|
||||||
using Moonlight.App.Repositories;
|
using Moonlight.App.Repositories;
|
||||||
using Moonlight.App.Repositories.Domains;
|
using Moonlight.App.Repositories.Domains;
|
||||||
using Moonlight.App.Repositories.LogEntries;
|
|
||||||
using Moonlight.App.Repositories.Servers;
|
using Moonlight.App.Repositories.Servers;
|
||||||
using Moonlight.App.Services;
|
using Moonlight.App.Services;
|
||||||
using Moonlight.App.Services.Addon;
|
using Moonlight.App.Services.Addon;
|
||||||
@@ -33,6 +31,8 @@ using Moonlight.App.Services.SupportChat;
|
|||||||
using Sentry;
|
using Sentry;
|
||||||
using Serilog;
|
using Serilog;
|
||||||
using Serilog.Events;
|
using Serilog.Events;
|
||||||
|
using Stripe;
|
||||||
|
using SubscriptionService = Moonlight.App.Services.SubscriptionService;
|
||||||
|
|
||||||
namespace Moonlight
|
namespace Moonlight
|
||||||
{
|
{
|
||||||
@@ -103,8 +103,6 @@ namespace Moonlight
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Logger.Info($"Working dir: {Directory.GetCurrentDirectory()}");
|
|
||||||
|
|
||||||
Logger.Info("Running pre-init tasks");
|
Logger.Info("Running pre-init tasks");
|
||||||
var databaseCheckupService = new DatabaseCheckupService(configService);
|
var databaseCheckupService = new DatabaseCheckupService(configService);
|
||||||
|
|
||||||
@@ -168,9 +166,6 @@ namespace Moonlight
|
|||||||
builder.Services.AddScoped<NewsEntryRepository>();
|
builder.Services.AddScoped<NewsEntryRepository>();
|
||||||
builder.Services.AddScoped<NodeAllocationRepository>();
|
builder.Services.AddScoped<NodeAllocationRepository>();
|
||||||
builder.Services.AddScoped<StatisticsRepository>();
|
builder.Services.AddScoped<StatisticsRepository>();
|
||||||
builder.Services.AddScoped<AuditLogEntryRepository>();
|
|
||||||
builder.Services.AddScoped<ErrorLogEntryRepository>();
|
|
||||||
builder.Services.AddScoped<SecurityLogEntryRepository>();
|
|
||||||
builder.Services.AddScoped(typeof(Repository<>));
|
builder.Services.AddScoped(typeof(Repository<>));
|
||||||
|
|
||||||
// Services
|
// Services
|
||||||
@@ -210,16 +205,13 @@ namespace Moonlight
|
|||||||
builder.Services.AddScoped<DynamicBackgroundService>();
|
builder.Services.AddScoped<DynamicBackgroundService>();
|
||||||
builder.Services.AddScoped<ServerAddonPluginService>();
|
builder.Services.AddScoped<ServerAddonPluginService>();
|
||||||
builder.Services.AddScoped<KeyListenerService>();
|
builder.Services.AddScoped<KeyListenerService>();
|
||||||
|
builder.Services.AddScoped<PopupService>();
|
||||||
builder.Services.AddScoped<SubscriptionService>();
|
builder.Services.AddScoped<SubscriptionService>();
|
||||||
builder.Services.AddScoped<SubscriptionAdminService>();
|
builder.Services.AddScoped<BillingService>();
|
||||||
|
|
||||||
builder.Services.AddScoped<SessionClientService>();
|
builder.Services.AddScoped<SessionClientService>();
|
||||||
builder.Services.AddSingleton<SessionServerService>();
|
builder.Services.AddSingleton<SessionServerService>();
|
||||||
|
|
||||||
// Loggers
|
|
||||||
builder.Services.AddScoped<MailService>();
|
builder.Services.AddScoped<MailService>();
|
||||||
builder.Services.AddSingleton<TrashMailDetectorService>();
|
|
||||||
|
|
||||||
// Support chat
|
// Support chat
|
||||||
builder.Services.AddSingleton<SupportChatServerService>();
|
builder.Services.AddSingleton<SupportChatServerService>();
|
||||||
@@ -246,6 +238,7 @@ namespace Moonlight
|
|||||||
builder.Services.AddSingleton<CleanupService>();
|
builder.Services.AddSingleton<CleanupService>();
|
||||||
builder.Services.AddSingleton<MalwareScanService>();
|
builder.Services.AddSingleton<MalwareScanService>();
|
||||||
builder.Services.AddSingleton<TelemetryService>();
|
builder.Services.AddSingleton<TelemetryService>();
|
||||||
|
builder.Services.AddSingleton<TempMailService>();
|
||||||
|
|
||||||
// Other
|
// Other
|
||||||
builder.Services.AddSingleton<MoonlightService>();
|
builder.Services.AddSingleton<MoonlightService>();
|
||||||
@@ -255,6 +248,10 @@ namespace Moonlight
|
|||||||
builder.Services.AddBlazorContextMenu();
|
builder.Services.AddBlazorContextMenu();
|
||||||
builder.Services.AddBlazorDownloadFile();
|
builder.Services.AddBlazorDownloadFile();
|
||||||
|
|
||||||
|
StripeConfiguration.ApiKey = configService
|
||||||
|
.Get()
|
||||||
|
.Moonlight.Stripe.ApiKey;
|
||||||
|
|
||||||
var app = builder.Build();
|
var app = builder.Build();
|
||||||
|
|
||||||
// Configure the HTTP request pipeline.
|
// Configure the HTTP request pipeline.
|
||||||
@@ -291,6 +288,7 @@ namespace Moonlight
|
|||||||
_ = app.Services.GetRequiredService<DiscordNotificationService>();
|
_ = app.Services.GetRequiredService<DiscordNotificationService>();
|
||||||
_ = app.Services.GetRequiredService<MalwareScanService>();
|
_ = app.Services.GetRequiredService<MalwareScanService>();
|
||||||
_ = app.Services.GetRequiredService<TelemetryService>();
|
_ = app.Services.GetRequiredService<TelemetryService>();
|
||||||
|
_ = app.Services.GetRequiredService<TempMailService>();
|
||||||
|
|
||||||
_ = app.Services.GetRequiredService<MoonlightService>();
|
_ = app.Services.GetRequiredService<MoonlightService>();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
{
|
{
|
||||||
"profiles": {
|
"profiles":
|
||||||
"Moonlight": {
|
{
|
||||||
|
"Default":
|
||||||
|
{
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
@@ -10,41 +12,29 @@
|
|||||||
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
||||||
"dotnetRunMessages": true
|
"dotnetRunMessages": true
|
||||||
},
|
},
|
||||||
"Sql Debug": {
|
"Live DB":
|
||||||
|
{
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
"ML_SQL_DEBUG": "true",
|
"ML_DEBUG": "true",
|
||||||
"ML_DEBUG": "true"
|
"ML_CONFIG_PATH": "storage\\configs\\live_config.json"
|
||||||
},
|
},
|
||||||
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
||||||
"dotnetRunMessages": true
|
"dotnetRunMessages": true
|
||||||
},
|
},
|
||||||
"Discord Bot": {
|
"Dev DB 1":
|
||||||
|
{
|
||||||
"commandName": "Project",
|
"commandName": "Project",
|
||||||
"launchBrowser": false,
|
|
||||||
"environmentVariables": {
|
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
|
||||||
"ML_SQL_DEBUG": "true",
|
|
||||||
"ML_DEBUG": "true"
|
|
||||||
},
|
|
||||||
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
|
||||||
"dotnetRunMessages": true
|
|
||||||
},
|
|
||||||
"Watch": {
|
|
||||||
"commandName": "Executable",
|
|
||||||
"executablePath": "dotnet",
|
|
||||||
"workingDirectory": "$(ProjectDir)",
|
|
||||||
"hotReloadEnabled": true,
|
|
||||||
"hotReloadProfile": "aspnetcore",
|
|
||||||
"commandLineArgs": "watch run",
|
|
||||||
"launchBrowser": true,
|
"launchBrowser": true,
|
||||||
"environmentVariables": {
|
"environmentVariables": {
|
||||||
"ASPNETCORE_ENVIRONMENT": "Development",
|
"ASPNETCORE_ENVIRONMENT": "Development",
|
||||||
"ML_DEBUG": "true"
|
"ML_DEBUG": "true",
|
||||||
|
"ML_CONFIG_PATH": "storage\\configs\\dev_1_config.json"
|
||||||
},
|
},
|
||||||
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118"
|
"applicationUrl": "http://moonlight.testy:5118;https://localhost:7118;http://localhost:5118",
|
||||||
|
"dotnetRunMessages": true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
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>
|
<span class="bullet me-5"></span> <TL>The resource was deleted</TL>
|
||||||
</li>
|
</li>
|
||||||
<li class="d-flex align-items-center py-2">
|
<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>
|
||||||
<li class="d-flex align-items-center py-2">
|
<li class="d-flex align-items-center py-2">
|
||||||
<span class="bullet me-5"></span> <TL>You may have entered invalid data</TL>
|
<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>
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
@using Moonlight.App.Database.Entities.LogsEntries
|
|
||||||
@using Moonlight.App.Helpers
|
|
||||||
@using Moonlight.App.Repositories
|
|
||||||
@using Newtonsoft.Json
|
|
||||||
@using Moonlight.App.Database.Entities
|
|
||||||
@using Moonlight.App.Models.Log
|
|
||||||
|
|
||||||
@inject UserRepository UserRepository
|
|
||||||
|
|
||||||
<div class="timeline-item">
|
|
||||||
<div class="timeline-line w-40px"></div>
|
|
||||||
<div class="timeline-icon symbol symbol-circle symbol-40px">
|
|
||||||
<div class="symbol-label bg-light">
|
|
||||||
<i class="bx bx-md bx-log-in"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="timeline-content mb-10 mt-n2">
|
|
||||||
<div class="overflow-auto pe-3">
|
|
||||||
<div class="fs-5 fw-semibold mb-2">
|
|
||||||
@if (User == null)
|
|
||||||
{
|
|
||||||
<TL>Password change for</TL> @(Data[0].Value)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<TL>Password change for</TL> <a href="/admin/users/view/@(User.Id)">@(User.Email)</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="d-flex align-items-center mt-1 fs-6">
|
|
||||||
<div class="text-muted me-2 fs-7">@(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code
|
|
||||||
{
|
|
||||||
[Parameter]
|
|
||||||
public AuditLogEntry Entry { get; set; }
|
|
||||||
|
|
||||||
private User? User;
|
|
||||||
private LogData[] Data;
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
Data = JsonConvert.DeserializeObject<LogData[]>(Entry.JsonData)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0].Value);
|
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
@using Moonlight.App.Database.Entities.LogsEntries
|
|
||||||
@using Moonlight.App.Helpers
|
|
||||||
@using Newtonsoft.Json
|
|
||||||
@using Moonlight.App.Database.Entities
|
|
||||||
@using Moonlight.App.Models.Log
|
|
||||||
@using Moonlight.App.Repositories.Servers
|
|
||||||
|
|
||||||
@inject ServerRepository ServerRepository
|
|
||||||
|
|
||||||
<div class="timeline-item">
|
|
||||||
<div class="timeline-line w-40px"></div>
|
|
||||||
<div class="timeline-icon symbol symbol-circle symbol-40px">
|
|
||||||
<div class="symbol-label bg-light">
|
|
||||||
<i class="bx bx-md bx-log-in"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="timeline-content mb-10 mt-n2">
|
|
||||||
<div class="overflow-auto pe-3">
|
|
||||||
<div class="fs-5 fw-semibold mb-2">
|
|
||||||
@if (Server == null)
|
|
||||||
{
|
|
||||||
<TL>Change power state for</TL> @(Data[0].Value) <TL>to</TL> @(Data[1].Value)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<TL>Change power state for</TL> <a href="/admin/servers/edit/@(Server.Id)">@(Server.Name)</a> <TL>to</TL> @(Data[1].Value)
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="d-flex align-items-center mt-1 fs-6">
|
|
||||||
<div class="text-muted me-2 fs-7">@(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code
|
|
||||||
{
|
|
||||||
[Parameter]
|
|
||||||
public AuditLogEntry Entry { get; set; }
|
|
||||||
|
|
||||||
private Server? Server;
|
|
||||||
private LogData[] Data;
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
Data = JsonConvert.DeserializeObject<LogData[]>(Entry.JsonData)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
Server = ServerRepository.Get().FirstOrDefault(x => x.Uuid == Guid.Parse(Data[0].Value));
|
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
@using Moonlight.App.Database.Entities.LogsEntries
|
|
||||||
@using Moonlight.App.Helpers
|
|
||||||
@using Moonlight.App.Repositories
|
|
||||||
@using Newtonsoft.Json
|
|
||||||
@using Moonlight.App.Database.Entities
|
|
||||||
@using Moonlight.App.Models.Log
|
|
||||||
|
|
||||||
@inject UserRepository UserRepository
|
|
||||||
|
|
||||||
<div class="timeline-item">
|
|
||||||
<div class="timeline-line w-40px"></div>
|
|
||||||
<div class="timeline-icon symbol symbol-circle symbol-40px">
|
|
||||||
<div class="symbol-label bg-light">
|
|
||||||
<i class="bx bx-md bx-log-in"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="timeline-content mb-10 mt-n2">
|
|
||||||
<div class="overflow-auto pe-3">
|
|
||||||
<div class="fs-5 fw-semibold mb-2">
|
|
||||||
@if (User == null)
|
|
||||||
{
|
|
||||||
<TL>New login for</TL> @(Data[0].Value)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<TL>New login for</TL> <a href="/admin/users/view/@(User.Id)">@(User.Email)</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="d-flex align-items-center mt-1 fs-6">
|
|
||||||
<div class="text-muted me-2 fs-7">@(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code
|
|
||||||
{
|
|
||||||
[Parameter]
|
|
||||||
public AuditLogEntry Entry { get; set; }
|
|
||||||
|
|
||||||
private User? User;
|
|
||||||
private LogData[] Data;
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
Data = JsonConvert.DeserializeObject<LogData[]>(Entry.JsonData)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0].Value);
|
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
@using Moonlight.App.Database.Entities.LogsEntries
|
|
||||||
@using Moonlight.App.Helpers
|
|
||||||
@using Moonlight.App.Repositories
|
|
||||||
@using Newtonsoft.Json
|
|
||||||
@using Moonlight.App.Database.Entities
|
|
||||||
@using Moonlight.App.Models.Log
|
|
||||||
|
|
||||||
@inject UserRepository UserRepository
|
|
||||||
|
|
||||||
<div class="timeline-item">
|
|
||||||
<div class="timeline-line w-40px"></div>
|
|
||||||
<div class="timeline-icon symbol symbol-circle symbol-40px">
|
|
||||||
<div class="symbol-label bg-light">
|
|
||||||
<i class="bx bx-md bx-log-in"></i>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="timeline-content mb-10 mt-n2">
|
|
||||||
<div class="overflow-auto pe-3">
|
|
||||||
<div class="fs-5 fw-semibold mb-2">
|
|
||||||
@if (User == null)
|
|
||||||
{
|
|
||||||
<TL>Register for</TL> @(Data[0].Value)
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
<TL>Register for</TL> <a href="/admin/users/view/@(User.Id)">@(User.Email)</a>
|
|
||||||
}
|
|
||||||
</div>
|
|
||||||
<div class="d-flex align-items-center mt-1 fs-6">
|
|
||||||
<div class="text-muted me-2 fs-7">@(Formatter.FormatDate(Entry.CreatedAt)), @(Entry.System ? "System" : Entry.Ip)</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
@code
|
|
||||||
{
|
|
||||||
[Parameter]
|
|
||||||
public AuditLogEntry Entry { get; set; }
|
|
||||||
|
|
||||||
private User? User;
|
|
||||||
private LogData[] Data;
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
|
||||||
{
|
|
||||||
Data = JsonConvert.DeserializeObject<LogData[]>(Entry.JsonData)!;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
|
||||||
{
|
|
||||||
if (firstRender)
|
|
||||||
{
|
|
||||||
User = UserRepository.Get().FirstOrDefault(x => x.Email == Data[0].Value);
|
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -49,9 +49,10 @@
|
|||||||
private PasswordModel Password = new();
|
private PasswordModel Password = new();
|
||||||
private User User;
|
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()
|
private async Task DoChange()
|
||||||
|
|||||||
@@ -50,9 +50,10 @@
|
|||||||
private User User;
|
private User User;
|
||||||
private NameModel Name = new ();
|
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()
|
private async Task SetName()
|
||||||
|
|||||||
@@ -57,7 +57,7 @@ else
|
|||||||
{
|
{
|
||||||
receivedExceptions.Add(exception);
|
receivedExceptions.Add(exception);
|
||||||
|
|
||||||
var user = await IdentityService.Get();
|
var user = IdentityService.User;
|
||||||
var id = user == null ? -1 : user.Id;
|
var id = user == null ? -1 : user.Id;
|
||||||
|
|
||||||
Logger.Error($"[{id}] An unhanded exception occured:");
|
Logger.Error($"[{id}] An unhanded exception occured:");
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
@using Moonlight.App.ApiClients.Modrinth
|
@using Moonlight.App.ApiClients.Modrinth
|
||||||
@using Moonlight.App.ApiClients.Wings
|
@using Moonlight.App.ApiClients.Wings
|
||||||
@using Moonlight.App.Helpers
|
@using Moonlight.App.Helpers
|
||||||
|
@using Stripe
|
||||||
@inherits ErrorBoundaryBase
|
@inherits ErrorBoundaryBase
|
||||||
|
|
||||||
@inject AlertService AlertService
|
@inject AlertService AlertService
|
||||||
@@ -105,6 +106,13 @@ else
|
|||||||
{
|
{
|
||||||
await AlertService.Error(SmartTranslateService.Translate("This function is not implemented"));
|
await AlertService.Error(SmartTranslateService.Translate("This function is not implemented"));
|
||||||
}
|
}
|
||||||
|
else if (exception is StripeException stripeException)
|
||||||
|
{
|
||||||
|
await AlertService.Error(
|
||||||
|
SmartTranslateService.Translate("Unknown error from stripe"),
|
||||||
|
stripeException.Message
|
||||||
|
);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
Logger.Warn(exception);
|
Logger.Warn(exception);
|
||||||
|
|||||||
@@ -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,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/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>
|
||||||
|
<li class="nav-item mt-2">
|
||||||
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 4 ? "active" : "")" href="/admin/security/logs">
|
||||||
|
<TL>Logs</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>
|
<TL>Overview</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li class="nav-item mt-2">
|
|
||||||
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 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">
|
<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">
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 5 ? "active" : "")" href="/admin/system/resources">
|
||||||
<TL>Resources</TL>
|
<TL>Resources</TL>
|
||||||
@@ -44,6 +29,11 @@
|
|||||||
<TL>Configuration</TL>
|
<TL>Configuration</TL>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li class="nav-item mt-2">
|
||||||
|
<a class="nav-link text-active-primary ms-0 me-10 py-5 @(Index == 9 ? "active" : "")" href="/admin/system/mail">
|
||||||
|
<TL>Mail</TL>
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,5 +1,8 @@
|
|||||||
@using Moonlight.App.Database.Entities
|
@using Moonlight.App.Database.Entities
|
||||||
@using Moonlight.App.Models.Misc
|
@using Moonlight.App.Models.Misc
|
||||||
|
@using Moonlight.App.Services.Sessions
|
||||||
|
|
||||||
|
@inject IdentityService IdentityService
|
||||||
|
|
||||||
<div class="card mb-5 mb-xl-10">
|
<div class="card mb-5 mb-xl-10">
|
||||||
<div class="card-body pt-9 pb-0">
|
<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 justify-content-between align-items-start flex-wrap mb-2">
|
||||||
<div class="d-flex flex-column">
|
<div class="d-flex flex-column">
|
||||||
<div class="d-flex align-items-center mb-2">
|
<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>
|
<i class="text-success bx bx-md bxs-badge-check"></i>
|
||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
<div class="d-flex flex-wrap fw-semibold fs-6 mb-4 pe-2">
|
<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" : "")">
|
<span class="d-flex align-items-center text-gray-400 mb-2 @(IdentityService.User.StreamerMode ? "blur" : "")">
|
||||||
@(User.Email)
|
@(IdentityService.User.Email)
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -51,9 +54,6 @@
|
|||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
[CascadingParameter]
|
|
||||||
public User User { get; set; }
|
|
||||||
|
|
||||||
[Parameter]
|
[Parameter]
|
||||||
public int Index { get; set; } = 0;
|
public int Index { get; set; } = 0;
|
||||||
}
|
}
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
User = await IdentityService.Get();
|
User = IdentityService.User;
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
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)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
User = await IdentityService.Get();
|
User = IdentityService.User;
|
||||||
sidebar = await JsRuntime.InvokeAsync<string>("document.body.getAttribute", "data-kt-app-layout");
|
sidebar = await JsRuntime.InvokeAsync<string>("document.body.getAttribute", "data-kt-app-layout");
|
||||||
StateHasChanged();
|
StateHasChanged();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ else
|
|||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
if (User.Admin)
|
if (IdentityService.Permissions.HasAnyPermissions())
|
||||||
{
|
{
|
||||||
<div class="menu-item pt-5">
|
<div class="menu-item pt-5">
|
||||||
<div class="menu-content">
|
<div class="menu-content">
|
||||||
@@ -92,60 +92,21 @@ else
|
|||||||
<span class="menu-title"><TL>System</TL></span>
|
<span class="menu-title"><TL>System</TL></span>
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div data-kt-menu-trigger="click" class="menu-item menu-accordion">
|
<div class="menu-item">
|
||||||
<span class="menu-link">
|
<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">
|
<span class="menu-icon">
|
||||||
<i class="bx bx-server"></i>
|
<i class="bx bx-server"></i>
|
||||||
</span>
|
</span>
|
||||||
<span class="menu-title"><TL>Servers</TL></span>
|
<span class="menu-title"><TL>Servers</TL></span>
|
||||||
<span class="menu-arrow"></span>
|
</a>
|
||||||
</span>
|
|
||||||
<div class="menu-sub menu-sub-accordion">
|
|
||||||
<div class="menu-item">
|
|
||||||
<a class="menu-link" href="/admin/servers">
|
|
||||||
<span class="menu-bullet">
|
|
||||||
<span class="bullet bullet-dot"></span>
|
|
||||||
</span>
|
|
||||||
<span class="menu-title"><TL>Overview</TL></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="menu-item">
|
|
||||||
<a class="menu-link" href="/admin/servers/manager">
|
|
||||||
<span class="menu-bullet">
|
|
||||||
<span class="bullet bullet-dot"></span>
|
|
||||||
</span>
|
|
||||||
<span class="menu-title"><TL>Manager</TL></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="menu-item">
|
|
||||||
<a class="menu-link" href="/admin/servers/cleanup">
|
|
||||||
<span class="menu-bullet">
|
|
||||||
<span class="bullet bullet-dot"></span>
|
|
||||||
</span>
|
|
||||||
<span class="menu-title"><TL>Cleanup</TL></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="menu-item">
|
|
||||||
<a class="menu-link" href="/admin/nodes">
|
|
||||||
<span class="menu-bullet">
|
|
||||||
<span class="bullet bullet-dot"></span>
|
|
||||||
</span>
|
|
||||||
<span class="menu-title"><TL>Nodes</TL></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="menu-item">
|
|
||||||
<a class="menu-link" href="/admin/servers/images">
|
|
||||||
<span class="menu-bullet">
|
|
||||||
<span class="bullet bullet-dot"></span>
|
|
||||||
</span>
|
|
||||||
<span class="menu-title"><TL>Images</TL></span>
|
|
||||||
</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
<div class="menu-item">
|
<div class="menu-item">
|
||||||
<a class="menu-link" href="/admin/webspaces">
|
<a class="menu-link" href="/admin/webspaces">
|
||||||
@@ -228,7 +189,7 @@ else
|
|||||||
{
|
{
|
||||||
if (firstRender)
|
if (firstRender)
|
||||||
{
|
{
|
||||||
User = await IdentityService.Get();
|
User = IdentityService.User;
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
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>
|
<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="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">
|
<div class="app-page flex-column flex-column-fluid" id="kt_app_page">
|
||||||
<canvas id="snow" class="snow-canvas"></canvas>
|
<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>
|
<PageHeader></PageHeader>
|
||||||
<div class="app-wrapper flex-column flex-row-fluid" id="kt_app_wrapper">
|
<div class="app-wrapper flex-column flex-row-fluid" id="kt_app_wrapper">
|
||||||
<Sidebar></Sidebar>
|
<Sidebar></Sidebar>
|
||||||
<div class="app-main flex-column flex-row-fluid" id="kt_app_main">
|
<div class="app-main flex-column flex-row-fluid" id="kt_app_main">
|
||||||
<div class="d-flex flex-column flex-column-fluid">
|
<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" 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 id="kt_app_content_container" class="app-container container-fluid">
|
||||||
<div class="mt-10">
|
<div class="mt-10">
|
||||||
<SoftErrorBoundary>
|
<SoftErrorBoundary>
|
||||||
@if (!IsIpBanned)
|
@if (!IsIpBanned)
|
||||||
|
{
|
||||||
|
if (UserProcessed)
|
||||||
{
|
{
|
||||||
if (UserProcessed)
|
if (uri.LocalPath != "/login" &&
|
||||||
|
uri.LocalPath != "/passwordreset" &&
|
||||||
|
uri.LocalPath != "/register")
|
||||||
{
|
{
|
||||||
if (uri.LocalPath != "/login" &&
|
if (IdentityService.User == null)
|
||||||
uri.LocalPath != "/passwordreset" &&
|
|
||||||
uri.LocalPath != "/register")
|
|
||||||
{
|
{
|
||||||
if (User == null)
|
<Login></Login>
|
||||||
{
|
|
||||||
<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/>
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
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
|
else
|
||||||
{
|
{
|
||||||
<div class="modal d-block">
|
if (uri.LocalPath == "/login")
|
||||||
<div class="modal-dialog modal-dialog-centered mw-900px">
|
{
|
||||||
<div class="modal-content">
|
<Login></Login>
|
||||||
<div class="pt-2 modal-body py-lg-10 px-lg-10">
|
}
|
||||||
<h2>@(SmartTranslateService.Translate("Authenticating"))...</h2>
|
else if (uri.LocalPath == "/register")
|
||||||
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Verifying token, loading user data"))</p>
|
{
|
||||||
</div>
|
<Register></Register>
|
||||||
</div>
|
}
|
||||||
</div>
|
else if (uri.LocalPath == "/passwordreset")
|
||||||
</div>
|
{
|
||||||
|
<PasswordReset></PasswordReset>
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -136,36 +123,48 @@
|
|||||||
<div class="modal-dialog modal-dialog-centered mw-900px">
|
<div class="modal-dialog modal-dialog-centered mw-900px">
|
||||||
<div class="modal-content">
|
<div class="modal-content">
|
||||||
<div class="pt-2 modal-body py-lg-10 px-lg-10">
|
<div class="pt-2 modal-body py-lg-10 px-lg-10">
|
||||||
<h2>@(SmartTranslateService.Translate("Your ip has been banned"))</h2>
|
<h2>@(SmartTranslateService.Translate("Authenticating"))...</h2>
|
||||||
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Your ip address has been banned by an admin"))</p>
|
<p class="mt-3 fw-normal fs-6">@(SmartTranslateService.Translate("Verifying token, loading user data"))</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</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>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<Footer></Footer>
|
|
||||||
</div>
|
</div>
|
||||||
|
<Footer></Footer>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</CascadingValue>
|
</div>
|
||||||
</GlobalErrorBoundary>
|
</GlobalErrorBoundary>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
private User? User;
|
|
||||||
private bool UserProcessed = false;
|
private bool UserProcessed = false;
|
||||||
|
|
||||||
private bool IsIpBanned = false;
|
private bool IsIpBanned = false;
|
||||||
|
|
||||||
protected override void OnAfterRender(bool firstRender)
|
protected override void OnAfterRender(bool firstRender)
|
||||||
{
|
{
|
||||||
if(firstRender)
|
if (firstRender)
|
||||||
AddBodyAttribute("data-kt-app-page-loading", "on");
|
AddBodyAttribute("data-kt-app-page-loading", "on");
|
||||||
|
|
||||||
//Initialize classes and attributes for layout with dark sidebar
|
//Initialize classes and attributes for layout with dark sidebar
|
||||||
@@ -202,7 +201,7 @@
|
|||||||
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
NavigationManager.NavigateTo(NavigationManager.Uri, true);
|
||||||
});
|
});
|
||||||
|
|
||||||
User = await IdentityService.Get();
|
await IdentityService.Load();
|
||||||
UserProcessed = true;
|
UserProcessed = true;
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
|
|
||||||
@@ -213,7 +212,10 @@
|
|||||||
await JsRuntime.InvokeVoidAsync("KTMenu.createInstances");
|
await JsRuntime.InvokeVoidAsync("KTMenu.createInstances");
|
||||||
await JsRuntime.InvokeVoidAsync("KTDrawer.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();
|
await SessionClientService.Start();
|
||||||
|
|
||||||
@@ -223,14 +225,14 @@
|
|||||||
await DynamicBackgroundService.Reset();
|
await DynamicBackgroundService.Reset();
|
||||||
};
|
};
|
||||||
|
|
||||||
if (User != null)
|
if (IdentityService.User != null)
|
||||||
{
|
{
|
||||||
await Event.On<SupportChatMessage>(
|
await Event.On<SupportChatMessage>(
|
||||||
$"supportChat.{User.Id}.message",
|
$"supportChat.{IdentityService.User.Id}.message",
|
||||||
this,
|
this,
|
||||||
async message =>
|
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}");
|
await ToastService.Info($"Support: {message.Content}");
|
||||||
}
|
}
|
||||||
@@ -257,9 +259,9 @@
|
|||||||
|
|
||||||
await KeyListenerService.DisposeAsync();
|
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
|
@code
|
||||||
{
|
{
|
||||||
private User? User;
|
|
||||||
|
|
||||||
protected override void OnInitialized()
|
protected override void OnInitialized()
|
||||||
{
|
{
|
||||||
AddBodyAttribute("data-kt-app-page-loading", "on");
|
AddBodyAttribute("data-kt-app-page-loading", "on");
|
||||||
@@ -95,7 +93,7 @@
|
|||||||
{
|
{
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
User = await IdentityService.Get();
|
await IdentityService.Load();
|
||||||
|
|
||||||
await InvokeAsync(StateHasChanged);
|
await InvokeAsync(StateHasChanged);
|
||||||
await Task.Delay(300);
|
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 DomainService DomainService
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
<OnlyAdmin>
|
@attribute [PermissionRequired(nameof(Permissions.AdminDomains))]
|
||||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
|
||||||
<div class="row">
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
<div class="card">
|
<div class="row">
|
||||||
<div class="card-header border-0 pt-5">
|
<div class="card">
|
||||||
<h3 class="card-title align-items-start flex-column">
|
<div class="card-header border-0 pt-5">
|
||||||
<span class="card-label fw-bold fs-3 mb-1">
|
<h3 class="card-title align-items-start flex-column">
|
||||||
<TL>Domains</TL>
|
<span class="card-label fw-bold fs-3 mb-1">
|
||||||
</span>
|
<TL>Domains</TL>
|
||||||
</h3>
|
</span>
|
||||||
<div class="card-toolbar">
|
</h3>
|
||||||
<a href="/admin/domains/new" class="btn btn-sm btn-light-success">
|
<div class="card-toolbar">
|
||||||
<i class="bx bx-layer-plus"></i>
|
<a href="/admin/domains/new" class="btn btn-sm btn-light-success">
|
||||||
<TL>New domain</TL>
|
<i class="bx bx-layer-plus"></i>
|
||||||
</a>
|
<TL>New domain</TL>
|
||||||
</div>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body pt-0">
|
</div>
|
||||||
<div class="table-responsive">
|
<div class="card-body pt-0">
|
||||||
<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">
|
<div class="table-responsive">
|
||||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
|
<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("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
|
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
|
||||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Shared domain"))" Field="@(x => x.SharedDomain)" Sortable="true" Filterable="true" Width="10%">
|
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
|
||||||
<Template>
|
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Shared domain"))" Field="@(x => x.SharedDomain)" Sortable="true" Filterable="true" Width="10%">
|
||||||
<span>@(context.SharedDomain.Name)</span>
|
<Template>
|
||||||
</Template>
|
<span>@(context.SharedDomain.Name)</span>
|
||||||
</Column>
|
</Template>
|
||||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true" Width="10%">
|
</Column>
|
||||||
<Template>
|
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Owner"))" Field="@(x => x.Owner)" Sortable="true" Filterable="true" Width="10%">
|
||||||
<a href="/admin/users/view/@(context.Owner.Id)">@(context.Owner.Email)</a>
|
<Template>
|
||||||
</Template>
|
<a href="/admin/users/view/@(context.Owner.Id)">@(context.Owner.Email)</a>
|
||||||
</Column>
|
</Template>
|
||||||
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
</Column>
|
||||||
<Template>
|
<Column TableItem="Domain" Title="@(SmartTranslateService.Translate("Manage"))" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
||||||
<a href="/domain/@(context.Id)">Manage</a>
|
<Template>
|
||||||
</Template>
|
<a href="/domain/@(context.Id)">Manage</a>
|
||||||
</Column>
|
</Template>
|
||||||
<Column TableItem="Domain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
</Column>
|
||||||
<Template>
|
<Column TableItem="Domain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
||||||
<DeleteButton Confirm="true"
|
<Template>
|
||||||
AdditionalCssClasses="float-end"
|
<DeleteButton Confirm="true"
|
||||||
OnClick="() => Delete(context)">
|
AdditionalCssClasses="float-end"
|
||||||
</DeleteButton>
|
OnClick="() => Delete(context)">
|
||||||
</Template>
|
</DeleteButton>
|
||||||
</Column>
|
</Template>
|
||||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
</Column>
|
||||||
</Table>
|
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||||
</div>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</LazyLoader>
|
</div>
|
||||||
</OnlyAdmin>
|
</LazyLoader>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -11,8 +11,9 @@
|
|||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
@inject DomainService DomainService
|
@inject DomainService DomainService
|
||||||
|
|
||||||
<OnlyAdmin>
|
@attribute [PermissionRequired(nameof(Permissions.AdminNewDomain))]
|
||||||
<div class="row mb-5">
|
|
||||||
|
<div class="row mb-5">
|
||||||
<div class="card card-body p-10">
|
<div class="card card-body p-10">
|
||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
<SmartForm Model="Model" OnValidSubmit="Add">
|
<SmartForm Model="Model" OnValidSubmit="Add">
|
||||||
@@ -48,7 +49,6 @@
|
|||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</OnlyAdmin>
|
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,39 +12,39 @@
|
|||||||
@inject AlertService AlertService
|
@inject AlertService AlertService
|
||||||
@inject ToastService ToastService
|
@inject ToastService ToastService
|
||||||
|
|
||||||
<OnlyAdmin>
|
@attribute [PermissionRequired(nameof(Permissions.AdminSharedDomains))]
|
||||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
|
||||||
<div class="card">
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
<div class="card-header border-0 pt-5">
|
<div class="card">
|
||||||
<h3 class="card-title align-items-start flex-column">
|
<div class="card-header border-0 pt-5">
|
||||||
<span class="card-label fw-bold fs-3 mb-1">
|
<h3 class="card-title align-items-start flex-column">
|
||||||
<span><TL>Shared domains</TL></span>
|
<span class="card-label fw-bold fs-3 mb-1">
|
||||||
</span>
|
<span><TL>Shared domains</TL></span>
|
||||||
</h3>
|
</span>
|
||||||
<div class="card-toolbar">
|
</h3>
|
||||||
<a href="/admin/domains/shared/new" class="btn btn-sm btn-light-success">
|
<div class="card-toolbar">
|
||||||
<i class="bx bx-layer-plus"></i>
|
<a href="/admin/domains/shared/new" class="btn btn-sm btn-light-success">
|
||||||
<span><TL>Add shared domain</TL></span>
|
<i class="bx bx-layer-plus"></i>
|
||||||
</a>
|
<span><TL>Add shared domain</TL></span>
|
||||||
</div>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
</div>
|
||||||
<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">
|
<div class="card-body">
|
||||||
<Column TableItem="SharedDomain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
|
<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("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
|
<Column TableItem="SharedDomain" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true" Width="10%"/>
|
||||||
<Column TableItem="SharedDomain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
<Column TableItem="SharedDomain" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true" Width="10%"/>
|
||||||
<Template>
|
<Column TableItem="SharedDomain" Title="" Field="@(x => x.Id)" Sortable="false" Filterable="false" Width="10%">
|
||||||
<DeleteButton Confirm="true"
|
<Template>
|
||||||
AdditionalCssClasses="float-end"
|
<DeleteButton Confirm="true"
|
||||||
OnClick="() => Delete(context)">
|
AdditionalCssClasses="float-end"
|
||||||
</DeleteButton>
|
OnClick="() => Delete(context)">
|
||||||
</Template>
|
</DeleteButton>
|
||||||
</Column>
|
</Template>
|
||||||
</Table>
|
</Column>
|
||||||
</div>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
</LazyLoader>
|
</div>
|
||||||
</OnlyAdmin>
|
</LazyLoader>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,6 +12,8 @@
|
|||||||
@inject ToastService ToastService
|
@inject ToastService ToastService
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
|
@attribute [PermissionRequired(nameof(Permissions.AdminNewSharedDomain))]
|
||||||
|
|
||||||
<LazyLoader Load="Load" @ref="LazyLoader">
|
<LazyLoader Load="Load" @ref="LazyLoader">
|
||||||
<div class="row mb-5">
|
<div class="row mb-5">
|
||||||
<div class="card card-body">
|
<div class="card card-body">
|
||||||
|
|||||||
@@ -14,118 +14,118 @@
|
|||||||
@inject DomainRepository DomainRepository
|
@inject DomainRepository DomainRepository
|
||||||
@inject ConfigService ConfigService
|
@inject ConfigService ConfigService
|
||||||
|
|
||||||
<OnlyAdmin>
|
@attribute [PermissionRequired(nameof(Permissions.AdminDashboard))]
|
||||||
<LazyLoader Load="Load">
|
|
||||||
<div class="row mb-5">
|
|
||||||
<div class="col-12 col-lg-6 col-xl">
|
|
||||||
<a class="mt-4 card" href="/admin/servers">
|
|
||||||
<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>
|
|
||||||
|
|
||||||
<LazyLoader Load="LoadHealthCheckData">
|
<LazyLoader Load="Load">
|
||||||
@if (HealthCheckData == null)
|
<div class="row mb-5">
|
||||||
{
|
<div class="col-12 col-lg-6 col-xl">
|
||||||
<div class="card">
|
<a class="mt-4 card" href="/admin/servers">
|
||||||
<div class="card-header">
|
<div class="card-body">
|
||||||
<div class="card-title">
|
<div class="row align-items-center gx-0">
|
||||||
<TL>Moonlight health</TL>
|
<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>
|
<div class="col-auto">
|
||||||
<div class="card-body">
|
<span class="h2 text-muted mb-0">
|
||||||
<div class="alert alert-warning">
|
<i class="text-primary bx bx-server bx-lg"></i>
|
||||||
<TL>Unable to fetch health check data</TL>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
}
|
</a>
|
||||||
else
|
</div>
|
||||||
{
|
<div class="col-12 col-lg-6 col-xl">
|
||||||
<HealthCheckView HealthCheck="@HealthCheckData"/>
|
<a class="mt-4 card" href="/admin/webspaces">
|
||||||
}
|
<div class="card-body">
|
||||||
</LazyLoader>
|
<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>
|
</LazyLoader>
|
||||||
</OnlyAdmin>
|
</LazyLoader>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -15,61 +15,61 @@
|
|||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
@inject EventSystem Event
|
@inject EventSystem Event
|
||||||
|
|
||||||
<OnlyAdmin>
|
@attribute [PermissionRequired(nameof(Permissions.AdminNodeDdos))]
|
||||||
<AdminNodesNavigation Index="1"/>
|
|
||||||
|
|
||||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
<AdminNodesNavigation Index="1"/>
|
||||||
<div class="card">
|
|
||||||
<div class="card-body pt-0">
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
<div class="table-responsive">
|
<div class="card">
|
||||||
<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">
|
<div class="card-body pt-0">
|
||||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
<div class="table-responsive">
|
||||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
<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">
|
||||||
<Template>
|
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Id)" Sortable="true" Filterable="true"/>
|
||||||
@if (context.Ongoing)
|
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
||||||
{
|
<Template>
|
||||||
<TL>DDos attack started</TL>
|
@if (context.Ongoing)
|
||||||
}
|
{
|
||||||
else
|
<TL>DDos attack started</TL>
|
||||||
{
|
}
|
||||||
<TL>DDos attack stopped</TL>
|
else
|
||||||
}
|
{
|
||||||
</Template>
|
<TL>DDos attack stopped</TL>
|
||||||
</Column>
|
}
|
||||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Node"))" Field="@(x => x.Node)" Sortable="false" Filterable="false">
|
</Template>
|
||||||
<Template>
|
</Column>
|
||||||
<a href="/admin/nodes/view/@(context.Id)">
|
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Node"))" Field="@(x => x.Node)" Sortable="false" Filterable="false">
|
||||||
@(context.Node.Name)
|
<Template>
|
||||||
</a>
|
<a href="/admin/nodes/view/@(context.Id)">
|
||||||
</Template>
|
@(context.Node.Name)
|
||||||
</Column>
|
</a>
|
||||||
<Column TableItem="DdosAttack" Title="Ip" Field="@(x => x.Ip)" Sortable="true" Filterable="true"/>
|
</Template>
|
||||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
</Column>
|
||||||
<Template>
|
<Column TableItem="DdosAttack" Title="Ip" Field="@(x => x.Ip)" Sortable="true" Filterable="true"/>
|
||||||
@if (context.Ongoing)
|
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Status"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
||||||
{
|
<Template>
|
||||||
@(context.Data)
|
@if (context.Ongoing)
|
||||||
<TL> packets</TL>
|
{
|
||||||
}
|
@(context.Data)
|
||||||
else
|
<TL> packets</TL>
|
||||||
{
|
}
|
||||||
@(context.Data)
|
else
|
||||||
<span> MB</span>
|
{
|
||||||
}
|
@(context.Data)
|
||||||
</Template>
|
<span> MB</span>
|
||||||
</Column>
|
}
|
||||||
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Date"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
</Template>
|
||||||
<Template>
|
</Column>
|
||||||
@(Formatter.FormatDate(context.CreatedAt))
|
<Column TableItem="DdosAttack" Title="@(SmartTranslateService.Translate("Date"))" Field="@(x => x.Ongoing)" Sortable="true" Filterable="true">
|
||||||
</Template>
|
<Template>
|
||||||
</Column>
|
@(Formatter.FormatDate(context.CreatedAt))
|
||||||
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
</Template>
|
||||||
</Table>
|
</Column>
|
||||||
</div>
|
<Pager ShowPageNumber="true" ShowTotalCount="true"/>
|
||||||
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</LazyLoader>
|
</div>
|
||||||
</OnlyAdmin>
|
</LazyLoader>
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
@@ -99,4 +99,6 @@
|
|||||||
{
|
{
|
||||||
await Event.Off("node.ddos", this);
|
await Event.Off("node.ddos", this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//TODO: Move to security
|
||||||
}
|
}
|
||||||
@@ -9,8 +9,9 @@
|
|||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
<OnlyAdmin>
|
@attribute [PermissionRequired(nameof(Permissions.AdminNodeEdit))]
|
||||||
<LazyLoader Load="Load" @ref="LazyLoader">
|
|
||||||
|
<LazyLoader Load="Load" @ref="LazyLoader">
|
||||||
@if (Node == null)
|
@if (Node == null)
|
||||||
{
|
{
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
@@ -149,7 +150,6 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
</OnlyAdmin>
|
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -13,89 +13,89 @@
|
|||||||
@inject NodeService NodeService
|
@inject NodeService NodeService
|
||||||
@inject SmartTranslateService SmartTranslateService
|
@inject SmartTranslateService SmartTranslateService
|
||||||
|
|
||||||
<OnlyAdmin>
|
@attribute [PermissionRequired(nameof(Permissions.AdminNodes))]
|
||||||
<AdminNodesNavigation Index="0"/>
|
|
||||||
|
|
||||||
<LazyLoader @ref="LazyLoader" Load="Load">
|
<AdminServersNavigation Index="3" />
|
||||||
<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;
|
|
||||||
}
|
|
||||||
|
|
||||||
@if (ss == null)
|
<LazyLoader @ref="LazyLoader" Load="Load">
|
||||||
{
|
<div class="card">
|
||||||
<span class="text-danger">Offline</span>
|
<div class="card-header border-0 pt-5">
|
||||||
}
|
<h3 class="card-title align-items-start flex-column">
|
||||||
else
|
<span class="card-label fw-bold fs-3 mb-1">
|
||||||
{
|
<TL>Nodes</TL>
|
||||||
<span class="text-success">Online (@(ss.Version))</span>
|
</span>
|
||||||
}
|
</h3>
|
||||||
</Template>
|
<div class="card-toolbar">
|
||||||
</Column>
|
<a href="/admin/nodes/new" class="btn btn-sm btn-light-success">
|
||||||
<Column TableItem="Node" Title="@(SmartTranslateService.Translate("Name"))" Field="@(x => x.Name)" Sortable="true" Filterable="true">
|
<i class="bx bx-layer-plus"></i>
|
||||||
<Template>
|
<TL>New node</TL>
|
||||||
<a href="/admin/nodes/view/@(context.Id)">@(context.Name)</a>
|
</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>
|
||||||
</div>
|
</div>
|
||||||
</LazyLoader>
|
<div class="card-body pt-0">
|
||||||
</OnlyAdmin>
|
@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
|
@code
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -9,8 +9,9 @@
|
|||||||
@inject NodeRepository NodeRepository
|
@inject NodeRepository NodeRepository
|
||||||
@inject NavigationManager NavigationManager
|
@inject NavigationManager NavigationManager
|
||||||
|
|
||||||
<OnlyAdmin>
|
@attribute [PermissionRequired(nameof(Permissions.AdminNewNode))]
|
||||||
<div class="d-flex flex-center">
|
|
||||||
|
<div class="d-flex flex-center">
|
||||||
<div class="card rounded-3 w-md-550px">
|
<div class="card rounded-3 w-md-550px">
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<div class="d-flex flex-center flex-column-fluid">
|
<div class="d-flex flex-center flex-column-fluid">
|
||||||
@@ -73,7 +74,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</OnlyAdmin>
|
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -6,12 +6,13 @@
|
|||||||
@inject NodeRepository NodeRepository
|
@inject NodeRepository NodeRepository
|
||||||
@inject ConfigService ConfigService
|
@inject ConfigService ConfigService
|
||||||
|
|
||||||
|
@attribute [PermissionRequired(nameof(Permissions.AdminNodeSetup))]
|
||||||
|
|
||||||
@{
|
@{
|
||||||
var appUrl = ConfigService.Get().Moonlight.AppUrl;
|
var appUrl = ConfigService.Get().Moonlight.AppUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
<OnlyAdmin>
|
<LazyLoader Load="Load">
|
||||||
<LazyLoader Load="Load">
|
|
||||||
@if (Node == null)
|
@if (Node == null)
|
||||||
{
|
{
|
||||||
<div class="alert alert-warning">
|
<div class="alert alert-warning">
|
||||||
@@ -141,7 +142,6 @@
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
</OnlyAdmin>
|
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,8 @@
|
|||||||
@inject NodeRepository NodeRepository
|
@inject NodeRepository NodeRepository
|
||||||
@inject NodeService NodeService
|
@inject NodeService NodeService
|
||||||
|
|
||||||
<OnlyAdmin>
|
@attribute [PermissionRequired(nameof(Permissions.AdminNodeView))]
|
||||||
|
|
||||||
<LazyLoader Load="Load">
|
<LazyLoader Load="Load">
|
||||||
@if (Node == null)
|
@if (Node == null)
|
||||||
{
|
{
|
||||||
@@ -252,7 +253,6 @@ else
|
|||||||
</div>
|
</div>
|
||||||
}
|
}
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
</OnlyAdmin>
|
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -12,8 +12,9 @@
|
|||||||
|
|
||||||
@implements IDisposable
|
@implements IDisposable
|
||||||
|
|
||||||
<OnlyAdmin>
|
@attribute [PermissionRequired(nameof(Permissions.AdminNotificationDebugging))]
|
||||||
<LazyLoader Load="Load">
|
|
||||||
|
<LazyLoader Load="Load">
|
||||||
<div class="card card-body">
|
<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">
|
<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"/>
|
<Column TableItem="ActiveNotificationClient" Title="@(SmartTranslateService.Translate("Id"))" Field="@(x => x.Client.Id)" Sortable="false" Filterable="true"/>
|
||||||
@@ -31,8 +32,6 @@
|
|||||||
</Table>
|
</Table>
|
||||||
</div>
|
</div>
|
||||||
</LazyLoader>
|
</LazyLoader>
|
||||||
</OnlyAdmin>
|
|
||||||
|
|
||||||
|
|
||||||
@code
|
@code
|
||||||
{
|
{
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user